From fbc445067f393b55f067e1a8c9f8cb2abc2b5077 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 11 Jul 2022 15:17:40 +0200 Subject: [PATCH 0001/1295] run error serializer check only before release (#4130) Signed-off-by: Matteo Collina --- package.json | 2 +- test/build/error-serializer.test.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5073f2e11a0..013c47e73f2 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "lint:markdown": "markdownlint-cli2", "lint:standard": "standard | snazzy", "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", - "prepublishOnly": "tap --no-check-coverage test/build/**.test.js", + "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js", "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", diff --git a/test/build/error-serializer.test.js b/test/build/error-serializer.test.js index d2ca8db6d08..8e8c8b12a2e 100644 --- a/test/build/error-serializer.test.js +++ b/test/build/error-serializer.test.js @@ -23,7 +23,9 @@ test('check generated code syntax', async (t) => { t.equal(result[0].fatalErrorCount, 0) }) -test('ensure the current error serializer is latest', async (t) => { +const isPrebublish = !!process.env.PREPUBLISH + +test('ensure the current error serializer is latest', { skip: !isPrebublish }, async (t) => { t.plan(1) const current = await fs.promises.readFile(path.resolve('lib/error-serializer.js')) From a3a20d649cd3dde773567ec021fd11cb1816aca0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 14:20:54 +0000 Subject: [PATCH 0002/1295] build(deps-dev): bump fastify-plugin from 3.0.1 to 4.0.0 (#4131) Bumps [fastify-plugin](https://github.com/fastify/fastify-plugin) from 3.0.1 to 4.0.0. - [Release notes](https://github.com/fastify/fastify-plugin/releases) - [Commits](https://github.com/fastify/fastify-plugin/compare/v3.0.1...v4.0.0) --- updated-dependencies: - dependency-name: fastify-plugin dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 013c47e73f2..8b9f097d62b 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,7 @@ "eslint-plugin-promise": "^6.0.0", "fast-json-body": "^1.1.0", "fast-json-stringify": "^5.0.0", - "fastify-plugin": "^3.0.1", + "fastify-plugin": "^4.0.0", "fluent-json-schema": "^3.1.0", "form-data": "^4.0.0", "frameguard": "^4.0.0", From 83f5dcf2d0aa73e20bdc40da0c37ac7cb2d3b084 Mon Sep 17 00:00:00 2001 From: David Ekete <88355936+davidekete@users.noreply.github.com> Date: Mon, 11 Jul 2022 16:28:24 +0100 Subject: [PATCH 0003/1295] Update README.md (#4132) Changed "If installing in an existing project, then Fastify can be installed into the project as a dependency:" to "To install Fastify in an existing project as a dependency:" because the first version is very hard to read, wordy, and doesn't have a clear meaning. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 90af4385471..d031e1c13f4 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,7 @@ generate functionality of [Fastify CLI](https://github.com/fastify/fastify-cli). ### Install -If installing in an existing project, then Fastify can be installed into the -project as a dependency: +To install Fastify in an existing project as a dependency: Install with npm: ```sh From 07a5d9abd2d98a1d6be9325c539010832b756e7d Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 12 Jul 2022 11:27:54 +0200 Subject: [PATCH 0004/1295] Revert "Re-add typed decorators (#4111)" (#4129) This reverts commit 20263a1bf8c8195d4692c634ecba86baa918b2d5. --- test/types/instance.test-d.ts | 40 +++++------------------------------ types/.eslintrc.json | 2 -- types/instance.d.ts | 40 +++++++---------------------------- 3 files changed, 13 insertions(+), 69 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 8f2837b2f86..a3dc17354dc 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -284,59 +284,29 @@ expectType(server.printRoutes({ includeMeta: ['key1', Symbol('key2')] }) expectType(server.printRoutes()) -server.decorate('nonexistent', () => {}) -server.decorateRequest('nonexistent', () => {}) -server.decorateReply('nonexistent', () => {}) - -declare module '../../fastify' { - interface FastifyInstance { - functionWithTypeDefinition: (foo: string, bar: number) => Promise - } - interface FastifyRequest { - numberWithTypeDefinition: number - } - interface FastifyReply { - stringWithTypeDefinition: 'foo' | 'bar' - } -} -expectError(server.decorate('functionWithTypeDefinition', (foo: any, bar: any) => {})) // error because invalid return type -expectError(server.decorate('functionWithTypeDefinition', (foo: any, bar: any) => true)) // error because doesn't return a promise -expectError(server.decorate('functionWithTypeDefinition', async (foo: any, bar: any, qwe: any) => true)) // error because too many args -expectAssignable(server.decorate('functionWithTypeDefinition', async (foo, bar) => { - expectType(foo) - expectType(bar) - return true -})) - -expectError(server.decorateRequest('numberWithTypeDefinition', 'not a number')) // error because invalid type -expectAssignable(server.decorateRequest('numberWithTypeDefinition', 10)) - -expectError(server.decorateReply('stringWithTypeDefinition', 'not in enum')) // error because invalid type -expectAssignable(server.decorateReply('stringWithTypeDefinition', 'foo')) - -server.decorate<'test', (x: string) => void>('test', function (x: string): void { +server.decorate<(x: string) => void>('test', function (x: string): void { expectType(this) }) server.decorate('test', function (x: string): void { expectType(this) }) -server.decorateRequest<'test', (x: string, y: number) => void>('test', function (x: string, y: number): void { +server.decorateRequest<(x: string, y: number) => void>('test', function (x: string, y: number): void { expectType(this) }) server.decorateRequest('test', function (x: string, y: number): void { expectType(this) }) -server.decorateReply<'test', (x: string) => void>('test', function (x: string): void { +server.decorateReply<(x: string) => void>('test', function (x: string): void { expectType(this) }) server.decorateReply('test', function (x: string): void { expectType(this) }) -expectError(server.decorate<'test', string>('test', true)) -expectError(server.decorate<'test', (myNumber: number) => number>('test', function (myNumber: number): string { +expectError(server.decorate('test', true)) +expectError(server.decorate<(myNumber: number) => number>('test', function (myNumber: number): string { return '' })) diff --git a/types/.eslintrc.json b/types/.eslintrc.json index 8389d4cf0da..ad7fba78019 100644 --- a/types/.eslintrc.json +++ b/types/.eslintrc.json @@ -17,8 +17,6 @@ "rules": { "no-console": "off", "@typescript-eslint/indent": ["error", 2], - "func-call-spacing": "off", - "@typescript-eslint/func-call-spacing": ["error"], "semi": ["error", "never"], "import/export": "off" // this errors on multiple exports (overload interfaces) }, diff --git a/types/instance.d.ts b/types/instance.d.ts index 078abf57394..1d03d37e7ca 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -107,45 +107,21 @@ export interface FastifyInstance< close(closeListener: () => void): undefined; // should be able to define something useful with the decorator getter/setter pattern using Generics to enforce the users function returns what they expect it to - decorate( - property: K, - value: FastifyInstance[K] extends (...args: any[]) => any - ? (this: FastifyInstance, ...args: Parameters) => ReturnType - : FastifyInstance[K], - dependencies?: string[] - ): FastifyInstance; - decorate( - property: NotInInterface, + decorate(property: string | symbol, value: T extends (...args: any[]) => any ? (this: FastifyInstance, ...args: Parameters) => ReturnType : T, dependencies?: string[] ): FastifyInstance; - decorateRequest( - property: K, - value: FastifyRequest[K] extends (...args: any[]) => any - ? (this: FastifyRequest, ...args: Parameters) => ReturnType - : FastifyRequest[K], - dependencies?: string[] - ): FastifyInstance; - decorateRequest( - property: NotInInterface, + decorateRequest(property: string | symbol, value: T extends (...args: any[]) => any ? (this: FastifyRequest, ...args: Parameters) => ReturnType : T, dependencies?: string[] ): FastifyInstance; - decorateReply( - property: K, - value: FastifyReply[K] extends (...args: any[]) => any - ? (this: FastifyReply, ...args: Parameters) => ReturnType - : FastifyReply[K], - dependencies?: string[] - ): FastifyInstance; - decorateReply( - property: NotInInterface, + decorateReply(property: string | symbol, value: T extends (...args: any[]) => any ? (this: FastifyReply, ...args: Parameters) => ReturnType : T, @@ -171,17 +147,17 @@ export interface FastifyInstance< * @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5` * @see https://github.com/fastify/fastify/pull/3712 */ - listen(port: number | string, address: string, backlog: number, callback: (err: Error | null, address: string) => void): void; + listen(port: number | string, address: string, backlog: number, callback: (err: Error|null, address: string) => void): void; /** * @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5` * @see https://github.com/fastify/fastify/pull/3712 */ - listen(port: number | string, address: string, callback: (err: Error | null, address: string) => void): void; + listen(port: number | string, address: string, callback: (err: Error|null, address: string) => void): void; /** * @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5` * @see https://github.com/fastify/fastify/pull/3712 */ - listen(port: number | string, callback: (err: Error | null, address: string) => void): void; + listen(port: number | string, callback: (err: Error|null, address: string) => void): void; /** * @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject)` instead. The variadic signature will be removed in `fastify@5` * @see https://github.com/fastify/fastify/pull/3712 @@ -508,11 +484,11 @@ export interface FastifyInstance< /** * Set the 404 handler */ - setNotFoundHandler( + setNotFoundHandler ( handler: (request: FastifyRequest, reply: FastifyReply) => void | Promise ): FastifyInstance; - setNotFoundHandler( + setNotFoundHandler ( opts: { preValidation?: preValidationHookHandler | preValidationHookHandler[]; preHandler?: preHandlerHookHandler | preHandlerHookHandler[]; From d6e06c7d89a0c80f8c2a42ffbda6ae927f6ef0dc Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 12 Jul 2022 11:54:52 +0200 Subject: [PATCH 0005/1295] Bumped v4.2.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 24 ++++++++++++------------ package.json | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/fastify.js b/fastify.js index 7a3829b91ed..476c2a51e87 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.2.0' +const VERSION = '4.2.1' const Avvio = require('avvio') const http = require('http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index 480effc7b74..8e8b4f52a1e 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -74,24 +74,24 @@ class Serializer { return bool === null ? 'null' : this.asBoolean(bool) } - asDatetime (date) { - const quotes = '"' + asDateTime (date) { + if (date === null) return '""' if (date instanceof Date) { - return quotes + date.toISOString() + quotes + return '"' + date.toISOString() + '"' } - return this.asString(date) + throw new Error(`The value "${date}" cannot be converted to a date-time.`) } - asDatetimeNullable (date) { - return date === null ? 'null' : this.asDatetime(date) + asDateTimeNullable (date) { + return date === null ? 'null' : this.asDateTime(date) } asDate (date) { - const quotes = '"' + if (date === null) return '""' if (date instanceof Date) { - return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes + return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"' } - return this.asString(date) + throw new Error(`The value "${date}" cannot be converted to a date.`) } asDateNullable (date) { @@ -99,11 +99,11 @@ class Serializer { } asTime (date) { - const quotes = '"' + if (date === null) return '""' if (date instanceof Date) { - return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes + return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"' } - return this.asString(date) + throw new Error(`The value "${date}" cannot be converted to a time.`) } asTimeNullable (date) { diff --git a/package.json b/package.json index 8b9f097d62b..0387452b100 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.2.0", + "version": "4.2.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From ed4f4c1e2abe6c30015556e97420f163700ce125 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 12 Jul 2022 17:09:46 +0200 Subject: [PATCH 0006/1295] improve: dont cache unnecessary content types (#4134) --- lib/contentTypeParser.js | 12 ++++++++++-- test/content-parser.test.js | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index be9c829ae1c..48e293390b0 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -92,10 +92,18 @@ ContentTypeParser.prototype.existingParser = function (contentType) { } ContentTypeParser.prototype.getParser = function (contentType) { + if (contentType in this.customParsers) { + return this.customParsers[contentType] + } + + if (this.cache.has(contentType)) { + return this.cache.get(contentType) + } + // eslint-disable-next-line no-var for (var i = 0; i !== this.parserList.length; ++i) { const parserName = this.parserList[i] - if (contentType.indexOf(parserName) > -1) { + if (contentType.indexOf(parserName) !== -1) { const parser = this.customParsers[parserName] this.cache.set(contentType, parser) return parser @@ -137,7 +145,7 @@ ContentTypeParser.prototype.remove = function (contentType) { } ContentTypeParser.prototype.run = function (contentType, handler, request, reply) { - const parser = this.cache.get(contentType) || this.getParser(contentType) + const parser = this.getParser(contentType) const resource = new AsyncResource('content-type-parser:run', request) if (parser === undefined) { diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 32ea5847133..87f79bd4058 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -58,6 +58,21 @@ test('getParser', t => { t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, third) }) + test('should return matching parser with caching', t => { + t.plan(6) + + const fastify = Fastify() + + fastify.addContentTypeParser('text/html', first) + + t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 0) + t.equal(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 1) + t.equal(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 1) + }) + test('should prefer content type parser with string value', t => { t.plan(2) From fe889eaeead123a841bc4dc017af7ad1d5dcc293 Mon Sep 17 00:00:00 2001 From: Branislav Katreniak Date: Tue, 12 Jul 2022 18:07:42 +0200 Subject: [PATCH 0007/1295] fix: default clientError replies on reused connection (#4101) (#4133) When fastify server receives request with invalid url in a reused connection, it closes the connection instead of 400 Bad Request reply. The closed connection is then propagated by load balancer (ALB) as 502 error. This turns client errors into closely monitored server errors. `socket.bytesWritten` is never going to be 0 on reused connection. Co-authored-by: Branislav Katreniak --- fastify.js | 2 +- test/request-error.test.js | 45 +++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 476c2a51e87..7769e927a83 100644 --- a/fastify.js +++ b/fastify.js @@ -621,7 +621,7 @@ function fastify (options) { // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666 // If the socket is not writable, there is no reason to try to send data. - if (socket.writable && socket.bytesWritten === 0) { + if (socket.writable) { socket.write(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`) } socket.destroy(err) diff --git a/test/request-error.test.js b/test/request-error.test.js index 15924c7b34e..a3be6e1870e 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -150,7 +150,7 @@ test('default clientError handler ignores sockets in destroyed state', t => { }) test('default clientError handler destroys sockets in writable state', t => { - t.plan(1) + t.plan(2) const fastify = Fastify({ bodyLimit: 1, @@ -166,6 +166,9 @@ test('default clientError handler destroys sockets in writable state', t => { }, destroy () { t.pass('destroy should be called') + }, + write (response) { + t.match(response, /^HTTP\/1.1 400 Bad Request/) } }) }) @@ -186,6 +189,9 @@ test('default clientError handler destroys http sockets in non-writable state', }, destroy () { t.pass('destroy should be called') + }, + write (response) { + t.fail('write should not be called') } }) }) @@ -270,3 +276,40 @@ test('encapsulated error handler binding', t => { t.equal(fastify.hello, undefined) }) }) + +test('default clientError replies with bad request on reused keep-alive connection', t => { + t.plan(2) + + let response = '' + + const fastify = Fastify({ + bodyLimit: 1, + keepAliveTimeout: 100 + }) + + fastify.get('/', (request, reply) => { + reply.send('OK\n') + }) + + fastify.listen({ port: 0 }, function (err) { + t.error(err) + fastify.server.unref() + + const client = connect(fastify.server.address().port) + + client.on('data', chunk => { + response += chunk.toString('utf-8') + }) + + client.on('end', () => { + t.match(response, /^HTTP\/1.1 200 OK.*HTTP\/1.1 400 Bad Request/s) + }) + + client.resume() + client.write('GET / HTTP/1.1\r\n') + client.write('\r\n\r\n') + client.write('GET /?a b HTTP/1.1\r\n') + client.write('Connection: close\r\n') + client.write('\r\n\r\n') + }) +}) From 329ab5de858ddfe625c310d5831536fe34d012b3 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Wed, 13 Jul 2022 10:19:38 -0400 Subject: [PATCH 0008/1295] docs(ecosystem): add electron-server (#4136) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 44760038680..861c5f6d9bd 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -169,6 +169,8 @@ section. - [`cls-rtracer`](https://github.com/puzpuzpuz/cls-rtracer) Fastify middleware for CLS-based request ID generation. An out-of-the-box solution for adding request IDs into your logs. +- [`electron-server`](https://github.com/anonrig/electron-server) A plugin for + using Fastify without the need of consuming a port on Electron apps. - [`fast-water`](https://github.com/tswayne/fast-water) A Fastify plugin for waterline. Decorates Fastify with waterline models. - [`fastify-405`](https://github.com/Eomm/fastify-405) Fastify plugin that adds From abab5882d13555616c81cd88c9541ade28698fe0 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Thu, 14 Jul 2022 12:34:22 +0200 Subject: [PATCH 0009/1295] feat: expose validate/serialize functions through Request and Reply (#3970) * feat: add routeOptions to context * feat: initial draft of validate/serialize functions * feat: add serialize method to request object * refactor: order symbols * feat: add cache to validate/serialize * test: add initial round of testing * fix: set custom serialize/validator compiler if any * fix(context): remove branching * test: add request#serialize tests * revert: context.routeOptions * refactor: refactor validation implementation * feat: add serialize helpers to reply * test(request): add missing tests * feat: add new error for missing serialization fn * fix(reply) handle missed kSchemaResponse * refactor(reply): handle httpStatus or schema dynamically * fix(reply): typo * refactor: apply requested changes on Reply * refactor: apply requested changes to Request * feat: add errors for invalid invocation * refactor: symbols * test(request): add 1st level nested testing * test: add default shape for context.config * test(request): extend 1.1 * test: adjust test counter * test: add 1 level nested scope testing * fix(test): error serializer * fix(test): reply serializer coverage * fix(test): request validator coverage * fix(coverage): 100 * refactor(reply): improve name of reply serialize map * refactor(request): make symbol for request implicit * test(request): add more scenarios * docs: add comment into lib/request.js Co-authored-by: Manuel Spigolon * refactor(request): lazy load Weakmap for caching * refactor(reply): lazy build cache * feat(types): add new types * refactor: remove leftover * test: extend coverage * docs(reply): add documentation * test: remove leftovers * test: adjust counter Co-authored-by: Manuel Spigolon Co-authored-by: Manuel Spigolon --- docs/Reference/Reply.md | 176 ++++ lib/context.js | 11 +- lib/errors.js | 8 + lib/reply.js | 82 +- lib/request.js | 98 +- lib/route.js | 2 + lib/symbols.js | 24 +- test/internals/reply-serialize.test.js | 583 +++++++++++ test/internals/request-validate.test.js | 1269 +++++++++++++++++++++++ test/internals/request.test.js | 13 +- types/reply.d.ts | 5 + types/request.d.ts | 7 + 12 files changed, 2263 insertions(+), 15 deletions(-) create mode 100644 test/internals/reply-serialize.test.js create mode 100644 test/internals/request-validate.test.js diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index bc0b9bf30db..0735f4746d6 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -20,6 +20,9 @@ - [.callNotFound()](#callnotfound) - [.getResponseTime()](#getresponsetime) - [.type(contentType)](#typecontenttype) + - [.getSerializationFunction(schema | httpStatus)](#getserializationfunction) + - [.compileSerializationSchema(schema, httpStatus)](#compileserializationschema) + - [.serializeInput(data, [schema | httpStatus], [httpStatus])](#serializeinput) - [.serializer(func)](#serializerfunc) - [.raw](#raw) - [.sent](#sent) @@ -60,6 +63,16 @@ object that exposes the following functions and properties: - `.serialize(payload)` - Serializes the specified payload using the default JSON serializer or using the custom serializer (if one is set) and returns the serialized payload. +- `.getSerializationFunction(schema | httpStatus)` - Returns the serialization + function for the specified schema or http status, if any of either are set. +- `.compileSerializationSchema(schema, httpStatus)` - Compiles the specified + schema and returns a serialization function using the default (or customized) + `SerializerCompiler`. The optional `httpStatus` is forwarded to the + `SerializerCompiler` if provided, default to `undefined`. +- `.serializeInput(data, schema, [,httpStatus])` - Serializes the specified data + using the specified schema and returns the serialized payload. + If the optional `httpStatus` is provided, the function will use the serializer + function given for that HTTP Status Code. Default to `undefined`. - `.serializer(function)` - Sets a custom serializer for the payload. - `.send(payload)` - Sends the payload to the user, could be a plain text, a buffer, JSON, stream, or an Error object. @@ -326,6 +339,169 @@ reply.type('text/html') If the `Content-Type` has a JSON subtype, and the charset parameter is not set, `utf-8` will be used as the charset by default. +### .getSerializationFunction(schema | httpStatus) + + +By calling this function using a provided `schema` or `httpStatus`, +it will return a `serialzation` function that can be used to +serialize diverse inputs. It returns `undefined` if no +serialization function was found using either of the provided inputs. + +This heavily depends of the `schema#responses` attached to the route, or +the serialization functions compiled by using `compileSerializationSchema`. + +```js +const serialize = reply + .getSerializationFunction({ + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }) +serialize({ foo: 'bar' }) // '{"foo":"bar"}' + +// or + +const serialize = reply + .getSerializationFunction(200) +serialize({ foo: 'bar' }) // '{"foo":"bar"}' +``` + +See [.compileSerializationSchema(schema, httpStatus)](#compileserializationschema) +for more information on how to compile serialization schemas. + +### .compileSerializationSchema(schema, httpStatus) + + +This function will compile a serialization schema and +return a function that can be used to serialize data. +The function returned (a.k.a. _serialization function_) returned is compiled +by using the provided `SerializerCompiler`. Also this is cached by using +a `WeakMap` for reducing compilation calls. + +The optional paramater `httpStatus`, if provided, is forwarded directly +the `SerializerCompiler`, so it can be used to compile the serialization +function if a custom `SerializerCompiler` is used. + +This heavily depends of the `schema#responses` attached to the route, or +the serialization functions compiled by using `compileSerializationSchema`. + +```js +const serialize = reply + .compileSerializationSchema({ + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }) +serialize({ foo: 'bar' }) // '{"foo":"bar"}' + +// or + +const serialize = reply + .compileSerializationSchema({ + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }, 200) +serialize({ foo: 'bar' }) // '{"foo":"bar"}' +``` + +Note that you should be careful when using this function, as it will cache +the compiled serialization functions based on the schema provided. If the +schemas provided is mutated or changed, the serialization functions will not +detect that the schema has been altered and for instance it will reuse the +previously compiled serialization function based on the reference of the schema +previously provided. + +If there's a need to change the properties of a schema, always opt to create +a totally new object, otherwise the implementation won't benefit from the cache +mechanism. + +:Using the following schema as example: +```js +const schema1 = { + type: 'object', + properties: { + foo: { + type: 'string' + } + } +} +``` + +*Not* +```js +const serialize = reply.compileSerializationSchema(schema1) + +// Later on... +schema1.properties.foo.type. = 'integer' +const newSerialize = reply.compileSerializationSchema(schema1) + +console.log(newSerialize === serialize) // true +``` + +*Instead* +```js +const serialize = reply.compileSerializationSchema(schema1) + +// Later on... +const newSchema = Object.assign({}, schema1) +newSchema.properties.foo.type = 'integer' + +const newSerialize = reply.compileSerializationSchema(newSchema) + +console.log(newSerialize === serialize) // false +``` + +### .serializeInput(data, [schema | httpStatus], [httpStatus]) + + +This function will serialize the input data based on the provided schema, +or http status code. If both provided, the `httpStatus` will take presedence. + +If there is not a serialization function for a given `schema`, a new serialization +function will be compiled forwarding the `httpStatus` if provided. + +```js +reply + .serializeInput({ foo: 'bar'}, { + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }) // '{"foo":"bar"}' + +// or + +reply + .serializeInput({ foo: 'bar'}, { + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }, 200) // '{"foo":"bar"}' + +// or + +reply + .serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}' +``` + +See [.compileSerializationSchema(schema, httpStatus)](#compileserializationschema) +for more information on how to compile serialization schemas. + ### .serializer(func) diff --git a/lib/context.js b/lib/context.js index 6baf0bf2f7d..4d3ad1c5f14 100644 --- a/lib/context.js +++ b/lib/context.js @@ -10,7 +10,9 @@ const { kBodyLimit, kLogLevel, kContentTypeParser, - kRouteByFastify + kRouteByFastify, + kRequestValidateWeakMap, + kReplySerializeWeakMap } = require('./symbols.js') // Objects that holds the context of every request @@ -24,6 +26,8 @@ function Context ({ logLevel, logSerializers, attachValidation, + validatorCompiler, + serializerCompiler, replySerializer, schemaErrorFormatter, server, @@ -54,6 +58,11 @@ function Context ({ this.schemaErrorFormatter = schemaErrorFormatter || server[kSchemaErrorFormatter] || defaultSchemaErrorFormatter this[kRouteByFastify] = isFastify + this[kRequestValidateWeakMap] = null + this[kReplySerializeWeakMap] = null + this.validatorCompiler = validatorCompiler || null + this.serializerCompiler = serializerCompiler || null + this.server = server } diff --git a/lib/errors.js b/lib/errors.js index 8633b997415..66fb1ec8b48 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -160,6 +160,14 @@ const codes = { 'FST_ERR_BAD_TRAILER_VALUE', "Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function." ), + FST_ERR_MISSING_SERIALIZATION_FN: createError( + 'FST_ERR_MISSING_SERIALIZATION_FN', + 'Missing serialization function. Key "%s"' + ), + FST_ERR_REQ_INVALID_VALIDATION_INVOCATION: createError( + 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION', + 'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.' + ), /** * schemas diff --git a/lib/reply.js b/lib/reply.js index 45db78b96e4..7ec650571d7 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -16,7 +16,11 @@ const { kReplyHasStatusCode, kReplyIsRunningOnErrorHook, kReplyNextErrorHandler, - kDisableRequestLogging + kDisableRequestLogging, + kSchemaResponse, + kReplySerializeWeakMap, + kSchemaController, + kOptions } = require('./symbols.js') const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks') @@ -38,7 +42,8 @@ const { FST_ERR_SEND_INSIDE_ONERR, FST_ERR_BAD_STATUS_CODE, FST_ERR_BAD_TRAILER_NAME, - FST_ERR_BAD_TRAILER_VALUE + FST_ERR_BAD_TRAILER_VALUE, + FST_ERR_MISSING_SERIALIZATION_FN } = require('./errors') const warning = require('./warnings') @@ -299,6 +304,79 @@ Reply.prototype.code = function (code) { Reply.prototype.status = Reply.prototype.code +Reply.prototype.getSerializationFunction = function (schemaOrStatus) { + let serialize + + if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') { + serialize = this.context[kSchemaResponse]?.[schemaOrStatus] + } else if (typeof schemaOrStatus === 'object') { + serialize = this.context[kReplySerializeWeakMap]?.get(schemaOrStatus) + } + + return serialize +} + +Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null) { + const { request } = this + const { method, url } = request + + // Check if serialize function already compiled + if (this.context[kReplySerializeWeakMap]?.has(schema)) { + return this.context[kReplySerializeWeakMap].get(schema) + } + + const serializerCompiler = this.context.serializerCompiler || + this.server[kSchemaController].serializerCompiler || + ( + // We compile the schemas if no custom serializerCompiler is provided + // nor set + this.server[kSchemaController].setupSerializer(this.server[kOptions]) || + this.server[kSchemaController].serializerCompiler + ) + + const serializeFn = serializerCompiler({ + schema, + method, + url, + httpStatus + }) + + // We create a WeakMap to compile the schema only once + // Its done leazily to avoid add overhead by creating the WeakMap + // if it is not used + // TODO: Explore a central cache for all the schemas shared across + // encapsulated contexts + if (this.context[kReplySerializeWeakMap] == null) { + this.context[kReplySerializeWeakMap] = new WeakMap() + } + + this.context[kReplySerializeWeakMap].set(schema, serializeFn) + + return serializeFn +} + +Reply.prototype.serializeInput = function (input, schema, httpStatus) { + let serialize + httpStatus = typeof schema === 'string' || typeof schema === 'number' + ? schema + : httpStatus + + if (httpStatus != null) { + serialize = this.context[kSchemaResponse]?.[httpStatus] + + if (serialize == null) throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus) + } else { + // Check if serialize function already compiled + if (this.context[kReplySerializeWeakMap]?.has(schema)) { + serialize = this.context[kReplySerializeWeakMap].get(schema) + } else { + serialize = this.compileSerializationSchema(schema, httpStatus) + } + } + + return serialize(input) +} + Reply.prototype.serialize = function (payload) { if (this[kReplySerializer] !== null) { return this[kReplySerializer](payload) diff --git a/lib/request.js b/lib/request.js index c2f6850d746..088a0825c28 100644 --- a/lib/request.js +++ b/lib/request.js @@ -4,8 +4,24 @@ const proxyAddr = require('proxy-addr') const semver = require('semver') const warning = require('./warnings') const { - kHasBeenDecorated + kHasBeenDecorated, + kSchemaBody, + kSchemaHeaders, + kSchemaParams, + kSchemaQuerystring, + kSchemaController, + kOptions, + kRequestValidateWeakMap } = require('./symbols') +const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors') + +const HTTP_PART_SYMBOL_MAP = { + body: kSchemaBody, + headers: kSchemaHeaders, + params: kSchemaParams, + querystring: kSchemaQuerystring, + query: kSchemaQuerystring +} function Request (id, params, req, query, log, context) { this.id = id @@ -194,6 +210,86 @@ Object.defineProperties(Request.prototype, { set (headers) { this.additionalHeaders = headers } + }, + getValidationFunction: { + value: function (httpPartOrSchema) { + if (typeof httpPartOrSchema === 'string') { + const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema] + return this.context[symbol] + } else if (typeof httpPartOrSchema === 'object') { + return this.context[kRequestValidateWeakMap]?.get(httpPartOrSchema) + } + } + }, + compileValidationSchema: { + value: function (schema, httpPart) { + const { method, url } = this + + if (this.context[kRequestValidateWeakMap]?.has(schema)) { + return this.context[kRequestValidateWeakMap].get(schema) + } + + const validatorCompiler = this.context.validatorCompiler || + this.server[kSchemaController].validatorCompiler || + ( + // We compile the schemas if no custom validatorCompiler is provided + // nor set + this.server[kSchemaController].setupValidator(this.server[kOptions]) || + this.server[kSchemaController].validatorCompiler + ) + + const validateFn = validatorCompiler({ + schema, + method, + url, + httpPart + }) + + // We create a WeakMap to compile the schema only once + // Its done leazily to avoid add overhead by creating the WeakMap + // if it is not used + // TODO: Explore a central cache for all the schemas shared across + // encapsulated contexts + if (this.context[kRequestValidateWeakMap] == null) { + this.context[kRequestValidateWeakMap] = new WeakMap() + } + + this.context[kRequestValidateWeakMap].set(schema, validateFn) + + return validateFn + } + }, + validate: { + value: function (input, schema, httpPart) { + httpPart = typeof schema === 'string' ? schema : httpPart + + const symbol = (httpPart != null && typeof httpPart === 'string') && HTTP_PART_SYMBOL_MAP[httpPart] + let validate + + if (symbol) { + // Validate using the HTTP Request Part schema + validate = this.context[symbol] + } + + // We cannot compile if the schema is missed + if (validate == null && (schema == null || + typeof schema !== 'object' || + Array.isArray(schema)) + ) { + throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart) + } + + if (validate == null) { + if (this.context[kRequestValidateWeakMap]?.has(schema)) { + validate = this.context[kRequestValidateWeakMap].get(schema) + } else { + // We proceed to compile if there's no validate function yet + validate = this.compileValidationSchema(schema, httpPart) + } + } + + return validate(input) + } } }) diff --git a/lib/route.js b/lib/route.js index fbc45a418c5..44cf6a51604 100644 --- a/lib/route.js +++ b/lib/route.js @@ -241,6 +241,8 @@ function buildRouting (options) { attachValidation: opts.attachValidation, schemaErrorFormatter: opts.schemaErrorFormatter, replySerializer: this[kReplySerializerDefault], + validatorCompiler: opts.validatorCompiler, + serializerCompiler: opts.serializerCompiler, server: this, isFastify }) diff --git a/lib/symbols.js b/lib/symbols.js index de534e29eb5..23df25e7527 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -9,6 +9,12 @@ const keys = { kLogLevel: Symbol('fastify.logLevel'), kLogSerializers: Symbol('fastify.logSerializers'), kHooks: Symbol('fastify.hooks'), + kContentTypeParser: Symbol('fastify.contentTypeParser'), + kState: Symbol('fastify.state'), + kOptions: Symbol('fastify.options'), + kDisableRequestLogging: Symbol('fastify.disableRequestLogging'), + kPluginNameChain: Symbol('fastify.pluginNameChain'), + // Schema kSchemaController: Symbol('fastify.schemaController'), kSchemaHeaders: Symbol('headers-schema'), kSchemaParams: Symbol('params-schema'), @@ -16,17 +22,20 @@ const keys = { kSchemaBody: Symbol('body-schema'), kSchemaResponse: Symbol('response-schema'), kSchemaErrorFormatter: Symbol('fastify.schemaErrorFormatter'), - kReplySerializerDefault: Symbol('fastify.replySerializerDefault'), - kContentTypeParser: Symbol('fastify.contentTypeParser'), - kReply: Symbol('fastify.Reply'), + kSchemaVisited: Symbol('fastify.schemas.visited'), + // Request kRequest: Symbol('fastify.Request'), + kRequestValidateFns: Symbol('fastify.request.cache.validateFns'), kRequestPayloadStream: Symbol('fastify.RequestPayloadStream'), kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'), - kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'), + // 404 kFourOhFour: Symbol('fastify.404'), + kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'), kFourOhFourLevelInstance: Symbol('fastify.404LogLevelInstance'), kFourOhFourContext: Symbol('fastify.404ContextKey'), kDefaultJsonParse: Symbol('fastify.defaultJSONParse'), + // Reply + kReply: Symbol('fastify.Reply'), kReplySerializer: Symbol('fastify.reply.serializer'), kReplyIsError: Symbol('fastify.reply.isError'), kReplyHeaders: Symbol('fastify.reply.headers'), @@ -38,11 +47,8 @@ const keys = { kReplyEndTime: Symbol('fastify.reply.endTime'), kReplyErrorHandlerCalled: Symbol('fastify.reply.errorHandlerCalled'), kReplyIsRunningOnErrorHook: Symbol('fastify.reply.isRunningOnErrorHook'), - kSchemaVisited: Symbol('fastify.schemas.visited'), - kState: Symbol('fastify.state'), - kOptions: Symbol('fastify.options'), - kDisableRequestLogging: Symbol('fastify.disableRequestLogging'), - kPluginNameChain: Symbol('fastify.pluginNameChain'), + kReplySerializerDefault: Symbol('fastify.replySerializerDefault'), + kReplySerializeWeakMap: Symbol('fastify.reply.cache.serializeFns'), // This symbol is only meant to be used for fastify tests and should not be used for any other purpose kTestInternals: Symbol('fastify.testInternals'), kErrorHandler: Symbol('fastify.errorHandler'), diff --git a/test/internals/reply-serialize.test.js b/test/internals/reply-serialize.test.js new file mode 100644 index 00000000000..9da883dc6e4 --- /dev/null +++ b/test/internals/reply-serialize.test.js @@ -0,0 +1,583 @@ +'use strict' + +const { test } = require('tap') +const { kReplySerializeWeakMap } = require('../../lib/symbols') +const Fastify = require('../../fastify') + +function getDefaultSchema () { + return { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' }, + world: { type: 'string' } + } + } +} + +function getResponseSchema () { + return { + 201: { + type: 'object', + required: ['status'], + properties: { + status: { + type: 'string', + enum: ['ok'] + }, + message: { + type: 'string' + } + } + }, + '4xx': { + type: 'object', + properties: { + status: { + type: 'string', + enum: ['error'] + }, + code: { + type: 'integer', + minimum: 1 + }, + message: { + type: 'string' + } + } + } + } +} + +test('Reply#compileSerializationSchema', t => { + t.plan(4) + + t.test('Should return a serialization function', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + const serialize = reply.compileSerializationSchema(getDefaultSchema()) + const input = { hello: 'world' } + t.type(serialize, Function) + t.type(serialize(input), 'string') + t.equal(serialize(input), JSON.stringify(input)) + + try { + serialize({ world: 'foo' }) + } catch (err) { + t.equal(err.message, '"hello" is required!') + } + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + t.test('Should reuse the serialize fn across multiple invocations - Route without schema', + async t => { + const fastify = Fastify() + let serialize = null + let counter = 0 + + t.plan(17) + + const schemaObj = getDefaultSchema() + + fastify.get('/', (req, reply) => { + const input = { hello: 'world' } + counter++ + if (counter > 1) { + const newSerialize = reply.compileSerializationSchema(schemaObj) + t.equal(serialize, newSerialize, 'Are the same validate function') + serialize = newSerialize + } else { + t.pass('build the schema compilation function') + serialize = reply.compileSerializationSchema(schemaObj) + } + + t.type(serialize, Function) + t.equal(serialize(input), JSON.stringify(input)) + + try { + serialize({ world: 'foo' }) + } catch (err) { + t.equal(err.message, '"hello" is required!') + } + + reply.send({ hello: 'world' }) + }) + + await Promise.all([ + fastify.inject('/'), + fastify.inject('/'), + fastify.inject('/'), + fastify.inject('/') + ]) + + t.equal(counter, 4) + } + ) + + t.test('Should use the custom serializer compiler for the route', + async t => { + const fastify = Fastify() + let called = 0 + const custom = ({ schema, httpStatus, url, method }) => { + t.equal(schema, schemaObj) + t.equal(url, '/') + t.equal(method, 'GET') + t.equal(httpStatus, '201') + + return input => { + called++ + t.same(input, { hello: 'world' }) + return JSON.stringify(input) + } + } + + t.plan(10) + const schemaObj = getDefaultSchema() + + fastify.get('/', { serializerCompiler: custom }, (req, reply) => { + const input = { hello: 'world' } + const first = reply.compileSerializationSchema(schemaObj, '201') + const second = reply.compileSerializationSchema(schemaObj, '201') + + t.equal(first, second) + t.ok(first(input), JSON.stringify(input)) + t.ok(second(input), JSON.stringify(input)) + t.equal(called, 2) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) + + t.test('Should build a WeakMap for cache when called', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + const input = { hello: 'world' } + + t.equal(reply.context[kReplySerializeWeakMap], null) + t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) + t.type(reply.context[kReplySerializeWeakMap], WeakMap) + t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) +}) + +test('Reply#getSerializationFunction', t => { + t.plan(3) + + t.test('Should retrieve the serialization function from the Schema definition', + async t => { + const fastify = Fastify() + const okInput201 = { + status: 'ok', + message: 'done!' + } + const notOkInput201 = { + message: 'created' + } + const okInput4xx = { + status: 'error', + code: 2, + message: 'oops!' + } + const notOkInput4xx = { + status: 'error', + code: 'something' + } + let cached4xx + let cached201 + + t.plan(9) + + const responseSchema = getResponseSchema() + + fastify.get( + '/:id', + { + params: { + id: { + type: 'integer' + } + }, + schema: { + response: responseSchema + } + }, + (req, reply) => { + const { id } = req.params + + if (parseInt(id) === 1) { + const serialize4xx = reply.getSerializationFunction('4xx') + const serialize201 = reply.getSerializationFunction(201) + const serializeUndefined = reply.getSerializationFunction(undefined) + + cached4xx = serialize4xx + cached201 = serialize201 + + t.type(serialize4xx, Function) + t.type(serialize201, Function) + t.equal(serialize4xx(okInput4xx), JSON.stringify(okInput4xx)) + t.equal(serialize201(okInput201), JSON.stringify(okInput201)) + t.notOk(serializeUndefined) + + try { + serialize4xx(notOkInput4xx) + } catch (err) { + t.equal( + err.message, + 'The value "something" cannot be converted to an integer.' + ) + } + + try { + serialize201(notOkInput201) + } catch (err) { + t.equal(err.message, '"status" is required!') + } + + reply.status(201).send(okInput201) + } else { + const serialize201 = reply.getSerializationFunction(201) + const serialize4xx = reply.getSerializationFunction('4xx') + + t.equal(serialize4xx, cached4xx) + t.equal(serialize201, cached201) + reply.status(401).send(okInput4xx) + } + } + ) + + await Promise.all([ + fastify.inject('/1'), + fastify.inject('/2') + ]) + } + ) + + t.test('Should retrieve the serialization function from the cached one', + async t => { + const fastify = Fastify() + + const schemaObj = getDefaultSchema() + + const okInput = { + hello: 'world', + world: 'done!' + } + const notOkInput = { + world: 'done!' + } + let cached + + t.plan(6) + + fastify.get( + '/:id', + { + params: { + id: { + type: 'integer' + } + } + }, + (req, reply) => { + const { id } = req.params + + if (parseInt(id) === 1) { + const serialize = reply.compileSerializationSchema(schemaObj) + + t.type(serialize, Function) + t.equal(serialize(okInput), JSON.stringify(okInput)) + + try { + serialize(notOkInput) + } catch (err) { + t.equal(err.message, '"hello" is required!') + } + + cached = serialize + } else { + const serialize = reply.getSerializationFunction(schemaObj) + + t.equal(serialize, cached) + t.equal(serialize(okInput), JSON.stringify(okInput)) + + try { + serialize(notOkInput) + } catch (err) { + t.equal(err.message, '"hello" is required!') + } + } + + reply.status(201).send(okInput) + } + ) + + await Promise.all([ + fastify.inject('/1'), + fastify.inject('/2') + ]) + } + ) + + t.test('Should not instantiate a WeakMap if it is not needed', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + t.notOk(reply.getSerializationFunction(getDefaultSchema())) + t.equal(reply.context[kReplySerializeWeakMap], null) + t.notOk(reply.getSerializationFunction('200')) + t.equal(reply.context[kReplySerializeWeakMap], null) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) +}) + +test('Reply#serializeInput', t => { + t.plan(5) + + t.test( + 'Should throw if missed serialization function from HTTP status', + async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.get('/', (req, reply) => { + reply.serializeInput({}, 201) + }) + + const result = await fastify.inject({ + path: '/', + method: 'GET' + }) + + t.equal(result.statusCode, 500) + t.same(result.json(), { + statusCode: 500, + code: 'FST_ERR_MISSING_SERIALIZATION_FN', + error: 'Internal Server Error', + message: 'Missing serialization function. Key "201"' + }) + } + ) + + t.test('Should use a serializer fn from HTTP status', async t => { + const fastify = Fastify() + const okInput201 = { + status: 'ok', + message: 'done!' + } + const notOkInput201 = { + message: 'created' + } + const okInput4xx = { + status: 'error', + code: 2, + message: 'oops!' + } + const notOkInput4xx = { + status: 'error', + code: 'something' + } + + t.plan(4) + + fastify.get( + '/', + { + params: { + id: { + type: 'integer' + } + }, + schema: { + response: getResponseSchema() + } + }, + (req, reply) => { + t.equal( + reply.serializeInput(okInput4xx, '4xx'), + JSON.stringify(okInput4xx) + ) + t.equal( + reply.serializeInput(okInput201, 201), + JSON.stringify(okInput201) + ) + + try { + reply.serializeInput(notOkInput4xx, '4xx') + } catch (err) { + t.equal( + err.message, + 'The value "something" cannot be converted to an integer.' + ) + } + + try { + reply.serializeInput(notOkInput201, 201) + } catch (err) { + t.equal(err.message, '"status" is required!') + } + + reply.status(204).send('') + } + ) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + t.test( + 'Should compile a serializer out of a schema if serializer fn missed', + async t => { + let compilerCalled = 0 + let serializerCalled = 0 + const testInput = { hello: 'world' } + const schemaObj = getDefaultSchema() + const fastify = Fastify() + const serializerCompiler = ({ schema, httpStatus, method, url }) => { + t.equal(schema, schemaObj) + t.notOk(httpStatus) + t.equal(method, 'GET') + t.equal(url, '/') + + compilerCalled++ + return input => { + t.equal(input, testInput) + serializerCalled++ + return JSON.stringify(input) + } + } + + t.plan(10) + + fastify.get('/', { serializerCompiler }, (req, reply) => { + t.equal( + reply.serializeInput(testInput, schemaObj), + JSON.stringify(testInput) + ) + + t.equal( + reply.serializeInput(testInput, schemaObj), + JSON.stringify(testInput) + ) + + reply.status(201).send(testInput) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + + t.equal(compilerCalled, 1) + t.equal(serializerCalled, 2) + } + ) + + t.test('Should use a cached serializer fn', async t => { + let compilerCalled = 0 + let serializerCalled = 0 + let cached + const testInput = { hello: 'world' } + const schemaObj = getDefaultSchema() + const fastify = Fastify() + const serializer = input => { + t.equal(input, testInput) + serializerCalled++ + return JSON.stringify(input) + } + const serializerCompiler = ({ schema, httpStatus, method, url }) => { + t.equal(schema, schemaObj) + t.notOk(httpStatus) + t.equal(method, 'GET') + t.equal(url, '/') + + compilerCalled++ + return serializer + } + + t.plan(12) + + fastify.get('/', { serializerCompiler }, (req, reply) => { + t.equal( + reply.serializeInput(testInput, schemaObj), + JSON.stringify(testInput) + ) + + cached = reply.getSerializationFunction(schemaObj) + + t.equal( + reply.serializeInput(testInput, schemaObj), + cached(testInput) + ) + + reply.status(201).send(testInput) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + + t.equal(cached, serializer) + t.equal(compilerCalled, 1) + t.equal(serializerCalled, 3) + }) + + t.test('Should instantiate a WeakMap after first call', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + const input = { hello: 'world' } + t.equal(reply.context[kReplySerializeWeakMap], null) + t.equal(reply.serializeInput(input, getDefaultSchema()), JSON.stringify(input)) + t.type(reply.context[kReplySerializeWeakMap], WeakMap) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) +}) diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js new file mode 100644 index 00000000000..f0a3cf0c731 --- /dev/null +++ b/test/internals/request-validate.test.js @@ -0,0 +1,1269 @@ +'use strict' + +const { test } = require('tap') +const Ajv = require('ajv') +const { kRequestValidateWeakMap } = require('../../lib/symbols') +const Fastify = require('../../fastify') + +const defaultSchema = { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' }, + world: { type: 'string' } + } +} + +const requestSchema = { + params: { + id: { + type: 'integer', + minimum: 1 + } + }, + querystring: { + foo: { + type: 'string', + enum: ['bar'] + } + }, + body: defaultSchema, + headers: { + 'x-foo': { + type: 'string' + } + } +} + +test('#compileValidationSchema', subtest => { + subtest.plan(5) + + subtest.test('Should return a function - Route without schema', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.type(validate, Function) + t.ok(validate({ hello: 'world' })) + t.notOk(validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + subtest.test( + 'Should reuse the validate fn across multiple invocations - Route without schema', + async t => { + const fastify = Fastify() + let validate = null + let counter = 0 + + t.plan(16) + + fastify.get('/', (req, reply) => { + counter++ + if (counter > 1) { + const newValidate = req.compileValidationSchema(defaultSchema) + t.equal(validate, newValidate, 'Are the same validate function') + validate = newValidate + } else { + validate = req.compileValidationSchema(defaultSchema) + } + + t.type(validate, Function) + t.ok(validate({ hello: 'world' })) + t.notOk(validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + }) + + await Promise.all([ + fastify.inject({ + path: '/', + method: 'GET' + }), + fastify.inject({ + path: '/', + method: 'GET' + }), + fastify.inject({ + path: '/', + method: 'GET' + }), + fastify.inject({ + path: '/', + method: 'GET' + }) + ]) + + t.equal(counter, 4) + } + ) + + subtest.test('Should return a function - Route with schema', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.post( + '/', + { + schema: { + body: defaultSchema + } + }, + (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.type(validate, Function) + t.ok(validate({ hello: 'world' })) + t.notOk(validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + } + ) + + await fastify.inject({ + path: '/', + method: 'POST', + payload: { + hello: 'world', + world: 'foo' + } + }) + }) + + subtest.test( + 'Should use the custom validator compiler for the route', + async t => { + const fastify = Fastify() + let called = 0 + const custom = ({ schema, httpPart, url, method }) => { + t.equal(schema, defaultSchema) + t.equal(url, '/') + t.equal(method, 'GET') + t.equal(httpPart, 'querystring') + + return input => { + called++ + t.same(input, { hello: 'world' }) + return true + } + } + + t.plan(10) + + fastify.get('/', { validatorCompiler: custom }, (req, reply) => { + const first = req.compileValidationSchema(defaultSchema, 'querystring') + const second = req.compileValidationSchema(defaultSchema, 'querystring') + + t.equal(first, second) + t.ok(first({ hello: 'world' })) + t.ok(second({ hello: 'world' })) + t.equal(called, 2) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) + + subtest.test( + 'Should instantiate a WeakMap when executed for first time', + async t => { + const fastify = Fastify() + + t.plan(5) + + fastify.get('/', (req, reply) => { + t.equal(req.context[kRequestValidateWeakMap], null) + t.type(req.compileValidationSchema(defaultSchema), Function) + t.type(req.context[kRequestValidateWeakMap], WeakMap) + t.type(req.compileValidationSchema(Object.assign({}, defaultSchema)), Function) + t.type(req.context[kRequestValidateWeakMap], WeakMap) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) +}) + +test('#getValidationFunction', subtest => { + subtest.plan(4) + + subtest.test('Should return a validation function', async t => { + const fastify = Fastify() + + t.plan(1) + + fastify.get('/', (req, reply) => { + const original = req.compileValidationSchema(defaultSchema) + const referenced = req.getValidationFunction(defaultSchema) + + t.equal(original, referenced) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + subtest.test('Should return undefined if no schema compiled', async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.get('/', (req, reply) => { + const validate = req.getValidationFunction(defaultSchema) + t.notOk(validate) + + const validateFn = req.getValidationFunction(42) + t.notOk(validateFn) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject('/') + }) + + subtest.test( + 'Should return the validation function from each HTTP part', + async t => { + const fastify = Fastify() + let headerValidation = null + let customValidation = null + + t.plan(15) + + fastify.post( + '/:id', + { + schema: requestSchema + }, + (req, reply) => { + const { params } = req + + switch (params.id) { + case 1: + customValidation = req.compileValidationSchema(defaultSchema) + t.ok(req.getValidationFunction('body')) + t.ok(req.getValidationFunction('body')({ hello: 'world' })) + t.notOk(req.getValidationFunction('body')({ world: 'hello' })) + break + case 2: + headerValidation = req.getValidationFunction('headers') + t.ok(headerValidation) + t.ok(headerValidation({ 'x-foo': 'world' })) + t.notOk(headerValidation({ 'x-foo': [] })) + break + case 3: + t.ok(req.getValidationFunction('params')) + t.ok(req.getValidationFunction('params')({ id: 123 })) + t.notOk(req.getValidationFunction('params'({ id: 1.2 }))) + break + case 4: + t.ok(req.getValidationFunction('querystring')) + t.ok(req.getValidationFunction('querystring')({ foo: 'bar' })) + t.notOk( + req.getValidationFunction('querystring')({ foo: 'not-bar' }) + ) + break + case 5: + t.equal( + customValidation, + req.getValidationFunction(defaultSchema) + ) + t.ok(customValidation({ hello: 'world' })) + t.notOk(customValidation({})) + t.equal(headerValidation, req.getValidationFunction('headers')) + break + default: + t.fail('Invalid id') + } + + reply.send({ hello: 'world' }) + } + ) + + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + fastify.inject({ + path: `/${i}`, + method: 'post', + query: { foo: 'bar' }, + payload: { + hello: 'world' + }, + headers: { + 'x-foo': 'x-bar' + } + }) + ) + } + + await Promise.all(promises) + } + ) + + subtest.test('Should not set a WeakMap if there is no schema', async t => { + const fastify = Fastify() + + t.plan(1) + + fastify.get('/', (req, reply) => { + req.getValidationFunction(defaultSchema) + req.getValidationFunction('body') + + t.equal(req.context[kRequestValidateWeakMap], null) + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) +}) + +test('#validate', subtest => { + subtest.plan(7) + + subtest.test( + 'Should return true/false if input valid - Route without schema', + async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.get('/', (req, reply) => { + const isNotValid = req.validate({ world: 'string' }, defaultSchema) + const isValid = req.validate({ hello: 'string' }, defaultSchema) + + t.notOk(isNotValid) + t.ok(isValid) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) + + subtest.test( + 'Should use the custom validator compiler for the route', + async t => { + const fastify = Fastify() + let called = 0 + const custom = ({ schema, httpPart, url, method }) => { + t.equal(schema, defaultSchema) + t.equal(url, '/') + t.equal(method, 'GET') + t.equal(httpPart, 'querystring') + + return input => { + called++ + t.same(input, { hello: 'world' }) + return true + } + } + + t.plan(9) + + fastify.get('/', { validatorCompiler: custom }, (req, reply) => { + const ok = req.validate( + { hello: 'world' }, + defaultSchema, + 'querystring' + ) + const ok2 = req.validate({ hello: 'world' }, defaultSchema) + + t.ok(ok) + t.ok(ok2) + t.equal(called, 2) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) + + subtest.test( + 'Should return true/false if input valid - With Schema for Route defined', + async t => { + const fastify = Fastify() + + t.plan(8) + + fastify.post( + '/:id', + { + schema: requestSchema + }, + (req, reply) => { + const { params } = req + + switch (params.id) { + case 1: + t.ok(req.validate({ hello: 'world' }, 'body')) + t.notOk(req.validate({ hello: [], world: 'foo' }, 'body')) + break + case 2: + t.notOk(req.validate({ foo: 'something' }, 'querystring')) + t.ok(req.validate({ foo: 'bar' }, 'querystring')) + break + case 3: + t.notOk(req.validate({ 'x-foo': [] }, 'headers')) + t.ok(req.validate({ 'x-foo': 'something' }, 'headers')) + break + case 4: + t.ok(req.validate({ id: params.id }, 'params')) + t.notOk(req.validate({ id: 0 }, 'params')) + break + default: + t.fail('Invalid id') + } + + reply.send({ hello: 'world' }) + } + ) + + const promises = [] + + for (let i = 1; i < 5; i++) { + promises.push( + fastify.inject({ + path: `/${i}`, + method: 'post', + query: { foo: 'bar' }, + payload: { + hello: 'world' + }, + headers: { + 'x-foo': 'x-bar' + } + }) + ) + } + + await Promise.all(promises) + } + ) + + subtest.test( + 'Should throw if missing validation fn for HTTP part and not schema provided', + async t => { + const fastify = Fastify() + + t.plan(10) + + fastify.get('/:id', (req, reply) => { + const { params } = req + + switch (parseInt(params.id)) { + case 1: + req.validate({}, 'body') + break + case 2: + req.validate({}, 'querystring') + break + case 3: + req.validate({}, 'query') + break + case 4: + req.validate({ 'x-foo': [] }, 'headers') + break + case 5: + req.validate({ id: 0 }, 'params') + break + default: + t.fail('Invalid id') + } + }) + + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + (async j => { + const response = await fastify.inject(`/${j}`) + + const result = response.json() + t.equal(result.statusCode, 500) + t.equal(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + })(i) + ) + } + + await Promise.all(promises) + } + ) + + subtest.test( + 'Should throw if missing validation fn for HTTP part and not valid schema provided', + async t => { + const fastify = Fastify() + + t.plan(10) + + fastify.get('/:id', (req, reply) => { + const { params } = req + + switch (parseInt(params.id)) { + case 1: + req.validate({}, 1, 'body') + break + case 2: + req.validate({}, [], 'querystring') + break + case 3: + req.validate({}, '', 'query') + break + case 4: + req.validate({ 'x-foo': [] }, null, 'headers') + break + case 5: + req.validate({ id: 0 }, () => {}, 'params') + break + default: + t.fail('Invalid id') + } + }) + + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + (async j => { + const response = await fastify.inject({ + path: `/${j}`, + method: 'GET' + }) + + const result = response.json() + t.equal(result.statusCode, 500) + t.equal(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + })(i) + ) + } + + await Promise.all(promises) + } + ) + + subtest.test('Should throw if invalid schema passed', async t => { + const fastify = Fastify() + + t.plan(10) + + fastify.get('/:id', (req, reply) => { + const { params } = req + + switch (parseInt(params.id)) { + case 1: + req.validate({}, 1) + break + case 2: + req.validate({}, '') + break + case 3: + req.validate({}, []) + break + case 4: + req.validate({ 'x-foo': [] }, null) + break + case 5: + req.validate({ id: 0 }, () => {}) + break + default: + t.fail('Invalid id') + } + }) + + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + (async j => { + const response = await fastify.inject({ + path: `/${j}`, + method: 'GET' + }) + + const result = response.json() + t.equal(result.statusCode, 500) + t.equal(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + })(i) + ) + } + + await Promise.all(promises) + }) + + subtest.test( + 'Should set a WeakMap if compiling the very first schema', + async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + t.equal(req.context[kRequestValidateWeakMap], null) + t.equal(req.validate({ hello: 'world' }, defaultSchema), true) + t.type(req.context[kRequestValidateWeakMap], WeakMap) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) +}) + +test('Nested Context', subtest => { + subtest.plan(1) + + subtest.test('Level_1', tst => { + tst.plan(3) + tst.test('#compileValidationSchema', ntst => { + ntst.plan(4) + + ntst.test('Should return a function - Route without schema', async t => { + const fastify = Fastify() + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.type(validate, Function) + t.ok(validate({ hello: 'world' })) + t.notOk(validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + t.plan(3) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + ntst.test( + 'Should reuse the validate fn across multiple invocations - Route without schema', + async t => { + const fastify = Fastify() + let validate = null + let counter = 0 + + t.plan(16) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + counter++ + if (counter > 1) { + const newValidate = req.compileValidationSchema(defaultSchema) + t.equal(validate, newValidate, 'Are the same validate function') + validate = newValidate + } else { + validate = req.compileValidationSchema(defaultSchema) + } + + t.type(validate, Function) + t.ok(validate({ hello: 'world' })) + t.notOk(validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await Promise.all([ + fastify.inject('/'), + fastify.inject('/'), + fastify.inject('/'), + fastify.inject('/') + ]) + + t.equal(counter, 4) + } + ) + + ntst.test('Should return a function - Route with schema', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.register((instance, opts, next) => { + instance.post( + '/', + { + schema: { + body: defaultSchema + } + }, + (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.type(validate, Function) + t.ok(validate({ hello: 'world' })) + t.notOk(validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + } + ) + + next() + }) + + await fastify.inject({ + path: '/', + method: 'POST', + payload: { + hello: 'world', + world: 'foo' + } + }) + }) + + ntst.test( + 'Should use the custom validator compiler for the route', + async t => { + const fastify = Fastify() + let called = 0 + + t.plan(10) + + fastify.register((instance, opts, next) => { + const custom = ({ schema, httpPart, url, method }) => { + t.equal(schema, defaultSchema) + t.equal(url, '/') + t.equal(method, 'GET') + t.equal(httpPart, 'querystring') + + return input => { + called++ + t.same(input, { hello: 'world' }) + return true + } + } + + fastify.get('/', { validatorCompiler: custom }, (req, reply) => { + const first = req.compileValidationSchema( + defaultSchema, + 'querystring' + ) + const second = req.compileValidationSchema( + defaultSchema, + 'querystring' + ) + + t.equal(first, second) + t.ok(first({ hello: 'world' })) + t.ok(second({ hello: 'world' })) + t.equal(called, 2) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + } + ) + }) + + tst.test('#getValidationFunction', ntst => { + ntst.plan(6) + + ntst.test('Should return a validation function', async t => { + const fastify = Fastify() + + t.plan(1) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const original = req.compileValidationSchema(defaultSchema) + const referenced = req.getValidationFunction(defaultSchema) + + t.equal(original, referenced) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + }) + + ntst.test('Should return undefined if no schema compiled', async t => { + const fastify = Fastify() + + t.plan(1) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const validate = req.getValidationFunction(defaultSchema) + + t.notOk(validate) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + }) + + ntst.test( + 'Should return the validation function from each HTTP part', + async t => { + const fastify = Fastify() + let headerValidation = null + let customValidation = null + + t.plan(15) + + fastify.register((instance, opts, next) => { + instance.post( + '/:id', + { + schema: requestSchema + }, + (req, reply) => { + const { params } = req + + switch (params.id) { + case 1: + customValidation = req.compileValidationSchema( + defaultSchema + ) + t.ok(req.getValidationFunction('body')) + t.ok(req.getValidationFunction('body')({ hello: 'world' })) + t.notOk( + req.getValidationFunction('body')({ world: 'hello' }) + ) + break + case 2: + headerValidation = req.getValidationFunction('headers') + t.ok(headerValidation) + t.ok(headerValidation({ 'x-foo': 'world' })) + t.notOk(headerValidation({ 'x-foo': [] })) + break + case 3: + t.ok(req.getValidationFunction('params')) + t.ok(req.getValidationFunction('params')({ id: 123 })) + t.notOk(req.getValidationFunction('params'({ id: 1.2 }))) + break + case 4: + t.ok(req.getValidationFunction('querystring')) + t.ok( + req.getValidationFunction('querystring')({ foo: 'bar' }) + ) + t.notOk( + req.getValidationFunction('querystring')({ + foo: 'not-bar' + }) + ) + break + case 5: + t.equal( + customValidation, + req.getValidationFunction(defaultSchema) + ) + t.ok(customValidation({ hello: 'world' })) + t.notOk(customValidation({})) + t.equal( + headerValidation, + req.getValidationFunction('headers') + ) + break + default: + t.fail('Invalid id') + } + + reply.send({ hello: 'world' }) + } + ) + + next() + }) + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + fastify.inject({ + path: `/${i}`, + method: 'post', + query: { foo: 'bar' }, + payload: { + hello: 'world' + }, + headers: { + 'x-foo': 'x-bar' + } + }) + ) + } + + await Promise.all(promises) + } + ) + + ntst.test('Should return a validation function - nested', async t => { + const fastify = Fastify() + let called = false + const custom = ({ schema, httpPart, url, method }) => { + t.equal(schema, defaultSchema) + t.equal(url, '/') + t.equal(method, 'GET') + t.notOk(httpPart) + + called = true + return () => true + } + + t.plan(6) + + fastify.setValidatorCompiler(custom) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const original = req.compileValidationSchema(defaultSchema) + const referenced = req.getValidationFunction(defaultSchema) + + t.equal(original, referenced) + t.equal(called, true) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + }) + + ntst.test( + 'Should return undefined if no schema compiled - nested', + async t => { + const fastify = Fastify() + let called = 0 + const custom = ({ schema, httpPart, url, method }) => { + called++ + return () => true + } + + t.plan(3) + + fastify.setValidatorCompiler(custom) + + fastify.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.equal(typeof validate, 'function') + + reply.send({ hello: 'world' }) + }) + + fastify.register( + (instance, opts, next) => { + instance.get('/', (req, reply) => { + const validate = req.getValidationFunction(defaultSchema) + + t.notOk(validate) + t.equal(called, 1) + + reply.send({ hello: 'world' }) + }) + + next() + }, + { prefix: '/nested' } + ) + + await fastify.inject('/') + await fastify.inject('/nested') + } + ) + + ntst.test('Should per-route defined validation compiler', async t => { + const fastify = Fastify() + let validateParent + let validateChild + let calledParent = 0 + let calledChild = 0 + const customParent = ({ schema, httpPart, url, method }) => { + calledParent++ + return () => true + } + + const customChild = ({ schema, httpPart, url, method }) => { + calledChild++ + return () => true + } + + t.plan(5) + + fastify.setValidatorCompiler(customParent) + + fastify.get('/', (req, reply) => { + validateParent = req.compileValidationSchema(defaultSchema) + + t.equal(typeof validateParent, 'function') + + reply.send({ hello: 'world' }) + }) + + fastify.register( + (instance, opts, next) => { + instance.get( + '/', + { + validatorCompiler: customChild + }, + (req, reply) => { + const validate1 = req.compileValidationSchema(defaultSchema) + validateChild = req.getValidationFunction(defaultSchema) + + t.equal(validate1, validateChild) + t.not(validateParent, validateChild) + t.equal(calledParent, 1) + t.equal(calledChild, 1) + + reply.send({ hello: 'world' }) + } + ) + + next() + }, + { prefix: '/nested' } + ) + + await fastify.inject('/') + await fastify.inject('/nested') + }) + }) + + tst.test('#validate', ntst => { + ntst.plan(3) + + ntst.test( + 'Should return true/false if input valid - Route without schema', + async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const isNotValid = req.validate( + { world: 'string' }, + defaultSchema + ) + const isValid = req.validate({ hello: 'string' }, defaultSchema) + + t.notOk(isNotValid) + t.ok(isValid) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + } + ) + + ntst.test( + 'Should use the custom validator compiler for the route', + async t => { + const fastify = Fastify() + let parentCalled = 0 + let childCalled = 0 + const customParent = () => { + parentCalled++ + + return () => true + } + + const customChild = ({ schema, httpPart, url, method }) => { + t.equal(schema, defaultSchema) + t.equal(url, '/') + t.equal(method, 'GET') + t.equal(httpPart, 'querystring') + + return input => { + childCalled++ + t.same(input, { hello: 'world' }) + return true + } + } + + t.plan(10) + + fastify.setValidatorCompiler(customParent) + + fastify.register((instance, opts, next) => { + instance.get( + '/', + { validatorCompiler: customChild }, + (req, reply) => { + const ok = req.validate( + { hello: 'world' }, + defaultSchema, + 'querystring' + ) + const ok2 = req.validate({ hello: 'world' }, defaultSchema) + + t.ok(ok) + t.ok(ok2) + t.equal(childCalled, 2) + t.equal(parentCalled, 0) + + reply.send({ hello: 'world' }) + } + ) + + next() + }) + + await fastify.inject('/') + } + ) + + ntst.test( + 'Should return true/false if input valid - With Schema for Route defined and scoped validator compiler', + async t => { + const validator = new Ajv() + const fastify = Fastify() + const childCounter = { + query: 0, + body: 0, + params: 0, + headers: 0 + } + let parentCalled = 0 + + const parent = () => { + parentCalled++ + return () => true + } + const child = ({ schema, httpPart, url, method }) => { + httpPart = httpPart === 'querystring' ? 'query' : httpPart + const validate = validator.compile(schema) + + return input => { + childCounter[httpPart]++ + return validate(input) + } + } + + t.plan(13) + + fastify.setValidatorCompiler(parent) + fastify.register((instance, opts, next) => { + instance.setValidatorCompiler(child) + instance.post( + '/:id', + { + schema: requestSchema + }, + (req, reply) => { + const { params } = req + + switch (parseInt(params.id)) { + case 1: + t.ok(req.validate({ hello: 'world' }, 'body')) + t.notOk(req.validate({ hello: [], world: 'foo' }, 'body')) + break + case 2: + t.notOk(req.validate({ foo: 'something' }, 'querystring')) + t.ok(req.validate({ foo: 'bar' }, 'querystring')) + break + case 3: + t.notOk(req.validate({ 'x-foo': [] }, 'headers')) + t.ok(req.validate({ 'x-foo': 'something' }, 'headers')) + break + case 4: + t.ok(req.validate({ id: 1 }, 'params')) + t.notOk(req.validate({ id: params.id }, 'params')) + break + default: + t.fail('Invalid id') + } + + reply.send({ hello: 'world' }) + } + ) + + next() + }) + + const promises = [] + + for (let i = 1; i < 5; i++) { + promises.push( + fastify.inject({ + path: `/${i}`, + method: 'post', + query: {}, + payload: { + hello: 'world' + } + }) + ) + } + + await Promise.all(promises) + + t.equal(childCounter.query, 6) // 4 calls made + 2 custom validations + t.equal(childCounter.headers, 6) // 4 calls made + 2 custom validations + t.equal(childCounter.body, 6) // 4 calls made + 2 custom validations + t.equal(childCounter.params, 6) // 4 calls made + 2 custom validations + t.equal(parentCalled, 0) + } + ) + }) + }) +}) diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 44f8e487667..412058f5a9f 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -19,6 +19,9 @@ test('Regular request', t => { req.connection = req.socket const request = new Request('id', 'params', req, 'query', 'log') t.type(request, Request) + t.type(request.validate, Function) + t.type(request.getValidationFunction, Function) + t.type(request.compileValidationSchema, Function) t.equal(request.id, 'id') t.equal(request.params, 'params') t.equal(request.raw, req) @@ -74,7 +77,7 @@ test('Regular request - host header has precedence over authority', t => { }) test('Request with trust proxy', t => { - t.plan(15) + t.plan(18) const headers = { 'x-forwarded-for': '2.2.2.2, 1.1.1.1', 'x-forwarded-host': 'example.com' @@ -103,6 +106,9 @@ test('Request with trust proxy', t => { t.equal(request.url, '/') t.equal(request.socket, req.socket) t.equal(request.protocol, 'http') + t.type(request.validate, Function) + t.type(request.getValidationFunction, Function) + t.type(request.compileValidationSchema, Function) }) test('Request with trust proxy, encrypted', t => { @@ -221,7 +227,7 @@ test('Request with trust proxy - plain', t => { }) test('Request with undefined socket', t => { - t.plan(15) + t.plan(18) const headers = { host: 'hostname' } @@ -247,6 +253,9 @@ test('Request with undefined socket', t => { t.equal(request.url, '/') t.equal(request.protocol, undefined) t.same(request.socket, req.socket) + t.type(request.validate, Function) + t.type(request.getValidationFunction, Function) + t.type(request.compileValidationSchema, Function) }) test('Request with trust proxy and undefined socket', t => { diff --git a/types/reply.d.ts b/types/reply.d.ts index 778032b6ca9..228ecebd830 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -54,5 +54,10 @@ export interface FastifyReply< type(contentType: string): FastifyReply; serializer(fn: (payload: any) => string): FastifyReply; serialize(payload: any): string | ArrayBuffer | Buffer; + getSerializationFunction(httpStatus: string): (payload: any) => string; + getSerializationFunction(schema: {[key: string]: any}): (payload: any) => string; + compileSerializationSchema(schema: {[key: string]: any}, httpStatus?: string): (payload: any) => string; + serializeInput(input: any, schema: {[key: string]: any}, httpStatus?: string): string + serializeInput(input: any, httpStatus: string): string then(fulfilled: () => void, rejected: (err: Error) => void): void; } diff --git a/types/request.d.ts b/types/request.d.ts index ba89f4b956d..80ac27de82f 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -6,6 +6,7 @@ import { FastifyTypeProvider, FastifyTypeProviderDefault, FastifyRequestType, Re import { FastifySchema } from './schema' import { FastifyContext } from './context' +type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' export interface RequestGenericInterface { Body?: RequestBodyDefault; Querystring?: RequestQuerystringDefault; @@ -54,6 +55,12 @@ export interface FastifyRequest boolean + getValidationFunction(schema: {[key: string]: any}): (input: any) => boolean + compileValidationSchema(schema: {[key: string]: any}, httpPart?: HTTPRequestPart): (input: any) => boolean + validate(input: any, schema: {[key: string]: any}, httpPart?: HTTPRequestPart): boolean + validate(input: any, httpPart?: HTTPRequestPart): boolean + // Prefer `socket` over deprecated `connection` property in node 13.0.0 or higher // @deprecated readonly connection: RawRequest['socket']; From 435c43726c3972dab290ef05fa43522e10e8e1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=2E=20Rom=C3=A1n?= Date: Fri, 15 Jul 2022 09:19:31 +0200 Subject: [PATCH 0010/1295] types: re-export `FastifyListenOptions` in top-level types (#4135) --- fastify.d.ts | 2 +- test/types/import.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.d.ts b/fastify.d.ts index 6c343fa4caa..66312159d75 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -194,7 +194,7 @@ export type { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRe export { FastifyRequest, RequestGenericInterface } from './types/request' export { FastifyReply } from './types/reply' export { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' -export { FastifyInstance, PrintRoutesOptions } from './types/instance' +export { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' export { FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel } from './types/logger' export { FastifyContext, FastifyContextConfig } from './types/context' export { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route' diff --git a/test/types/import.ts b/test/types/import.ts index 5f48f5d087d..7ce901fb712 100644 --- a/test/types/import.ts +++ b/test/types/import.ts @@ -1 +1 @@ -import { FastifyLogFn } from '../../fastify' +import { FastifyListenOptions, FastifyLogFn } from '../../fastify' From 81a937a90c13f17e4e23f1ae25d9e525fe687bf2 Mon Sep 17 00:00:00 2001 From: Sebastian Zimmer Date: Mon, 18 Jul 2022 12:30:28 +0200 Subject: [PATCH 0011/1295] docs: remove http2 experimental status (#4142) (#4144) --- docs/Reference/HTTP2.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/Reference/HTTP2.md b/docs/Reference/HTTP2.md index e868502763e..c67ec1b63b9 100644 --- a/docs/Reference/HTTP2.md +++ b/docs/Reference/HTTP2.md @@ -2,9 +2,7 @@ ## HTTP2 -_Fastify_ offers **experimental support** for HTTP2 starting from Node 8 LTS, -which includes HTTP2 without a flag; HTTP2 is supported over either HTTPS or -plaintext. +_Fastify_ supports HTTP2 over either HTTPS (h2) or plaintext (h2c). Currently, none of the HTTP2-specific APIs are available through _Fastify_, but Node's `req` and `res` can be accessed through our `Request` and `Reply` From 0ca63a0819a154023c4de14e2723578a2b3f0961 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Tue, 19 Jul 2022 11:58:25 +0200 Subject: [PATCH 0012/1295] refactor: rename `request.validate` to `request.validateInput` (#4139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(reply): add documentation for new validation apis * docs(reply): adjust serialization documentation * docs: address comments * refactor(request): rename from validate to validateInput * docs: adjust new renaming * refactor: change method name * Update docs/Reference/Request.md Co-authored-by: 小菜 Co-authored-by: Frazer Smith Co-authored-by: 小菜 --- docs/Reference/Reply.md | 4 +- docs/Reference/Request.md | 171 ++++++++++++++++++++++++ lib/request.js | 4 +- test/internals/request-validate.test.js | 80 +++++------ test/internals/request.test.js | 6 +- 5 files changed, 218 insertions(+), 47 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 0735f4746d6..ab67409e421 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -369,7 +369,7 @@ const serialize = reply serialize({ foo: 'bar' }) // '{"foo":"bar"}' ``` -See [.compileSerializationSchema(schema, httpStatus)](#compileserializationschema) +See [.compileSerializationSchema(schema, [httpStatus])](#compileserializationschema) for more information on how to compile serialization schemas. ### .compileSerializationSchema(schema, httpStatus) @@ -499,7 +499,7 @@ reply .serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}' ``` -See [.compileSerializationSchema(schema, httpStatus)](#compileserializationschema) +See [.compileSerializationSchema(schema, [httpStatus])](#compileserializationschema) for more information on how to compile serialization schemas. ### .serializer(func) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 9784ed1f518..cf4771626cf 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -35,6 +35,19 @@ Request is a core Fastify object containing the following fields: - `connection` - Deprecated, use `socket` instead. The underlying connection of the incoming request. - `socket` - the underlying connection of the incoming request +- [.getValidationFunction(schema | httpPart)](#getvalidationfunction) - + Returns a validation function for the specified schema or http part, + if any of either are set or cached. +- [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema) - + Compiles the specified schema and returns a validation function + using the default (or customized) `ValidationCompiler`. + The optional `httpPart` is forwarded to the `ValidationCompiler` + if provided, defaults to `null`. +- [.validateInput(data, schema | httpPart, [httpPart])](#validate) - + Validates the specified input by using the specified + schema and returns the serialized payload. If the optional + `httpPart` is provided, the function will use the serializer + function given for that HTTP Status Code. Defaults to `null`. - `context` - A Fastify internal object. You should not use it directly or modify it. It is useful to access one special key: - `context.config` - The route [`config`](./Routes.md#routes-config) object. @@ -77,3 +90,161 @@ fastify.post('/:params', options, function (request, reply) { request.log.info('some info') }) ``` +### .getValidationFunction(schema | httpPart) + + +By calling this function using a provided `schema` or `httpPart`, +it will return a `validation` function that can be used to +validate diverse inputs. It returns `undefined` if no +serialization function was found using either of the provided inputs. + +```js +const validate = request + .getValidationFunction({ + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }) +validate({ foo: 'bar' }) // true + +// or + +const validate = request + .getValidationFunction('body') +validate({ foo: 0.5 }) // false +``` + +See [.compilaValidationSchema(schema, [httpStatus])](#compilevalidationschema) +for more information on how to compile validation function. + +### .compileValidationSchema(schema, [httpPart]) + + +This function will compile a validation schema and +return a function that can be used to validate data. +The function returned (a.k.a. _validation function_) is compiled +by using the provided [`SchemaControler#ValidationCompiler`](./Server.md#schema-controller). +A `WeakMap` is used to cached this, reducing compilation calls. + +The optional parameter `httpPart`, if provided, is forwarded directly +the `ValidationCompiler`, so it can be used to compile the validation +function if a custom `ValidationCompiler` is provided for the route. + + +```js +const validate = request + .compileValidationSchema({ + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }) +console.log(validate({ foo: 'bar' })) // true + +// or + +const validate = request + .compileValidationSchema({ + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }, 200) +console.log(validate({ hello: 'world' })) // false +``` + +Note that you should be careful when using this function, as it will cache +the compiled validation functions based on the schema provided. If the +schemas provided are mutated or changed, the validation functions will not +detect that the schema has been altered and for instance it will reuse the +previously compiled validation function, as the cache is based on +the reference of the schema (Object) previously provided. + +If there is a need to change the properties of a schema, always opt to create +a totally new schema (object), otherwise the implementation will not benefit from +the cache mechanism. + +Using the following schema as an example: +```js +const schema1 = { + type: 'object', + properties: { + foo: { + type: 'string' + } + } +} +``` + +*Not* +```js +const validate = request.compileValidationSchema(schema1) + +// Later on... +schema1.properties.foo.type. = 'integer' +const newValidate = request.compileValidationSchema(schema1) + +console.log(newValidate === validate) // true +``` + +*Instead* +```js +const validate = request.compileValidationSchema(schema1) + +// Later on... +const newSchema = Object.assign({}, schema1) +newSchema.properties.foo.type = 'integer' + +const newValidate = request.compileValidationSchema(newSchema) + +console.log(newValidate === validate) // false +``` + +### .validateInput(data, [schema | httpStatus], [httpStatus]) + + +This function will validate the input based on the provided schema, +or HTTP part passed. If both are provided, the `httpPart` parameter +will take precedence. + +If there is not a validation function for a given `schema`, a new validation +function will be compiled, forwarding the `httpPart` if provided. + +```js +request + .validateInput({ foo: 'bar'}, { + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }) // true + +// or + +request + .validateInput({ foo: 'bar'}, { + type: 'object', + properties: { + foo: { + type: 'string' + } + } + }, 'body') // true + +// or + +request + .validateInput({ hello: 'world'}, 'query') // false +``` + +See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema) +for more information on how to compile validation schemas. \ No newline at end of file diff --git a/lib/request.js b/lib/request.js index 088a0825c28..b86990c4820 100644 --- a/lib/request.js +++ b/lib/request.js @@ -222,7 +222,7 @@ Object.defineProperties(Request.prototype, { } }, compileValidationSchema: { - value: function (schema, httpPart) { + value: function (schema, httpPart = null) { const { method, url } = this if (this.context[kRequestValidateWeakMap]?.has(schema)) { @@ -259,7 +259,7 @@ Object.defineProperties(Request.prototype, { return validateFn } }, - validate: { + validateInput: { value: function (input, schema, httpPart) { httpPart = typeof schema === 'string' ? schema : httpPart diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js index f0a3cf0c731..89132e5939a 100644 --- a/test/internals/request-validate.test.js +++ b/test/internals/request-validate.test.js @@ -358,8 +358,8 @@ test('#validate', subtest => { t.plan(2) fastify.get('/', (req, reply) => { - const isNotValid = req.validate({ world: 'string' }, defaultSchema) - const isValid = req.validate({ hello: 'string' }, defaultSchema) + const isNotValid = req.validateInput({ world: 'string' }, defaultSchema) + const isValid = req.validateInput({ hello: 'string' }, defaultSchema) t.notOk(isNotValid) t.ok(isValid) @@ -395,12 +395,12 @@ test('#validate', subtest => { t.plan(9) fastify.get('/', { validatorCompiler: custom }, (req, reply) => { - const ok = req.validate( + const ok = req.validateInput( { hello: 'world' }, defaultSchema, 'querystring' ) - const ok2 = req.validate({ hello: 'world' }, defaultSchema) + const ok2 = req.validateInput({ hello: 'world' }, defaultSchema) t.ok(ok) t.ok(ok2) @@ -433,20 +433,20 @@ test('#validate', subtest => { switch (params.id) { case 1: - t.ok(req.validate({ hello: 'world' }, 'body')) - t.notOk(req.validate({ hello: [], world: 'foo' }, 'body')) + t.ok(req.validateInput({ hello: 'world' }, 'body')) + t.notOk(req.validateInput({ hello: [], world: 'foo' }, 'body')) break case 2: - t.notOk(req.validate({ foo: 'something' }, 'querystring')) - t.ok(req.validate({ foo: 'bar' }, 'querystring')) + t.notOk(req.validateInput({ foo: 'something' }, 'querystring')) + t.ok(req.validateInput({ foo: 'bar' }, 'querystring')) break case 3: - t.notOk(req.validate({ 'x-foo': [] }, 'headers')) - t.ok(req.validate({ 'x-foo': 'something' }, 'headers')) + t.notOk(req.validateInput({ 'x-foo': [] }, 'headers')) + t.ok(req.validateInput({ 'x-foo': 'something' }, 'headers')) break case 4: - t.ok(req.validate({ id: params.id }, 'params')) - t.notOk(req.validate({ id: 0 }, 'params')) + t.ok(req.validateInput({ id: params.id }, 'params')) + t.notOk(req.validateInput({ id: 0 }, 'params')) break default: t.fail('Invalid id') @@ -490,19 +490,19 @@ test('#validate', subtest => { switch (parseInt(params.id)) { case 1: - req.validate({}, 'body') + req.validateInput({}, 'body') break case 2: - req.validate({}, 'querystring') + req.validateInput({}, 'querystring') break case 3: - req.validate({}, 'query') + req.validateInput({}, 'query') break case 4: - req.validate({ 'x-foo': [] }, 'headers') + req.validateInput({ 'x-foo': [] }, 'headers') break case 5: - req.validate({ id: 0 }, 'params') + req.validateInput({ id: 0 }, 'params') break default: t.fail('Invalid id') @@ -539,19 +539,19 @@ test('#validate', subtest => { switch (parseInt(params.id)) { case 1: - req.validate({}, 1, 'body') + req.validateInput({}, 1, 'body') break case 2: - req.validate({}, [], 'querystring') + req.validateInput({}, [], 'querystring') break case 3: - req.validate({}, '', 'query') + req.validateInput({}, '', 'query') break case 4: - req.validate({ 'x-foo': [] }, null, 'headers') + req.validateInput({ 'x-foo': [] }, null, 'headers') break case 5: - req.validate({ id: 0 }, () => {}, 'params') + req.validateInput({ id: 0 }, () => {}, 'params') break default: t.fail('Invalid id') @@ -589,19 +589,19 @@ test('#validate', subtest => { switch (parseInt(params.id)) { case 1: - req.validate({}, 1) + req.validateInput({}, 1) break case 2: - req.validate({}, '') + req.validateInput({}, '') break case 3: - req.validate({}, []) + req.validateInput({}, []) break case 4: - req.validate({ 'x-foo': [] }, null) + req.validateInput({ 'x-foo': [] }, null) break case 5: - req.validate({ id: 0 }, () => {}) + req.validateInput({ id: 0 }, () => {}) break default: t.fail('Invalid id') @@ -637,7 +637,7 @@ test('#validate', subtest => { fastify.get('/', (req, reply) => { t.equal(req.context[kRequestValidateWeakMap], null) - t.equal(req.validate({ hello: 'world' }, defaultSchema), true) + t.equal(req.validateInput({ hello: 'world' }, defaultSchema), true) t.type(req.context[kRequestValidateWeakMap], WeakMap) reply.send({ hello: 'world' }) @@ -1096,11 +1096,11 @@ test('Nested Context', subtest => { fastify.register((instance, opts, next) => { instance.get('/', (req, reply) => { - const isNotValid = req.validate( + const isNotValid = req.validateInput( { world: 'string' }, defaultSchema ) - const isValid = req.validate({ hello: 'string' }, defaultSchema) + const isValid = req.validateInput({ hello: 'string' }, defaultSchema) t.notOk(isNotValid) t.ok(isValid) @@ -1149,12 +1149,12 @@ test('Nested Context', subtest => { '/', { validatorCompiler: customChild }, (req, reply) => { - const ok = req.validate( + const ok = req.validateInput( { hello: 'world' }, defaultSchema, 'querystring' ) - const ok2 = req.validate({ hello: 'world' }, defaultSchema) + const ok2 = req.validateInput({ hello: 'world' }, defaultSchema) t.ok(ok) t.ok(ok2) @@ -1214,20 +1214,20 @@ test('Nested Context', subtest => { switch (parseInt(params.id)) { case 1: - t.ok(req.validate({ hello: 'world' }, 'body')) - t.notOk(req.validate({ hello: [], world: 'foo' }, 'body')) + t.ok(req.validateInput({ hello: 'world' }, 'body')) + t.notOk(req.validateInput({ hello: [], world: 'foo' }, 'body')) break case 2: - t.notOk(req.validate({ foo: 'something' }, 'querystring')) - t.ok(req.validate({ foo: 'bar' }, 'querystring')) + t.notOk(req.validateInput({ foo: 'something' }, 'querystring')) + t.ok(req.validateInput({ foo: 'bar' }, 'querystring')) break case 3: - t.notOk(req.validate({ 'x-foo': [] }, 'headers')) - t.ok(req.validate({ 'x-foo': 'something' }, 'headers')) + t.notOk(req.validateInput({ 'x-foo': [] }, 'headers')) + t.ok(req.validateInput({ 'x-foo': 'something' }, 'headers')) break case 4: - t.ok(req.validate({ id: 1 }, 'params')) - t.notOk(req.validate({ id: params.id }, 'params')) + t.ok(req.validateInput({ id: 1 }, 'params')) + t.notOk(req.validateInput({ id: params.id }, 'params')) break default: t.fail('Invalid id') diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 412058f5a9f..a5b2d3d2ab9 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -19,7 +19,7 @@ test('Regular request', t => { req.connection = req.socket const request = new Request('id', 'params', req, 'query', 'log') t.type(request, Request) - t.type(request.validate, Function) + t.type(request.validateInput, Function) t.type(request.getValidationFunction, Function) t.type(request.compileValidationSchema, Function) t.equal(request.id, 'id') @@ -106,7 +106,7 @@ test('Request with trust proxy', t => { t.equal(request.url, '/') t.equal(request.socket, req.socket) t.equal(request.protocol, 'http') - t.type(request.validate, Function) + t.type(request.validateInput, Function) t.type(request.getValidationFunction, Function) t.type(request.compileValidationSchema, Function) }) @@ -253,7 +253,7 @@ test('Request with undefined socket', t => { t.equal(request.url, '/') t.equal(request.protocol, undefined) t.same(request.socket, req.socket) - t.type(request.validate, Function) + t.type(request.validateInput, Function) t.type(request.getValidationFunction, Function) t.type(request.compileValidationSchema, Function) }) From f5a392bfea204062202f3e29cc8531cab0234035 Mon Sep 17 00:00:00 2001 From: sinclairzx81 Date: Fri, 22 Jul 2022 01:34:58 +0900 Subject: [PATCH 0013/1295] Fix #4120: Defer resolution of FastifyRequestType until FastifyRequest (#4123) * Defer RequestType inference until FastifyRequest property navigation * Lint * Remove optional ReplyType generic argument from FastifyReply * Restore Optional Generic Argument for Request | Reply * Comment RequestType generic argument with reference to issue --- test/types/hooks.test-d.ts | 3 +- test/types/request.test-d.ts | 2 +- test/types/type-provider.test-d.ts | 82 +++++++++++++++++++++++++++--- types/hooks.d.ts | 58 +++++++-------------- types/instance.d.ts | 58 +++++++-------------- types/request.d.ts | 12 +++-- types/route.d.ts | 52 +++++++++---------- types/type-provider.d.ts | 13 ++--- 8 files changed, 155 insertions(+), 125 deletions(-) diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index d9d17bb506f..5470dd927fb 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -223,8 +223,7 @@ RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, -FastifyTypeProviderDefault, -ResolveFastifyRequestType +FastifyTypeProviderDefault > = async function (request, reply): Promise { expectType(this) expectAssignable(request) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index fbff88ee096..f2617969d77 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -85,7 +85,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.server) } -const getHandlerWithCustomLogger: RouteHandlerMethod, CustomLoggerInterface> = function (request, _reply) { +const getHandlerWithCustomLogger: RouteHandlerMethod = function (request, _reply) { expectType(request.log) } diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index 5e8a9d38c22..91e7b7fc3da 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -1,15 +1,14 @@ import fastify, { - ContextConfigDefault, FastifySchema, - FastifyTypeProvider, RawReplyDefaultExpression, - RawRequestDefaultExpression, - RawServerDefault, - RouteHandlerMethod + FastifyTypeProvider, + HookHandlerDoneFunction, + FastifyRequest, + FastifyReply, + FastifyInstance } from '../../fastify' import { expectAssignable, expectError, expectType } from 'tsd' import { IncomingHttpHeaders } from 'http' import { Type, TSchema, Static } from '@sinclair/typebox' import { FromSchema, JSONSchema } from 'json-schema-to-ts' -import { RouteGenericInterface } from '../../types/route' const server = fastify() @@ -437,3 +436,74 @@ expectAssignable(server.withTypeProvider().get<{Reply: b return true } )) + +// ------------------------------------------------------------------- +// FastifyPlugin: Auxiliary +// ------------------------------------------------------------------- + +interface AuxiliaryPluginProvider extends FastifyTypeProvider { output: 'plugin-auxiliary' } + +// Auxiliary plugins may have varying server types per application. Recommendation would be to explicitly remap instance provider context within plugin if required. +function plugin (instance: T) { + expectAssignable(instance.withTypeProvider().get( + '/', + { + schema: { body: null } + }, + (req) => { + expectType<'plugin-auxiliary'>(req.body) + } + )) +} + +expectAssignable(server.withTypeProvider().register(plugin).get( + '/', + { + schema: { body: null } + }, + (req) => { + expectType<'plugin-auxiliary'>(req.body) + } +)) + +// ------------------------------------------------------------------- +// Handlers: Inline +// ------------------------------------------------------------------- + +interface InlineHandlerProvider extends FastifyTypeProvider { output: 'handler-inline' } + +// Inline handlers should infer for the request parameters (non-shared) +expectAssignable(server.withTypeProvider().get( + '/', + { + onRequest: (req, res) => { + expectType<'handler-inline'>(req.body) + }, + schema: { body: null } + }, + (req) => { + expectType<'handler-inline'>(req.body) + } +)) + +// ------------------------------------------------------------------- +// Handlers: Auxiliary +// ------------------------------------------------------------------- + +interface AuxiliaryHandlerProvider extends FastifyTypeProvider { output: 'handler-auxiliary' } + +// Auxiliary handlers are likely shared for multiple routes and thus should infer as unknown due to potential varying parameters +function auxiliaryHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction): void { + expectType(request.body) +} + +expectAssignable(server.withTypeProvider().get( + '/', + { + onRequest: auxiliaryHandler, + schema: { body: null } + }, + (req) => { + expectType<'handler-auxiliary'>(req.body) + } +)) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 95025e20c67..54f8f634c33 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -7,10 +7,8 @@ import { FastifyReply } from './reply' import { FastifyError } from '@fastify/error' import { FastifyLoggerInstance } from './logger' import { - FastifyRequestType, FastifyTypeProvider, - FastifyTypeProviderDefault, - ResolveFastifyRequestType + FastifyTypeProviderDefault } from './type-provider' import { RegisterOptions } from './register' import { FastifySchema } from './schema' @@ -36,12 +34,11 @@ export interface onRequestHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction ): void; @@ -55,12 +52,11 @@ export interface onRequestAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, ): Promise; } @@ -77,12 +73,11 @@ export interface preParsingHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, payload: RequestPayload, done: (err?: TError | null, res?: RequestPayload) => void @@ -97,12 +92,11 @@ export interface preParsingAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, payload: RequestPayload, ): Promise; @@ -119,12 +113,11 @@ export interface preValidationHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction ): void; @@ -138,12 +131,11 @@ export interface preValidationAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, ): Promise; } @@ -159,12 +151,11 @@ export interface preHandlerHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction ): void; @@ -178,12 +169,11 @@ export interface preHandlerAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, ): Promise; } @@ -208,12 +198,11 @@ export interface preSerializationHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, payload: PreSerializationPayload, done: DoneFuncWithErrOrRes @@ -229,12 +218,11 @@ export interface preSerializationAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, payload: PreSerializationPayload ): Promise; @@ -253,12 +241,11 @@ export interface onSendHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, payload: OnSendPayload, done: DoneFuncWithErrOrRes @@ -274,12 +261,11 @@ export interface onSendAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, payload: OnSendPayload, ): Promise; @@ -297,12 +283,11 @@ export interface onResponseHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction ): void; @@ -316,12 +301,11 @@ export interface onResponseAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply ): Promise; } @@ -338,12 +322,11 @@ export interface onTimeoutHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction ): void; @@ -357,12 +340,11 @@ export interface onTimeoutAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply ): Promise; } @@ -382,12 +364,11 @@ export interface onErrorHookHandler< TError extends Error = FastifyError, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, error: TError, done: () => void @@ -403,12 +384,11 @@ export interface onErrorAsyncHookHandler< TError extends Error = FastifyError, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply, error: TError ): Promise; diff --git a/types/instance.d.ts b/types/instance.d.ts index 1d03d37e7ca..a3fdef2afdd 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -17,10 +17,8 @@ import { FastifySerializerCompiler } from './schema' import { - FastifyRequestType, FastifyTypeProvider, - FastifyTypeProviderDefault, - ResolveFastifyRequestType + FastifyTypeProviderDefault } from './type-provider' import { ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' @@ -200,22 +198,20 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onRequest', - hook: onRequestHookHandler + hook: onRequestHookHandler ): FastifyInstance; addHook< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onRequest', - hook: onRequestAsyncHookHandler + hook: onRequestAsyncHookHandler ): FastifyInstance; /** @@ -226,22 +222,20 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'preParsing', - hook: preParsingHookHandler + hook: preParsingHookHandler ): FastifyInstance; addHook< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'preParsing', - hook: preParsingAsyncHookHandler + hook: preParsingAsyncHookHandler ): FastifyInstance; /** @@ -251,22 +245,20 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'preValidation', - hook: preValidationHookHandler + hook: preValidationHookHandler ): FastifyInstance; addHook< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'preValidation', - hook: preValidationAsyncHookHandler + hook: preValidationAsyncHookHandler ): FastifyInstance; /** @@ -276,22 +268,20 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'preHandler', - hook: preHandlerHookHandler + hook: preHandlerHookHandler ): FastifyInstance; addHook< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'preHandler', - hook: preHandlerAsyncHookHandler + hook: preHandlerAsyncHookHandler ): FastifyInstance; /** @@ -303,11 +293,10 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'preSerialization', - hook: preSerializationHookHandler + hook: preSerializationHookHandler ): FastifyInstance; addHook< @@ -315,11 +304,10 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'preSerialization', - hook: preSerializationAsyncHookHandler + hook: preSerializationAsyncHookHandler ): FastifyInstance; /** @@ -331,11 +319,10 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onSend', - hook: onSendHookHandler + hook: onSendHookHandler ): FastifyInstance; addHook< @@ -343,11 +330,10 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onSend', - hook: onSendAsyncHookHandler + hook: onSendAsyncHookHandler ): FastifyInstance; /** @@ -358,22 +344,20 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onResponse', - hook: onResponseHookHandler + hook: onResponseHookHandler ): FastifyInstance; addHook< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onResponse', - hook: onResponseAsyncHookHandler + hook: onResponseAsyncHookHandler ): FastifyInstance; /** @@ -384,22 +368,20 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onTimeout', - hook: onTimeoutHookHandler + hook: onTimeoutHookHandler ): FastifyInstance; addHook< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onTimeout', - hook: onTimeoutAsyncHookHandler + hook: onTimeoutAsyncHookHandler ): FastifyInstance; /** @@ -412,22 +394,20 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onError', - hook: onErrorHookHandler + hook: onErrorHookHandler ): FastifyInstance; addHook< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance >( name: 'onError', - hook: onErrorAsyncHookHandler + hook: onErrorAsyncHookHandler ): FastifyInstance; // Application addHooks diff --git a/types/request.d.ts b/types/request.d.ts index 80ac27de82f..c493acaa105 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -24,11 +24,17 @@ export interface FastifyRequest, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyLoggerInstance = FastifyLoggerInstance, + RequestType extends FastifyRequestType = ResolveFastifyRequestType + // ^ Temporary Note: RequestType has been re-ordered to be the last argument in + // generic list. This generic argument is now considered optional as it can be + // automatically inferred from the SchemaCompiler, RouteGeneric and TypeProvider + // arguments. Implementations that already pass this argument can either omit + // the RequestType (preferred) or swap Logger and RequestType arguments when + // creating custom types of FastifyRequest. Related issue #4123 > { id: any; - params: RequestType['params']; + params: RequestType['params']; // deferred inference raw: RawRequest; query: RequestType['query']; headers: RawRequest['headers'] & RequestType['headers']; // this enables the developer to extend the existing http(s|2) headers list diff --git a/types/route.d.ts b/types/route.d.ts index 7aa2f00cf11..a76659f5a00 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -7,10 +7,9 @@ import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHa import { FastifyError } from '@fastify/error' import { FastifyContext } from './context' import { - FastifyRequestType, FastifyTypeProvider, FastifyTypeProviderDefault, - ResolveFastifyReplyReturnType, ResolveFastifyRequestType + ResolveFastifyReplyReturnType } from './type-provider' import { FastifyLoggerInstance, LogLevel } from './logger' @@ -27,7 +26,6 @@ export interface RouteShorthandOptions< ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { schema?: SchemaCompiler, // originally FastifySchema @@ -47,15 +45,15 @@ export interface RouteShorthandOptions< schemaErrorFormatter?: (errors: FastifySchemaValidationError[], dataVar: string) => Error; // hooks - onRequest?: onRequestHookHandler | onRequestHookHandler[]; - preParsing?: preParsingHookHandler | preParsingHookHandler[]; - preValidation?: preValidationHookHandler | preValidationHookHandler[]; - preHandler?: preHandlerHookHandler | preHandlerHookHandler[]; - preSerialization?: preSerializationHookHandler | preSerializationHookHandler[]; - onSend?: onSendHookHandler | onSendHookHandler[]; - onResponse?: onResponseHookHandler | onResponseHookHandler[]; - onTimeout?: onTimeoutHookHandler | onTimeoutHookHandler[]; - onError?: onErrorHookHandler | onErrorHookHandler[]; + onRequest?: onRequestHookHandler | onRequestHookHandler[]; + preParsing?: preParsingHookHandler | preParsingHookHandler[]; + preValidation?: preValidationHookHandler | preValidationHookHandler[]; + preHandler?: preHandlerHookHandler | preHandlerHookHandler[]; + preSerialization?: preSerializationHookHandler | preSerializationHookHandler[]; + onSend?: onSendHookHandler | onSendHookHandler[]; + onResponse?: onResponseHookHandler | onResponseHookHandler[]; + onTimeout?: onTimeoutHookHandler | onTimeoutHookHandler[]; + onError?: onErrorHookHandler | onErrorHookHandler[]; } /** @@ -69,11 +67,10 @@ export type RouteHandlerMethod< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > = ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply // This return type used to be a generic type argument. Due to TypeScript's inference of return types, this rendered returns unchecked. ) => ResolveFastifyReplyReturnType @@ -89,10 +86,9 @@ export interface RouteShorthandOptionsWithHandler< ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance -> extends RouteShorthandOptions { - handler: RouteHandlerMethod; +> extends RouteShorthandOptions { + handler: RouteHandlerMethod; } /** @@ -104,18 +100,18 @@ export interface RouteShorthandMethod< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { - , Logger extends FastifyLoggerInstance = FastifyLoggerInstance>( + ( path: string, - opts: RouteShorthandOptions, - handler: RouteHandlerMethod + opts: RouteShorthandOptions, + handler: RouteHandlerMethod ): FastifyInstance; - , Logger extends FastifyLoggerInstance = FastifyLoggerInstance>( + ( path: string, - handler: RouteHandlerMethod + handler: RouteHandlerMethod ): FastifyInstance; - , Logger extends FastifyLoggerInstance = FastifyLoggerInstance>( + ( path: string, - opts: RouteShorthandOptionsWithHandler + opts: RouteShorthandOptionsWithHandler ): FastifyInstance; } @@ -130,12 +126,11 @@ export interface RouteOptions< ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance -> extends RouteShorthandOptions { +> extends RouteShorthandOptions { method: HTTPMethods | HTTPMethods[]; url: string; - handler: RouteHandlerMethod; + handler: RouteHandlerMethod; } export type RouteHandler< @@ -146,11 +141,10 @@ export type RouteHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - RequestType extends FastifyRequestType = ResolveFastifyRequestType, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > = ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply ) => RouteGeneric['Reply'] | void | Promise diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 017587e509e..c09f2f9a9bb 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -48,12 +48,13 @@ export interface FastifyRequestType = FastifyRequestType< -ResolveRequestParams, -ResolveRequestQuerystring, -ResolveRequestHeaders, -ResolveRequestBody -> +// Resolves the FastifyRequest generic parameters +export interface ResolveFastifyRequestType extends FastifyRequestType { + params: ResolveRequestParams, + query: ResolveRequestQuerystring, + headers: ResolveRequestHeaders, + body: ResolveRequestBody +} // ----------------------------------------------------------------------------------------------- // FastifyReplyType From 95f9fa5abc105397a715fc376c3a6e704181d2e1 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 21 Jul 2022 18:37:44 +0200 Subject: [PATCH 0014/1295] Bumped v4.3.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 7769e927a83..1e5f033d6bc 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.2.1' +const VERSION = '4.3.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 0387452b100..5f3709dabf7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.2.1", + "version": "4.3.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From a232d8e5a1680d08abbad660b8aee2fd08e0701e Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Mon, 25 Jul 2022 11:25:10 +0200 Subject: [PATCH 0015/1295] fix(types): bad naming (#4151) --- types/request.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/request.d.ts b/types/request.d.ts index c493acaa105..35418ef2a40 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -64,8 +64,8 @@ export interface FastifyRequest boolean getValidationFunction(schema: {[key: string]: any}): (input: any) => boolean compileValidationSchema(schema: {[key: string]: any}, httpPart?: HTTPRequestPart): (input: any) => boolean - validate(input: any, schema: {[key: string]: any}, httpPart?: HTTPRequestPart): boolean - validate(input: any, httpPart?: HTTPRequestPart): boolean + validateInput(input: any, schema: {[key: string]: any}, httpPart?: HTTPRequestPart): boolean + validateInput(input: any, httpPart?: HTTPRequestPart): boolean // Prefer `socket` over deprecated `connection` property in node 13.0.0 or higher // @deprecated From 885df235b7bac845b3f81490338eed28637fc6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B8=BF=E5=88=99?= Date: Mon, 25 Jul 2022 17:26:11 +0800 Subject: [PATCH 0016/1295] feat: add webdav http methods (#3836) * feat: match node supported http methods * fix: supported methods * fix: add trace method * docs: add method to docs * docs: add method to docs * feat: add search method * test: search method * feat: add webdav http methods * docs: restore the original directory the vscode plugin automatically updated the directory * test: add webdav methods test case - remove webdav method shorthand Co-authored-by: Tomas Monteiro --- docs/Reference/Routes.md | 5 +- fastify.js | 2 +- lib/handleRequest.js | 4 +- lib/httpMethods.js | 22 ++++++++ lib/route.js | 2 +- test/copy.test.js | 41 ++++++++++++++ test/internals/all.test.js | 10 +++- test/lock.test.js | 73 +++++++++++++++++++++++++ test/mkcol.test.js | 38 +++++++++++++ test/move.test.js | 45 ++++++++++++++++ test/propfind.test.js | 108 +++++++++++++++++++++++++++++++++++++ test/proppatch.test.js | 78 +++++++++++++++++++++++++++ test/search.test.js | 100 ++++++++++++++++++++++++++++++++++ test/trace.test.js | 21 ++++++++ test/unlock.test.js | 41 ++++++++++++++ types/utils.d.ts | 3 +- 16 files changed, 584 insertions(+), 9 deletions(-) create mode 100644 lib/httpMethods.js create mode 100644 test/copy.test.js create mode 100644 test/lock.test.js create mode 100644 test/mkcol.test.js create mode 100644 test/move.test.js create mode 100644 test/propfind.test.js create mode 100644 test/proppatch.test.js create mode 100644 test/search.test.js create mode 100644 test/trace.test.js create mode 100644 test/unlock.test.js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index f213871c5dd..fca9a3b3960 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -32,8 +32,9 @@ fastify.route(options) ### Routes options -*`method`: currently it supports `'DELETE'`, `'GET'`, `'HEAD'`, `'PATCH'`, - `'POST'`, `'PUT'` and `'OPTIONS'`. It could also be an array of methods. +* `method`: currently it supports `'DELETE'`, `'GET'`, `'HEAD'`, `'PATCH'`, + `'POST'`, `'PUT'`, `'OPTIONS'`, `'SEARCH'`, `'TRACE'`, `'PROPFIND'`, `'PROPPATCH'`, `'MKCOL'`, + `'COPY'`, `'MOVE'`, `'LOCK'` and `'UNLOCK'`. It could also be an array of methods. * `url`: the path of the URL to match this route (alias: `path`). * `schema`: an object containing the schemas for the request and response. They need to be in [JSON Schema](https://json-schema.org/) format, check diff --git a/fastify.js b/fastify.js index 1e5f033d6bc..de1a3984920 100644 --- a/fastify.js +++ b/fastify.js @@ -34,7 +34,7 @@ const { const { createServer, compileValidateHTTPVersion } = require('./lib/server') const Reply = require('./lib/reply') const Request = require('./lib/request') -const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS'] +const { supportedMethods } = require('./lib/httpMethods') const decorator = require('./lib/decorate') const ContentTypeParser = require('./lib/contentTypeParser') const SchemaController = require('./lib/schema-controller') diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 282fb6a320b..a8edccc935f 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -18,14 +18,14 @@ function handleRequest (err, request, reply) { const method = request.raw.method const headers = request.headers - if (method === 'GET' || method === 'HEAD') { + if (method === 'GET' || method === 'HEAD' || method === 'SEARCH') { handler(request, reply) return } const contentType = headers['content-type'] - if (method === 'POST' || method === 'PUT' || method === 'PATCH') { + if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE') { if (contentType === undefined) { if ( headers['transfer-encoding'] === undefined && diff --git a/lib/httpMethods.js b/lib/httpMethods.js new file mode 100644 index 00000000000..f32abbaa099 --- /dev/null +++ b/lib/httpMethods.js @@ -0,0 +1,22 @@ +'use strict' + +module.exports = { + supportedMethods: [ + 'DELETE', + 'GET', + 'HEAD', + 'PATCH', + 'POST', + 'PUT', + 'OPTIONS', + 'PROPFIND', + 'PROPPATCH', + 'MKCOL', + 'COPY', + 'MOVE', + 'LOCK', + 'UNLOCK', + 'TRACE', + 'SEARCH' + ] +} diff --git a/lib/route.js b/lib/route.js index 44cf6a51604..7559653c6ca 100644 --- a/lib/route.js +++ b/lib/route.js @@ -4,7 +4,7 @@ const FindMyWay = require('find-my-way') const Context = require('./context') const handleRequest = require('./handleRequest') const { hookRunner, hookIterator, lifecycleHooks } = require('./hooks') -const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS'] +const { supportedMethods } = require('./httpMethods') const { normalizeSchema } = require('./schemas') const { parseHeadOnSendHandlers } = require('./headRoute') const warning = require('./warnings') diff --git a/test/copy.test.js b/test/copy.test.js new file mode 100644 index 00000000000..cdc4b2b18ac --- /dev/null +++ b/test/copy.test.js @@ -0,0 +1,41 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('..')() + +test('can be created - copy', t => { + t.plan(1) + try { + fastify.route({ + method: 'COPY', + url: '*', + handler: function (req, reply) { + reply.code(204).send() + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + test('request - copy', t => { + t.plan(2) + sget({ + url: `http://localhost:${fastify.server.address().port}/test.txt`, + method: 'COPY', + headers: { + Destination: '/test2.txt' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 204) + }) + }) +}) diff --git a/test/internals/all.test.js b/test/internals/all.test.js index 72756481269..73867968cf8 100644 --- a/test/internals/all.test.js +++ b/test/internals/all.test.js @@ -3,9 +3,15 @@ const t = require('tap') const test = t.test const Fastify = require('../..') -const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS'] +const { supportedMethods } = require('../../lib/httpMethods') test('fastify.all should add all the methods to the same url', t => { + const requirePayload = [ + 'POST', + 'PUT', + 'PATCH' + ] + t.plan(supportedMethods.length * 2) const fastify = Fastify() @@ -22,7 +28,7 @@ test('fastify.all should add all the methods to the same url', t => { method } - if (method === 'POST' || method === 'PUT' || method === 'PATCH') { + if (requirePayload.includes(method)) { options.payload = { hello: 'world' } } diff --git a/test/lock.test.js b/test/lock.test.js new file mode 100644 index 00000000000..28755886163 --- /dev/null +++ b/test/lock.test.js @@ -0,0 +1,73 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('..')() + +test('can be created - lock', t => { + t.plan(1) + try { + fastify.route({ + method: 'LOCK', + url: '*', + handler: function (req, reply) { + reply + .code(200) + .send(` + + + + + + + + + + infinity + + http://example.org/~ejw/contact.html + + Second-604800 + + urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4 + + + http://example.com/workspace/webdav/proposal.oc + + + + ` + ) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + test('request - lock', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + body: ` + + + + + http://example.org/~ejw/contact.html + + `, + method: 'LOCK' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) +}) diff --git a/test/mkcol.test.js b/test/mkcol.test.js new file mode 100644 index 00000000000..ace80348519 --- /dev/null +++ b/test/mkcol.test.js @@ -0,0 +1,38 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('..')() + +test('can be created - mkcol', t => { + t.plan(1) + try { + fastify.route({ + method: 'MKCOL', + url: '*', + handler: function (req, reply) { + reply.code(201).send() + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + test('request - mkcol', t => { + t.plan(2) + sget({ + url: `http://localhost:${fastify.server.address().port}/test/`, + method: 'MKCOL' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 201) + }) + }) +}) diff --git a/test/move.test.js b/test/move.test.js new file mode 100644 index 00000000000..45f556f09b3 --- /dev/null +++ b/test/move.test.js @@ -0,0 +1,45 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('..')() + +test('shorthand - move', t => { + t.plan(1) + try { + fastify.route({ + method: 'MOVE', + url: '*', + handler: function (req, reply) { + const destination = req.headers.destination + reply.code(201) + .header('location', destination) + .send() + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + test('request - move', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test.txt`, + method: 'MOVE', + headers: { + Destination: '/test2.txt' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 201) + t.equal(response.headers.location, '/test2.txt') + }) + }) +}) diff --git a/test/propfind.test.js b/test/propfind.test.js new file mode 100644 index 00000000000..1b2bdfc878e --- /dev/null +++ b/test/propfind.test.js @@ -0,0 +1,108 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('..')() + +test('can be created - propfind', t => { + t.plan(1) + try { + fastify.route({ + method: 'PROPFIND', + url: '*', + handler: function (req, reply) { + return reply.code(207) + .send(` + + + / + + + + + + 2022-04-13T12:35:30Z + Wed, 13 Apr 2022 12:35:30 GMT + "e0-5dc8869b53ef1" + + + + + + + + + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + + ` + ) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + test('request - propfind', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/`, + method: 'PROPFIND' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) + + test('request with other path - propfind', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test`, + method: 'PROPFIND' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) + + test('request with body - propfind', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test`, + body: ` + + + + + + `, + method: 'PROPFIND' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) +}) diff --git a/test/proppatch.test.js b/test/proppatch.test.js new file mode 100644 index 00000000000..b4886eef52d --- /dev/null +++ b/test/proppatch.test.js @@ -0,0 +1,78 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('..')() + +test('shorthand - proppatch', t => { + t.plan(1) + try { + fastify.route({ + method: 'PROPPATCH', + url: '*', + handler: function (req, reply) { + reply + .code(207) + .send(` + + + http://www.example.com/bar.html + + + + + HTTP/1.1 424 Failed Dependency + + + + + + HTTP/1.1 409 Conflict + + Copyright Owner cannot be deleted or altered. + + ` + ) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + test('request - proppatch', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + body: ` + + + + + Jim Whitehead + Roy Fielding + + + + + + + + + `, + method: 'PROPPATCH' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) +}) diff --git a/test/search.test.js b/test/search.test.js new file mode 100644 index 00000000000..68a8414df40 --- /dev/null +++ b/test/search.test.js @@ -0,0 +1,100 @@ +'use strict' + +const t = require('tap') +const test = t.test +const fastify = require('..')() + +const schema = { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } + } +} + +const querySchema = { + schema: { + querystring: { + type: 'object', + properties: { + hello: { + type: 'integer' + } + } + } + } +} + +const paramsSchema = { + schema: { + params: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + test: { + type: 'integer' + } + } + } + } +} + +test('shorthand - search', t => { + t.plan(1) + try { + fastify.route({ + method: 'SEARCH', + url: '/', + schema, + handler: function (request, reply) { + reply.code(200).send({ hello: 'world' }) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +test('shorthand - search params', t => { + t.plan(1) + try { + fastify.route({ + method: 'SEARCH', + url: '/params/:foo/:test', + paramsSchema, + handler: function (request, reply) { + reply.code(200).send(request.params) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +test('shorthand - get, querystring schema', t => { + t.plan(1) + try { + fastify.route({ + method: 'SEARCH', + url: '/query', + querySchema, + handler: function (request, reply) { + reply.code(200).send(request.query) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) diff --git a/test/trace.test.js b/test/trace.test.js new file mode 100644 index 00000000000..e88c5e2085d --- /dev/null +++ b/test/trace.test.js @@ -0,0 +1,21 @@ +'use strict' + +const t = require('tap') +const test = t.test +const fastify = require('..')() + +test('shorthand - trace', t => { + t.plan(1) + try { + fastify.route({ + method: 'TRACE', + url: '/', + handler: function (request, reply) { + reply.code(200).send('TRACE OK') + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) diff --git a/test/unlock.test.js b/test/unlock.test.js new file mode 100644 index 00000000000..0d88729495b --- /dev/null +++ b/test/unlock.test.js @@ -0,0 +1,41 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('..')() + +test('can be created - unlock', t => { + t.plan(1) + try { + fastify.route({ + method: 'UNLOCK', + url: '*', + handler: function (req, reply) { + reply.code(204).send() + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + test('request - unlock', t => { + t.plan(2) + sget({ + url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + method: 'UNLOCK', + headers: { + 'Lock-Token': 'urn:uuid:a515cfa4-5da4-22e1-f5b5-00a0451e6bf7' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 204) + }) + }) +}) diff --git a/types/utils.d.ts b/types/utils.d.ts index 995e2b0f9a8..cb0740c1267 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -5,7 +5,8 @@ import * as https from 'https' /** * Standard HTTP method strings */ -export type HTTPMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS' +export type HTTPMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS' | +'PROPFIND' | 'PROPPATCH' | 'MKCOL' | 'COPY' | 'MOVE' | 'LOCK' | 'UNLOCK' | 'TRACE' | 'SEARCH' /** * A union type of the Node.js server types from the http, https, and http2 modules. From 66fc397212531c2565a9cb86a2c0f4a2f06a8c77 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 25 Jul 2022 19:27:43 +0200 Subject: [PATCH 0017/1295] markdown lint fix Signed-off-by: Matteo Collina --- docs/Reference/Routes.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index fca9a3b3960..8936427befa 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -33,8 +33,9 @@ fastify.route(options) * `method`: currently it supports `'DELETE'`, `'GET'`, `'HEAD'`, `'PATCH'`, - `'POST'`, `'PUT'`, `'OPTIONS'`, `'SEARCH'`, `'TRACE'`, `'PROPFIND'`, `'PROPPATCH'`, `'MKCOL'`, - `'COPY'`, `'MOVE'`, `'LOCK'` and `'UNLOCK'`. It could also be an array of methods. + `'POST'`, `'PUT'`, `'OPTIONS'`, `'SEARCH'`, `'TRACE'`, `'PROPFIND'`, + `'PROPPATCH'`, `'MKCOL'`, `'COPY'`, `'MOVE'`, `'LOCK'` and `'UNLOCK'`. + It could also be an array of methods. * `url`: the path of the URL to match this route (alias: `path`). * `schema`: an object containing the schemas for the request and response. They need to be in [JSON Schema](https://json-schema.org/) format, check From 18651e612d0aa14592bf3bac6a9e4a23116983f3 Mon Sep 17 00:00:00 2001 From: Sean Parmelee Date: Tue, 26 Jul 2022 03:44:02 -0500 Subject: [PATCH 0018/1295] fix(types): reply.getHeader can return numbers and arrays (#4154) --- test/internals/reply.test.js | 5 ++++- test/types/reply.test-d.ts | 2 +- types/reply.d.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 3dfe2465617..085b67b2803 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -1044,7 +1044,7 @@ test('reply.hasHeader returns correct values', t => { }) test('reply.getHeader returns correct values', t => { - t.plan(4) + t.plan(5) const fastify = require('../../')() @@ -1055,6 +1055,9 @@ test('reply.getHeader returns correct values', t => { reply.header('x-foo', 'bar') t.strictSame(reply.getHeader('x-foo'), 'bar') + reply.header('x-foo', 42) + t.strictSame(reply.getHeader('x-foo'), 42) + reply.header('set-cookie', 'one') reply.header('set-cookie', 'two') t.strictSame(reply.getHeader('set-cookie'), ['one', 'two']) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index f988d604358..ebfe90e05f4 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -19,7 +19,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<((payload?: unknown) => FastifyReply)>(reply.send) expectType<(key: string, value: any) => FastifyReply>(reply.header) expectType<(values: {[key: string]: any}) => FastifyReply>(reply.headers) - expectType<(key: string) => string | undefined>(reply.getHeader) + expectType<(key: string) => number | string | string[] | undefined>(reply.getHeader) expectType<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders) expectType<(key: string) => void>(reply.removeHeader) expectType<(key: string) => boolean>(reply.hasHeader) diff --git a/types/reply.d.ts b/types/reply.d.ts index 228ecebd830..8dcd81ded78 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -38,7 +38,7 @@ export interface FastifyReply< send(payload?: ReplyType): FastifyReply; header(key: string, value: any): FastifyReply; headers(values: {[key: string]: any}): FastifyReply; - getHeader(key: string): string | undefined; + getHeader(key: string): number | string | string[] | undefined; getHeaders(): { // Node's `getHeaders()` can return numbers and arrays, so they're included here as possible types. [key: string]: number | string | string[] | undefined; From 7870f23bd6f99f141b32a50994ccb3bc8aaf4ebb Mon Sep 17 00:00:00 2001 From: Sergey Grigorev Date: Wed, 27 Jul 2022 11:54:07 +0300 Subject: [PATCH 0019/1295] feat(logger): use pino.BaseLogger as main interface for fastify logger (#4150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(logger): use pino.BaseLogger as main interface for fastify logger instance Using pino.BaseLogger allows to use an own logger implementation in fastify. The interface describes only base properties unlike pino.Logger that requires to implement all properties of pino. * chore(logger): add comment todo about deleting FastifyLoggerInstance type in the next major release * chore(logger): add a test to check that FastifyLoggerInstance is deprecated Co-authored-by: Григорьев Сергей Николаевич --- docs/Reference/Logging.md | 2 +- test/types/logger.test-d.ts | 9 ++++----- test/types/request.test-d.ts | 28 +--------------------------- types/logger.d.ts | 11 +++++++---- 4 files changed, 13 insertions(+), 37 deletions(-) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 960c8725b2f..060b4dfab70 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -170,7 +170,7 @@ app.addHook('preHandler', function (req, reply, done) { You can also supply your own logger instance. Instead of passing configuration options, pass the instance. The logger you supply must conform to the Pino interface; that is, it must have the following methods: `info`, `error`, -`debug`, `fatal`, `warn`, `trace`, `child`. +`debug`, `fatal`, `warn`, `trace`, `silent`, `child` and a string property `level`. Example: diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index daac4c5e9b3..d8e6b9cbfa8 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -1,4 +1,4 @@ -import { expectError, expectType } from 'tsd' +import { expectDeprecated, expectError, expectType } from 'tsd' import fastify, { FastifyLogFn, LogLevel, @@ -24,13 +24,10 @@ class Foo {} expectType(fastify().log[logLevel as LogLevel](new Foo())) }) -/* -// TODO make pino export BaseLogger again interface CustomLogger extends FastifyBaseLogger { customMethod(msg: string, ...args: unknown[]): void; } -// // ToDo https://github.com/pinojs/pino/issues/1100 class CustomLoggerImpl implements CustomLogger { level = 'info' customMethod (msg: string, ...args: unknown[]) { console.log(msg, args) } @@ -60,7 +57,6 @@ CustomLoggerImpl >({ logger: customLogger }) expectType(serverWithCustomLogger.log) -*/ const serverWithPino = fastify< Server, @@ -213,6 +209,9 @@ const passPinoOption = fastify({ expectType(passPinoOption.log) +// FastifyLoggerInstance is deprecated +expectDeprecated({} as FastifyLoggerInstance) + const childParent = fastify().log // we test different option variant here expectType(childParent.child({}, { level: 'info' })) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index f2617969d77..c7f318c1c37 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -21,7 +21,6 @@ import { FastifyRequest } from '../../types/request' import { FastifyReply } from '../../types/reply' import { FastifyInstance } from '../../types/instance' import { RouteGenericInterface } from '../../types/route' -import { ResolveFastifyReplyReturnType, ResolveFastifyRequestType } from '../../types/type-provider' interface RequestBody { content: string; @@ -128,17 +127,6 @@ server.put('/put', putHandler) const customLogger: CustomLoggerInterface = { level: 'info', - version: '5.0', - useOnlyCustomLevels: false, - useLevelLabels: false, - levels: { labels: [], values: {} }, - eventNames: () => [], - listenerCount: (eventName: string | symbol) => 0, - bindings: () => ({}), - flush: () => () => {}, - customLevels: { foo: 1 }, - isLevelEnabled: () => false, - levelVal: 0, silent: () => { }, info: () => { }, warn: () => { }, @@ -147,21 +135,7 @@ const customLogger: CustomLoggerInterface = { trace: () => { }, debug: () => { }, foo: () => { }, // custom severity logger method - on: (event, listener) => customLogger, - emit: (event, listener) => false, - off: (event, listener) => customLogger, - addListener: (event, listener) => customLogger, - prependListener: (event, listener) => customLogger, - prependOnceListener: (event, listener) => customLogger, - removeListener: (event, listener) => customLogger, - removeAllListeners: (event) => customLogger, - setMaxListeners: (n) => customLogger, - getMaxListeners: () => 0, - listeners: () => [], - rawListeners: () => [], - once: (event, listener) => customLogger, - child: () => customLogger as pino.Logger, - setBindings: (bindings) => { } + child: () => customLogger as pino.Logger } const serverWithCustomLogger = fastify({ logger: customLogger }) diff --git a/types/logger.d.ts b/types/logger.d.ts index 4483f0b372b..d25f01da424 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -19,13 +19,16 @@ export type Bindings = pino.Bindings export type ChildLoggerOptions = pino.ChildLoggerOptions -export type FastifyLoggerInstance = pino.Logger -// TODO make pino export BaseLogger again -// export type FastifyBaseLogger = pino.BaseLogger & { -export type FastifyBaseLogger = pino.Logger & { +export type FastifyBaseLogger = pino.BaseLogger & { child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger } +// TODO delete FastifyLoggerInstance in the next major release. It seems that it is enough to have only FastifyBaseLogger. +/** + * @deprecated Use FastifyBaseLogger instead + */ +export type FastifyLoggerInstance = FastifyBaseLogger + export interface FastifyLoggerStreamDestination { write(msg: string): void; } From 5a785ee49b2119d66cc08597d4f8d2f9cb506ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Strgar?= Date: Wed, 27 Jul 2022 16:40:31 +0200 Subject: [PATCH 0020/1295] docs(ecosystem): Add fastify-bugsnag as community plugin (#4155) Signed-off-by: ZigaStrgar --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 861c5f6d9bd..3dac90fd55b 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -215,6 +215,8 @@ section. to add [boom](https://github.com/hapijs/boom) support. - [`fastify-bree`](https://github.com/climba03003/fastify-bree) Fastify plugin to add [bree](https://github.com/breejs/bree) support. +- [`fastify-bugsnag`](https://github.com/ZigaStrgar/fastify-bugsnag) Fastify plugin + to add support for [Bugsnag](https://www.bugsnag.com/) error reporting. - [`fastify-casbin`](https://github.com/nearform/fastify-casbin) Casbin support for Fastify. - [`fastify-casbin-rest`](https://github.com/nearform/fastify-casbin-rest) From 2fcbd9d49a8dd7c080ec295bf3ca8c082b5920fb Mon Sep 17 00:00:00 2001 From: Het Patel <79783828+developerHet@users.noreply.github.com> Date: Sat, 30 Jul 2022 23:07:05 +0530 Subject: [PATCH 0021/1295] fix typo in docs/reference/Server.md (#4163) --- docs/Reference/Server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index e0204aecf5f..f539c8b4962 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -890,7 +890,7 @@ fastify.ready().then(() => { Starts the server and internally waits for the `.ready()` event. The signature is `.listen([options][, callback])`. Both the `options` object and the `callback` parameters follow the [Node.js -core][https://nodejs.org/api/net.html#serverlistenoptions-callback] parameter +core](https://nodejs.org/api/net.html#serverlistenoptions-callback) parameter definitions. By default, the server will listen on the address(es) resolved by `localhost` From 5bd6eec0b20954ea4a4235eb7c61d65a54eab50a Mon Sep 17 00:00:00 2001 From: CallMe AsYouFeel Date: Mon, 1 Aug 2022 05:00:05 +0900 Subject: [PATCH 0022/1295] Make ResolveFastifyReplyType union-aware (#4164) --- test/types/reply.test-d.ts | 36 +++++++++++++++++++++++++++++- test/types/type-provider.test-d.ts | 4 +++- types/type-provider.d.ts | 15 +------------ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index ebfe90e05f4..80906a47818 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -40,6 +40,14 @@ interface ReplyPayload { }; } +interface ReplyUnion { + Reply: { + success: boolean; + } | { + error: string; + } +} + const typedHandler: RouteHandler = async (request, reply) => { expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.send) } @@ -53,9 +61,35 @@ server.get('/get-generic-send', async function handler (request, r server.get('/get-generic-return', async function handler (request, reply) { return { test: false } }) -expectError(server.get('/get-generic-return-error', async function handler (request, reply) { +expectError(server.get('/get-generic-send-error', async function handler (request, reply) { reply.send({ foo: 'bar' }) })) expectError(server.get('/get-generic-return-error', async function handler (request, reply) { return { foo: 'bar' } })) +server.get('/get-generic-union-send', async function handler (request, reply) { + if (0 as number === 0) { + reply.send({ success: true }) + } else { + reply.send({ error: 'error' }) + } +}) +server.get('/get-generic-union-return', async function handler (request, reply) { + if (0 as number === 0) { + return { success: true } + } else { + return { error: 'error' } + } +}) +expectError(server.get('/get-generic-union-send-error-1', async function handler (request, reply) { + reply.send({ successes: true }) +})) +expectError(server.get('/get-generic-union-send-error-2', async function handler (request, reply) { + reply.send({ error: 500 }) +})) +expectError(server.get('/get-generic-union-return-error-1', async function handler (request, reply) { + return { successes: true } +})) +expectError(server.get('/get-generic-union-return-error-2', async function handler (request, reply) { + return { error: 500 } +})) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index 91e7b7fc3da..e492c31cce1 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -391,7 +391,9 @@ expectError(server.withTypeProvider().get( // https://github.com/fastify/fastify/issues/4088 expectError(server.withTypeProvider().get('/', { schema: { - response: { type: 'string' } + response: { + 200: { type: 'string' } + } } as const }, (_, res) => { return { foo: 555 } diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index c09f2f9a9bb..9f1c8da6dc5 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -60,15 +60,6 @@ export interface ResolveFastifyRequestType = keyof RouteGeneric['Reply'] extends never ? false : true - -// Tests if the user has specified a response schema. -type UseReplyFromSchemaCompiler = keyof SchemaCompiler['response'] extends never ? false : true - -// Resolves the Reply type from the generic argument -type ResolveReplyFromRouteGeneric = RouteGeneric['Reply'] - // Resolves the Reply type by taking a union of response status codes type ResolveReplyFromSchemaCompiler = { [K in keyof SchemaCompiler['response']]: CallTypeProvider @@ -80,11 +71,7 @@ export type FastifyReplyType = Reply // Resolves the Reply type either via generic argument or from response schema. This type uses a different // resolution strategy to Requests where the Reply will infer a union of each status code type specified // by the user. The Reply can be explicitly overriden by users providing a generic Reply type on the route. -export type ResolveFastifyReplyType = FastifyReplyType< -UseReplyFromRouteGeneric extends true ? ResolveReplyFromRouteGeneric : - UseReplyFromSchemaCompiler extends true ? ResolveReplyFromSchemaCompiler : - unknown -> +export type ResolveFastifyReplyType = UndefinedToUnknown extends never ? ResolveReplyFromSchemaCompiler : RouteGeneric['Reply']> // ----------------------------------------------------------------------------------------------- // FastifyReplyReturnType From be54b9e99c5f486fc664b233015eb2615ee29cac Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Mon, 1 Aug 2022 10:04:57 +0300 Subject: [PATCH 0023/1295] docs: update code snippet for accurate use (#4167) For folks who copy&paste this code snippet, it would be beneficial to include an accurate and working example which imports the `promisify()` function. Signed-off-by: Liran Tal --- docs/Reference/Reply.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index ab67409e421..2e601dbe5ae 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -767,6 +767,7 @@ Fastify natively handles promises and supports async-await. *Note that in the following examples we are not using reply.send.* ```js +const { promisify } = require('util') const delay = promisify(setTimeout) fastify.get('/promises', options, function (request, reply) { From 81ddf7d740e8fc34ece8d909f9207eed61c9b12c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 14:13:39 +0000 Subject: [PATCH 0024/1295] build(deps): bump lycheeverse/lychee-action from 1.5.0 to 1.5.1 (#4169) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.5.0 to 1.5.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/76ab977fedbeaeb32029313724a2e56a8a393548...4a5af7cd2958a2282cefbd9c10f63bdb89982d76) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 618df1305df..5d07926e8a4 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@76ab977fedbeaeb32029313724a2e56a8a393548 + uses: lycheeverse/lychee-action@4a5af7cd2958a2282cefbd9c10f63bdb89982d76 with: fail: true # As external links behaviour is not predictable, we check only internal links From c6cd0e9aa4d2a88a34ebb551edb958b7a5597d50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 14:20:41 +0000 Subject: [PATCH 0025/1295] build(deps-dev): bump markdownlint-cli2 from 0.4.0 to 0.5.0 (#4170) Bumps [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/DavidAnson/markdownlint-cli2/releases) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: markdownlint-cli2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f3709dabf7..4daa1d59f60 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "json-schema-to-ts": "^2.5.3", "JSONStream": "^1.3.5", "license-checker": "^25.0.1", - "markdownlint-cli2": "^0.4.0", + "markdownlint-cli2": "^0.5.0", "proxyquire": "^2.1.3", "pump": "^3.0.0", "self-cert": "^2.0.0", From ba9ca08c2b7b775ceb174721b8019efdc3ba5f5d Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Wed, 3 Aug 2022 13:25:07 +0200 Subject: [PATCH 0026/1295] types: adjust and add testing (#4158) --- test/types/reply.test-d.ts | 9 ++++++++- test/types/request.test-d.ts | 9 ++++++++- types/reply.d.ts | 11 ++++++----- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 80906a47818..6d2dfb3dcd1 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,4 +1,4 @@ -import { expectType, expectError } from 'tsd' +import { expectType, expectError, expectAssignable } from 'tsd' import fastify, { RouteHandlerMethod, RouteHandler, RawRequestDefaultExpression, FastifyContext, FastifyContextConfig, FastifyRequest, FastifyReply } from '../../fastify' import { RawServerDefault, RawReplyDefaultExpression, ContextConfigDefault } from '../../types/utils' import { FastifyLoggerInstance } from '../../types/logger' @@ -6,6 +6,8 @@ import { RouteGenericInterface } from '../../types/route' import { FastifyInstance } from '../../types/instance' import { Buffer } from 'buffer' +type DefaultSerializationFunction = (payload: {[key: string]: unknown}) => string + const getHandler: RouteHandlerMethod = function (_request, reply) { expectType(reply.raw) expectType>(reply.context) @@ -32,6 +34,11 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<(payload: any) => string | ArrayBuffer | Buffer>(reply.serialize) expectType<(fulfilled: () => void, rejected: (err: Error) => void) => void>(reply.then) expectType(reply.server) + expectAssignable<((httpStatus: string) => DefaultSerializationFunction)>(reply.getSerializationFunction) + expectAssignable<((schema: {[key: string]: unknown}) => DefaultSerializationFunction)>(reply.getSerializationFunction) + expectAssignable<((schema: {[key: string]: unknown}, httpStatus?: string) => DefaultSerializationFunction)>(reply.compileSerializationSchema) + expectAssignable<((input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string) => unknown)>(reply.serializeInput) + expectAssignable<((input: {[key: string]: unknown}, httpStatus: string) => unknown)>(reply.serializeInput) } interface ReplyPayload { diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index c7f318c1c37..007be236eea 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -1,4 +1,4 @@ -import { expectType } from 'tsd' +import { expectAssignable, expectType } from 'tsd' import pino from 'pino' import fastify, { RouteHandler, @@ -54,6 +54,9 @@ type CustomRequest = FastifyRequest<{ Headers: RequestHeaders; }> +type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' +type ExpectedGetValidationFunction = (input: {[key: string]: unknown}) => boolean + interface CustomLoggerInterface extends FastifyLoggerInstance { foo: FastifyLogFn; // custom severity logger method } @@ -82,6 +85,10 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.socket) expectType(request.validationError) expectType(request.server) + expectAssignable<(httpPart: HTTPRequestPart) => ExpectedGetValidationFunction>(request.getValidationFunction) + expectAssignable<(schema: {[key: string]: unknown}) => ExpectedGetValidationFunction>(request.getValidationFunction) + expectAssignable<(input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) + expectAssignable<(input: {[key: string]: unknown}, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) } const getHandlerWithCustomLogger: RouteHandlerMethod = function (request, _reply) { diff --git a/types/reply.d.ts b/types/reply.d.ts index 8dcd81ded78..8b637160022 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -54,10 +54,11 @@ export interface FastifyReply< type(contentType: string): FastifyReply; serializer(fn: (payload: any) => string): FastifyReply; serialize(payload: any): string | ArrayBuffer | Buffer; - getSerializationFunction(httpStatus: string): (payload: any) => string; - getSerializationFunction(schema: {[key: string]: any}): (payload: any) => string; - compileSerializationSchema(schema: {[key: string]: any}, httpStatus?: string): (payload: any) => string; - serializeInput(input: any, schema: {[key: string]: any}, httpStatus?: string): string - serializeInput(input: any, httpStatus: string): string + // Serialization Methods + getSerializationFunction(httpStatus: string): (payload: {[key: string]: unknown}) => string; + getSerializationFunction(schema: {[key: string]: unknown}): (payload: {[key: string]: unknown}) => string; + compileSerializationSchema(schema: {[key: string]: unknown}, httpStatus?: string): (payload: {[key: string]: unknown}) => string; + serializeInput(input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string): string; + serializeInput(input: {[key: string]: unknown}, httpStatus: string): unknown; then(fulfilled: () => void, rejected: (err: Error) => void): void; } From 803fda1851805e6cb0827b3f34bd9395b8fb6999 Mon Sep 17 00:00:00 2001 From: Mateo Nunez Date: Fri, 5 Aug 2022 13:53:49 +0200 Subject: [PATCH 0027/1295] docs(ecosystem): add fastify-lyra (#4176) --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 3dac90fd55b..4e081ac76a7 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -147,6 +147,9 @@ section. - [`@immobiliarelabs/fastify-sentry`](https://github.com/immobiliare/fastify-sentry) Sentry errors handler that just works! Install, add your DSN and you're good to go! +- [`@mateonunez/fastify-lyra`](https://github.com/mateonunez/fastify-lyra) + A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine + on Fastify - [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit) A plugin to close the server gracefully - [`@mgcrea/fastify-request-logger`](https://github.com/mgcrea/fastify-request-logger) From 4e341e8e1f778b29c1dfcd0388129b9a710b7077 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Sat, 6 Aug 2022 00:46:26 +0300 Subject: [PATCH 0028/1295] Add links to type providers (#4177) --- docs/Guides/Ecosystem.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 4e081ac76a7..2498b232667 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -112,6 +112,14 @@ section. - [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) Plugin for serving Swagger/OpenAPI documentation for Fastify, supporting dynamic generation. +- [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) + Fastify + [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) + for [json-schema-to-ts](https://github.com/ThomasAribart/json-schema-to-ts). +- [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox) + Fastify + [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) + for [Typebox](https://github.com/sinclairzx81/typebox). - [`@fastify/under-pressure`](https://github.com/fastify/under-pressure) Measure process load with automatic handling of _"Service Unavailable"_ plugin for Fastify. @@ -513,6 +521,10 @@ section. TOTP (e.g. for 2FA). - [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools) Useful functions for Twitch Extension Backend Services (EBS). +- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod) + Fastify + [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) + for [zod](https://github.com/colinhacks/zod). - [`fastify-typeorm-plugin`](https://github.com/inthepocket/fastify-typeorm-plugin) Fastify plugin to work with TypeORM. - [`fastify-vhost`](https://github.com/patrickpissurno/fastify-vhost) Proxy From 35aa4d9d0f00acb49930377bfe7e97209bb6b99c Mon Sep 17 00:00:00 2001 From: "YuBin, Hsu" <31545456+yubinTW@users.noreply.github.com> Date: Mon, 8 Aug 2022 14:39:38 +0800 Subject: [PATCH 0029/1295] docs(ecosystem): add fastify-keycloak-adapter (#4180) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 2498b232667..5f7c4c593a4 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -341,6 +341,8 @@ section. authentication for Fastify-based web apps. - [`fastify-kafkajs`](https://github.com/kffl/fastify-kafkajs) Fastify plugin that adds support for KafkaJS - a modern Apache Kafka client library. +- [`fastify-keycloak-adapter`](https://github.com/yubinTW/fastify-keycloak-adapter) + A keycloak adapter for a Fastify app. - [`fastify-knexjs`](https://github.com/chapuletta/fastify-knexjs) Fastify plugin for support KnexJS Query Builder. - [`fastify-knexjs-mock`](https://github.com/chapuletta/fastify-knexjs-mock) From 0767cbd767fcb7aba948ac7ff353603442f630e3 Mon Sep 17 00:00:00 2001 From: Andrea Vanelli <50946384+avanelli@users.noreply.github.com> Date: Mon, 8 Aug 2022 08:50:35 +0200 Subject: [PATCH 0030/1295] Docs: onResponse hook error logging (#4178) * add onResponse note on disableRequestLogging * add note on setErrorHandler for onResponse * fix line lenght * fix line lenght * add reason setErrorHandler don't catch onResponse * fix wording * changed hook example --- docs/Reference/Hooks.md | 5 ++++- docs/Reference/Server.md | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 3fd8f75245f..90dec038061 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -244,6 +244,9 @@ The `onResponse` hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example, to gather statistics. +**Note:** setting `disableRequestLogging` to `true` will disable any error log +inside the `onResponse` hook. In this case use `try - catch` to log errors. + ### onTimeout ```js @@ -287,7 +290,7 @@ fastify.addHook('preHandler', (request, reply, done) => { Or if you're using `async/await` you can just throw an error: ```js -fastify.addHook('onResponse', async (request, reply) => { +fastify.addHook('onRequest', async (request, reply) => { throw new Error('Some error') }) ``` diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index f539c8b4962..694f03e6b37 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1402,6 +1402,9 @@ handlers. *async-await* is supported as well. *Note: If the error `statusCode` is less than 400, Fastify will automatically set it at 500 before calling the error handler.* +*Also note* that `setErrorHandler` will ***not*** catch any error inside +an `onResponse` hook because the response has already been sent to the client. + ```js fastify.setErrorHandler(function (error, request, reply) { // Log error From e618c7cd7d9d7b067aebdc4dbde22c290d8e3987 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 8 Aug 2022 10:49:05 +0200 Subject: [PATCH 0031/1295] Bumped v4.4.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index de1a3984920..7e56a70500f 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.3.0' +const VERSION = '4.4.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 4daa1d59f60..74051b43c0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.3.0", + "version": "4.4.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 723eadbe4584c2fcf09ec244b20e03e2478655cc Mon Sep 17 00:00:00 2001 From: Giuseppe Zileni <6775950+gzileni@users.noreply.github.com> Date: Tue, 9 Aug 2022 11:27:11 +0200 Subject: [PATCH 0032/1295] Update Ecosystem.md (#4185) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 5f7c4c593a4..d514e251be2 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -426,6 +426,8 @@ section. - [`fastify-orientdb`](https://github.com/mahmed8003/fastify-orientdb) Fastify OrientDB connection plugin, with which you can share the OrientDB connection across every part of your server. +- [`fastify-osm`](https://github.com/gzileni/fastify-osm) Fastify + OSM plugin to run overpass queries by OpenStreetMap. - [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo) Fastify plugin for memoize responses by expressive settings. - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker From e5a227bc12d63e76c8818ab5a71f423868e6293e Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 10 Aug 2022 12:57:51 +0100 Subject: [PATCH 0033/1295] docs(reference/routes): fix `onSend` example (#4188) --- docs/Reference/Routes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 8936427befa..d3b39f32458 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -658,7 +658,7 @@ fastify.inject({ > > ```js > const append = require('vary').append -> fastify.addHook('onSend', async (req, reply) => { +> fastify.addHook('onSend', (req, reply, payload, done) => { > if (req.headers['accept-version']) { // or the custom header you are using > let value = reply.getHeader('Vary') || '' > const header = Array.isArray(value) ? value.join(', ') : String(value) @@ -666,6 +666,7 @@ fastify.inject({ > reply.header('Vary', value) > } > } +> done() > }) > ``` From 1a33214af46affb714f43b54253bc480b8708803 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Wed, 10 Aug 2022 14:30:21 +0200 Subject: [PATCH 0034/1295] chore: clean dev-dependancies (#4189) --- package.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index 74051b43c0c..9510a42d7f3 100644 --- a/package.json +++ b/package.json @@ -137,8 +137,6 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "branch-comparer": "^1.1.0", - "cors": "^2.8.5", - "dns-prefetch-control": "^0.3.0", "eslint": "^8.16.0", "eslint-config-standard": "^17.0.0-1", "eslint-import-resolver-node": "^0.3.6", @@ -150,10 +148,7 @@ "fastify-plugin": "^4.0.0", "fluent-json-schema": "^3.1.0", "form-data": "^4.0.0", - "frameguard": "^4.0.0", "h2url": "^0.2.0", - "helmet": "^5.1.0", - "hide-powered-by": "^1.1.0", "http-errors": "^2.0.0", "joi": "^17.6.0", "json-schema-to-ts": "^2.5.3", @@ -164,7 +159,6 @@ "pump": "^3.0.0", "self-cert": "^2.0.0", "send": "^0.18.0", - "serve-static": "^1.15.0", "simple-get": "^4.0.1", "snazzy": "^9.0.0", "split2": "^4.1.0", @@ -173,7 +167,7 @@ "tsd": "^0.22.0", "typescript": "^4.7.2", "undici": "^5.4.0", - "x-xss-protection": "^2.0.0", + "vary": "^1.1.2", "yup": "^0.32.11" }, "dependencies": { From d0f8303d020be00cf181a6271b3c719ca6b4ab88 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 10 Aug 2022 15:48:19 +0200 Subject: [PATCH 0035/1295] Forward upgrade from secondary server to primary (#4190) Signed-off-by: Matteo Collina --- lib/server.js | 1 + test/upgrade.test.js | 53 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 test/upgrade.test.js diff --git a/lib/server.js b/lib/server.js index 46a2f76ba4f..0a23fd022f6 100644 --- a/lib/server.js +++ b/lib/server.js @@ -132,6 +132,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o const secondaryServer = getServerInstance(serverOpts, httpHandler) const closeSecondary = () => { secondaryServer.close(() => {}) } + secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade')) mainServer.on('unref', closeSecondary) mainServer.on('close', closeSecondary) mainServer.on('error', closeSecondary) diff --git a/test/upgrade.test.js b/test/upgrade.test.js new file mode 100644 index 00000000000..fe727c7b2fd --- /dev/null +++ b/test/upgrade.test.js @@ -0,0 +1,53 @@ +'use strict' + +const { test, skip } = require('tap') +const { lookup } = require('dns').promises +const Fastify = require('..') +const { connect } = require('net') +const { once } = require('events') + +async function setup () { + const results = await lookup('localhost', { all: true }) + if (results.length === 1) { + skip('requires both IPv4 and IPv6') + return + } + + test('upgrade to both servers', async t => { + t.plan(2) + const app = Fastify() + app.server.on('upgrade', (req, socket, head) => { + t.pass(`upgrade event ${JSON.stringify(socket.address())}`) + socket.end() + }) + app.get('/', (req, res) => { + }) + await app.listen() + t.teardown(app.close.bind(app)) + + { + const client = connect(app.server.address().port, '127.0.0.1') + client.write('GET / HTTP/1.1\r\n') + client.write('Upgrade: websocket\r\n') + client.write('Connection: Upgrade\r\n') + client.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n') + client.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n') + client.write('Sec-WebSocket-Version: 13\r\n\r\n') + client.write('\r\n\r\n') + await once(client, 'close') + } + + { + const client = connect(app.server.address().port, '::1') + client.write('GET / HTTP/1.1\r\n') + client.write('Upgrade: websocket\r\n') + client.write('Connection: Upgrade\r\n') + client.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n') + client.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n') + client.write('Sec-WebSocket-Version: 13\r\n\r\n') + await once(client, 'close') + } + }) +} + +setup() From ea8aae960e81974f0e73d0ef2718ffb54923021b Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Wed, 10 Aug 2022 16:39:17 +0200 Subject: [PATCH 0036/1295] add unit test for ajv-formats (#4187) --- test/schema-examples.test.js | 54 ++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/schema-examples.test.js b/test/schema-examples.test.js index 5e5977f0ce3..cca8c4af1d9 100644 --- a/test/schema-examples.test.js +++ b/test/schema-examples.test.js @@ -509,6 +509,60 @@ test('should return custom error messages with ajv-errors', t => { }) }) +test('should be able to handle formats of ajv-formats when added by plugins option', t => { + t.plan(3) + + const fastify = Fastify({ + ajv: { + plugins: [ + require('ajv-formats') + ] + } + }) + + const schema = { + body: { + type: 'object', + properties: { + id: { type: 'string', format: 'uuid' }, + email: { type: 'string', format: 'email' } + }, + required: ['id', 'email'] + } + } + + fastify.post('/', { schema }, function (req, reply) { + reply.code(200).send(req.body.id) + }) + + fastify.inject({ + method: 'POST', + payload: { + id: '254381a5-888c-4b41-8116-e3b1a54980bd', + email: 'info@fastify.io' + }, + url: '/' + }, (_err, res) => { + t.equal(res.body, '254381a5-888c-4b41-8116-e3b1a54980bd') + t.equal(res.statusCode, 200) + }) + + fastify.inject({ + method: 'POST', + payload: { + id: 'invalid', + email: 'info@fastify.io' + }, + url: '/' + }, (_err, res) => { + t.same(JSON.parse(res.payload), { + statusCode: 400, + error: 'Bad Request', + message: 'body/id must match format "uuid"' + }) + }) +}) + test('should return localized error messages with ajv-i18n', t => { t.plan(3) From 4e797257001cce8c00a627b8c45f111f365b06a3 Mon Sep 17 00:00:00 2001 From: Philipp Viereck <105976309+philippviereck@users.noreply.github.com> Date: Thu, 11 Aug 2022 14:51:37 +0200 Subject: [PATCH 0037/1295] feat: disable/ignore request-id header (#4193) * add option to disable/ignore request-id header fixes #4192 * add more restrictive schema definition * update configValidator generated by running 'node build/build-validation.js' after change on 'build-validation.js' * move logic into reqIdGenFactory * update docs for requestHeaderId Adding documentation for opt-out of 'requestHeaderId' --- build/build-validation.js | 2 +- docs/Reference/Server.md | 13 +++- fastify.d.ts | 2 +- fastify.js | 4 +- lib/configValidator.js | 87 +++++++++++++++++++++------ lib/reqIdGenFactory.js | 15 ++++- lib/route.js | 4 +- test/logger.test.js | 108 ++++++++++++++++++++++++++++++++++ test/types/fastify.test-d.ts | 1 + test/types/instance.test-d.ts | 2 +- types/instance.d.ts | 2 +- 11 files changed, 207 insertions(+), 33 deletions(-) diff --git a/build/build-validation.js b/build/build-validation.js index 5439355ffe8..2ed914f69b1 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -97,7 +97,7 @@ const schema = { onProtoPoisoning: { type: 'string', default: defaultInitOptions.onProtoPoisoning }, onConstructorPoisoning: { type: 'string', default: defaultInitOptions.onConstructorPoisoning }, pluginTimeout: { type: 'integer', default: defaultInitOptions.pluginTimeout }, - requestIdHeader: { type: 'string', default: defaultInitOptions.requestIdHeader }, + requestIdHeader: { anyOf: [{ enum: [false] }, { type: 'string' }], default: defaultInitOptions.requestIdHeader }, requestIdLogLabel: { type: 'string', default: defaultInitOptions.requestIdLogLabel }, http2SessionTimeout: { type: 'integer', default: defaultInitOptions.http2SessionTimeout }, exposeHeadRoutes: { type: 'boolean', default: defaultInitOptions.exposeHeadRoutes }, diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 694f03e6b37..ed6cd73ac7e 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -55,7 +55,7 @@ describes the properties available in that options object. - [routing](#routing) - [route](#route) - [close](#close) - - [decorate\*](#decorate) + - [decorate*](#decorate) - [register](#register) - [addHook](#addhook) - [prefix](#prefix) @@ -488,11 +488,18 @@ about safe regexp: [Safe-regex2](https://www.npmjs.com/package/safe-regex2) ### `requestIdHeader` -The header name used to know the request-id. See [the +The header name used to set the request-id. See [the request-id](./Logging.md#logging-request-id) section. +Setting `requestIdHeader` to `false` will always use [genReqId](#genreqid) + Default: `'request-id'` - + +```js +const fastify = require('fastify')({ + requestIdHeader: 'x-custom-id', // -> use 'X-Custom-Id' header if available + //requestIdHeader: false, // -> always use genReqId +}) +``` ### `requestIdLogLabel` diff --git a/fastify.d.ts b/fastify.d.ts index 66312159d75..c708e179e8f 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -123,7 +123,7 @@ export type FastifyServerOptions< serializerOpts?: FJSOptions | Record, serverFactory?: FastifyServerFactory, caseSensitive?: boolean, - requestIdHeader?: string, + requestIdHeader?: string | false, requestIdLogLabel?: string; jsonShorthand?: boolean; genReqId?: (req: FastifyRequest, FastifySchema, TypeProvider>) => string, diff --git a/fastify.js b/fastify.js index 7e56a70500f..67509da5b1b 100644 --- a/fastify.js +++ b/fastify.js @@ -98,8 +98,8 @@ function fastify (options) { validateBodyLimitOption(options.bodyLimit) - const requestIdHeader = options.requestIdHeader || defaultInitOptions.requestIdHeader - const genReqId = options.genReqId || reqIdGenFactory() + const requestIdHeader = (options.requestIdHeader === false) ? false : (options.requestIdHeader || defaultInitOptions.requestIdHeader) + const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId) const requestIdLogLabel = options.requestIdLogLabel || 'reqId' const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit const disableRequestLogging = options.disableRequestLogging || false diff --git a/lib/configValidator.js b/lib/configValidator.js index 8d0e08715c5..f7ded6173c4 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -3,7 +3,7 @@ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"type":"string","default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -837,6 +837,23 @@ var valid0 = _errs55 === errors; if(valid0){ let data19 = data.requestIdHeader; const _errs57 = errors; +const _errs58 = errors; +let valid6 = false; +const _errs59 = errors; +if(!(data19 === false)){ +const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/enum",keyword:"enum",params:{allowedValues: schema11.properties.requestIdHeader.anyOf[0].enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err12]; +} +else { +vErrors.push(err12); +} +errors++; +} +var _valid3 = _errs59 === errors; +valid6 = valid6 || _valid3; +if(!valid6){ +const _errs60 = errors; if(typeof data19 !== "string"){ let dataType21 = typeof data19; let coerced21 = undefined; @@ -848,8 +865,14 @@ else if(data19 === null){ coerced21 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/type",keyword:"type",params:{type: "string"},message:"must be string"}]; -return false; +const err13 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err13]; +} +else { +vErrors.push(err13); +} +errors++; } } if(coerced21 !== undefined){ @@ -859,10 +882,36 @@ data["requestIdHeader"] = coerced21; } } } +var _valid3 = _errs60 === errors; +valid6 = valid6 || _valid3; +} +if(!valid6){ +const err14 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"}; +if(vErrors === null){ +vErrors = [err14]; +} +else { +vErrors.push(err14); +} +errors++; +validate10.errors = vErrors; +return false; +} +else { +errors = _errs58; +if(vErrors !== null){ +if(_errs58){ +vErrors.length = _errs58; +} +else { +vErrors = null; +} +} +} var valid0 = _errs57 === errors; if(valid0){ let data20 = data.requestIdLogLabel; -const _errs59 = errors; +const _errs62 = errors; if(typeof data20 !== "string"){ let dataType22 = typeof data20; let coerced22 = undefined; @@ -885,10 +934,10 @@ data["requestIdLogLabel"] = coerced22; } } } -var valid0 = _errs59 === errors; +var valid0 = _errs62 === errors; if(valid0){ let data21 = data.http2SessionTimeout; -const _errs61 = errors; +const _errs64 = errors; if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){ let dataType23 = typeof data21; let coerced23 = undefined; @@ -909,10 +958,10 @@ data["http2SessionTimeout"] = coerced23; } } } -var valid0 = _errs61 === errors; +var valid0 = _errs64 === errors; if(valid0){ let data22 = data.exposeHeadRoutes; -const _errs63 = errors; +const _errs66 = errors; if(typeof data22 !== "boolean"){ let coerced24 = undefined; if(!(coerced24 !== undefined)){ @@ -934,12 +983,12 @@ data["exposeHeadRoutes"] = coerced24; } } } -var valid0 = _errs63 === errors; +var valid0 = _errs66 === errors; if(valid0){ if(data.versioning !== undefined){ let data23 = data.versioning; -const _errs65 = errors; -if(errors === _errs65){ +const _errs68 = errors; +if(errors === _errs68){ if(data23 && typeof data23 == "object" && !Array.isArray(data23)){ let missing1; if(((data23.storage === undefined) && (missing1 = "storage")) || ((data23.deriveVersion === undefined) && (missing1 = "deriveVersion"))){ @@ -952,7 +1001,7 @@ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/prop return false; } } -var valid0 = _errs65 === errors; +var valid0 = _errs68 === errors; } else { var valid0 = true; @@ -960,13 +1009,13 @@ var valid0 = true; if(valid0){ if(data.constraints !== undefined){ let data24 = data.constraints; -const _errs68 = errors; -if(errors === _errs68){ +const _errs71 = errors; +if(errors === _errs71){ if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ for(const key2 in data24){ let data25 = data24[key2]; -const _errs71 = errors; -if(errors === _errs71){ +const _errs74 = errors; +if(errors === _errs74){ if(data25 && typeof data25 == "object" && !Array.isArray(data25)){ let missing2; if(((((data25.name === undefined) && (missing2 = "name")) || ((data25.storage === undefined) && (missing2 = "storage"))) || ((data25.validate === undefined) && (missing2 = "validate"))) || ((data25.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){ @@ -1006,8 +1055,8 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/ return false; } } -var valid6 = _errs71 === errors; -if(!valid6){ +var valid7 = _errs74 === errors; +if(!valid7){ break; } } @@ -1017,7 +1066,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs68 === errors; +var valid0 = _errs71 === errors; } else { var valid0 = true; diff --git a/lib/reqIdGenFactory.js b/lib/reqIdGenFactory.js index fc7e35ecdb3..c8c36d0bea1 100644 --- a/lib/reqIdGenFactory.js +++ b/lib/reqIdGenFactory.js @@ -1,6 +1,6 @@ 'use strict' -module.exports = function () { +module.exports = function (requestIdHeader, optGenReqId) { // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8). // With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days. // This is very likely to happen in real-world applications, hence the limit is enforced. @@ -8,8 +8,19 @@ module.exports = function () { // In the worst cases, it will become a float, losing accuracy. const maxInt = 2147483647 let nextReqId = 0 - return function genReqId (req) { + function defaultGenReqId (req) { nextReqId = (nextReqId + 1) & maxInt return `req-${nextReqId.toString(36)}` } + + const genReqId = optGenReqId || defaultGenReqId + + if (requestIdHeader) { + // requestIdHeader = typeof requestIdHeader === 'string' ? requestIdHeader : 'request-id' + return function (req) { + return req.headers[requestIdHeader] || genReqId(req) + } + } + + return genReqId } diff --git a/lib/route.js b/lib/route.js index 7559653c6ca..ef9f9209724 100644 --- a/lib/route.js +++ b/lib/route.js @@ -46,7 +46,6 @@ function buildRouting (options) { let avvio let fourOhFour - let requestIdHeader let requestIdLogLabel let logger let hasLogger @@ -74,7 +73,6 @@ function buildRouting (options) { validateHTTPVersion = fastifyArgs.validateHTTPVersion globalExposeHeadRoutes = options.exposeHeadRoutes - requestIdHeader = options.requestIdHeader requestIdLogLabel = options.requestIdLogLabel genReqId = options.genReqId disableRequestLogging = options.disableRequestLogging @@ -397,7 +395,7 @@ function buildRouting (options) { req.headers[kRequestAcceptVersion] = undefined } - const id = req.headers[requestIdHeader] || genReqId(req) + const id = genReqId(req) const loggerBinding = { [requestIdLogLabel]: id diff --git a/test/logger.test.js b/test/logger.test.js index c084c2f2e5f..48ba39a2035 100644 --- a/test/logger.test.js +++ b/test/logger.test.js @@ -330,6 +330,51 @@ test('The request id header key can be customized', t => { }) }) +test('The request id header key can be ignored', t => { + t.plan(9) + const REQUEST_ID = 'ignore-me' + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: false + }) + t.teardown(() => fastify.close()) + + fastify.get('/', (req, reply) => { + t.equal(req.id, 'req-1') + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'request-id': REQUEST_ID + } + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'req-1') + + stream.once('data', line => { + t.equal(line.reqId, 'req-1') + t.equal(line.msg, 'incoming request', 'message is set') + + stream.once('data', line => { + t.equal(line.reqId, 'req-1') + t.equal(line.msg, 'some log message', 'message is set') + + stream.once('data', line => { + t.equal(line.reqId, 'req-1') + t.equal(line.msg, 'request completed', 'message is set') + }) + }) + }) + }) +}) + test('The request id header key can be customized along with a custom id generator', t => { t.plan(12) const REQUEST_ID = '42' @@ -393,6 +438,69 @@ test('The request id header key can be customized along with a custom id generat }) }) +test('The request id header key can be ignored along with a custom id generator', t => { + t.plan(12) + const REQUEST_ID = 'ignore-me' + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: false, + genReqId (req) { + return 'foo' + } + }) + t.teardown(() => fastify.close()) + + fastify.get('/one', (req, reply) => { + t.equal(req.id, 'foo') + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + fastify.get('/two', (req, reply) => { + t.equal(req.id, 'foo') + req.log.info('some log message 2') + reply.send({ id: req.id }) + }) + + const matches = [ + { reqId: 'foo', msg: /incoming request/ }, + { reqId: 'foo', msg: /some log message/ }, + { reqId: 'foo', msg: /request completed/ }, + { reqId: 'foo', msg: /incoming request/ }, + { reqId: 'foo', msg: /some log message 2/ }, + { reqId: 'foo', msg: /request completed/ } + ] + + let i = 0 + stream.on('data', line => { + t.match(line, matches[i]) + i += 1 + }) + + fastify.inject({ + method: 'GET', + url: '/one', + headers: { + 'request-id': REQUEST_ID + } + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'foo') + }) + + fastify.inject({ + method: 'GET', + url: '/two' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'foo') + }) +}) + test('The request id log label can be changed', t => { t.plan(6) const REQUEST_ID = '42' diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 66c4d64d179..515d5c3b936 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -121,6 +121,7 @@ expectAssignable(fastify({ serverFactory: () => http.createServer() })) expectAssignable(fastify({ caseSensitive: true })) expectAssignable(fastify({ requestIdHeader: 'request-id' })) +expectAssignable(fastify({ requestIdHeader: false })) expectAssignable(fastify({ genReqId: () => 'request-id' })) expectAssignable(fastify({ trustProxy: true })) expectAssignable(fastify({ querystringParser: () => ({ foo: 'bar' }) })) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index a3dc17354dc..eadd16443f1 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -267,7 +267,7 @@ type InitialConfig = Readonly<{ onProtoPoisoning?: 'error' | 'remove' | 'ignore', onConstructorPoisoning?: 'error' | 'remove' | 'ignore', pluginTimeout?: number, - requestIdHeader?: string, + requestIdHeader?: string | false, requestIdLogLabel?: string, http2SessionTimeout?: number }> diff --git a/types/instance.d.ts b/types/instance.d.ts index a3fdef2afdd..94f13e50f33 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -573,7 +573,7 @@ export interface FastifyInstance< onProtoPoisoning?: ProtoAction, onConstructorPoisoning?: ConstructorAction, pluginTimeout?: number, - requestIdHeader?: string, + requestIdHeader?: string | false, requestIdLogLabel?: string, http2SessionTimeout?: number }> From 37ae4ea1f7490e8194e42b1bf85a9db60f048c57 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 13 Aug 2022 09:02:58 +0200 Subject: [PATCH 0038/1295] doc: add fastify.display-name symbol (#4191) * doc: add fastify.display-name symbol * fix: wording * Update docs/Reference/Server.md Co-authored-by: Simone Busoli * fix: lint Co-authored-by: Simone Busoli --- docs/Reference/Server.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index ed6cd73ac7e..bf529a0b4a5 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1119,12 +1119,15 @@ fastify.register(function (instance, opts, done) { Name of the current plugin. The root plugin is called `'fastify'`. There are -three ways to define a name (in order). +different ways to define a name (in order). 1. If you use [fastify-plugin](https://github.com/fastify/fastify-plugin) the metadata `name` is used. -2. If you `module.exports` a plugin the filename is used. -3. If you use a regular [function +2. If the exported plugin has the `Symbol.for('fastify.display-name')` property, + then the value of that property is used. + Example: `pluginFn[Symbol.for('fastify.display-name')] = "Custom Name"` +3. If you `module.exports` a plugin the filename is used. +4. If you use a regular [function declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Defining_functions) the function name is used. From 4b790fc3b4ed5d38b0b727d423b72a3fbebf028e Mon Sep 17 00:00:00 2001 From: Gali Armero Date: Sun, 14 Aug 2022 14:26:15 +0800 Subject: [PATCH 0039/1295] docs(validation-and-serialization): Fix example on query string coercion (#4198) --- docs/Reference/Validation-and-Serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 9732a85d648..9e29caa1b81 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -268,7 +268,7 @@ fastify.listen({ port: 3000 }, (err) => { ```sh curl -X GET "http://localhost:3000/?ids=1 -{"params":{"hello":["1"]}} +{"params":{"ids":["1"]}} ``` You can also specify a custom schema validator for each parameter type (body, From 5c95ad2fcbdc2859962d3c06040e28de59af6eec Mon Sep 17 00:00:00 2001 From: Matt Bishop Date: Tue, 16 Aug 2022 12:48:39 -0700 Subject: [PATCH 0040/1295] docs(ecosystem): add fastify-https-always (#4201) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index d514e251be2..d6085d23a60 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -328,6 +328,8 @@ section. - [`fastify-http2https`](https://github.com/lolo32/fastify-http2https) Redirect HTTP requests to HTTPS, both using the same port number, or different response on HTTP and HTTPS. +- [`fastify-https-always`](https://github.com/mattbishop/fastify-https-always) + Lightweight, proxy-aware redirect plugin from HTTP to HTTPS. - [`fastify-https-redirect`](https://github.com/tomsvogel/fastify-https-redirect) Fastify plugin for auto-redirect from HTTP to HTTPS. - [`fastify-impressions`](https://github.com/manju4ever/fastify-impressions) From 3af02e43be5a818a31282e36c9970912baaf1ac1 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 17 Aug 2022 13:05:38 +0200 Subject: [PATCH 0041/1295] Bumped v4.5.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 67509da5b1b..664de65d132 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.4.0' +const VERSION = '4.5.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 9510a42d7f3..9dfb993135b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.4.0", + "version": "4.5.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 2e5b82a612729378d4a296509108b19b21f40950 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 17 Aug 2022 21:04:41 +0200 Subject: [PATCH 0042/1295] make sure preSerialization hooks are null by default (#4203) Signed-off-by: Matteo Collina Signed-off-by: Matteo Collina --- lib/context.js | 1 + test/hooks-async.test.js | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/context.js b/lib/context.js index 4d3ad1c5f14..8fdc7d241b2 100644 --- a/lib/context.js +++ b/lib/context.js @@ -44,6 +44,7 @@ function Context ({ this.onTimeout = null this.preHandler = null this.onResponse = null + this.preSerialization = null this.config = config this.errorHandler = errorHandler || server[kErrorHandler] this._middie = null diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index c28ee9bc118..6240d8cd6be 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -710,3 +710,46 @@ test('preSerializationEnd should handle errors if the serialize method throws', t.end() }) + +t.test('nested hooks to do not crash on 404', t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/hello', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.register(async function (fastify) { + fastify.get('/something', (req, reply) => { + reply.callNotFound() + }) + + fastify.setNotFoundHandler(async (request, reply) => { + reply.statusCode = 404 + return { status: 'nested-not-found' } + }) + + fastify.setErrorHandler(async (error, request, reply) => { + reply.statusCode = 500 + return { status: 'nested-error', error } + }) + }, { prefix: '/nested' }) + + fastify.setNotFoundHandler(async (request, reply) => { + reply.statusCode = 404 + return { status: 'not-found' } + }) + + fastify.setErrorHandler(async (error, request, reply) => { + reply.statusCode = 500 + return { status: 'error', error } + }) + + fastify.inject({ + method: 'GET', + url: '/nested/something' + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 404) + }) +}) From 208d05b42b3240a6d2f6caf73f3296d85b6dfb0f Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 18 Aug 2022 14:45:18 +0200 Subject: [PATCH 0043/1295] Bumped v4.5.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 664de65d132..cc72b2f7f9f 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.5.0' +const VERSION = '4.5.1' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 9dfb993135b..5907199f63d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.5.0", + "version": "4.5.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 3fd294dbe998fd687c1e8d3f059f1603f77c1edc Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 18 Aug 2022 16:18:52 +0200 Subject: [PATCH 0044/1295] Fix 4204 (#4205) * set the errorHandler of the root 404 handler before starting Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * Update test/404s.test.js Co-authored-by: Uzlopak Signed-off-by: Matteo Collina Co-authored-by: Uzlopak --- lib/fourOhFour.js | 4 ++- test/404s.test.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/lib/fourOhFour.js b/lib/fourOhFour.js index e6e828046cc..b8ad4e73e4d 100644 --- a/lib/fourOhFour.js +++ b/lib/fourOhFour.js @@ -10,7 +10,8 @@ const { kCanSetNotFoundHandler, kFourOhFourLevelInstance, kFourOhFourContext, - kHooks + kHooks, + kErrorHandler } = require('./symbols.js') const { lifecycleHooks } = require('./hooks') const { buildErrorHandler } = require('./error-handler.js') @@ -148,6 +149,7 @@ function fourOhFour (options) { .map(h => h.bind(this)) context[hook] = toSet.length ? toSet : null } + context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler] }) if (this[kFourOhFourContext] !== null && prefix === '/') { diff --git a/test/404s.test.js b/test/404s.test.js index cb235f4367d..0c945c6806f 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -1944,3 +1944,68 @@ test('Send 404 when frameworkError calls reply.callNotFound', t => { t.end() }) + +test('hooks are applied to not found handlers /1', async ({ equal }) => { + const fastify = Fastify() + + // adding await here is fundamental for this test + await fastify.register(async function (fastify) { + }) + + fastify.setErrorHandler(function (_, request, reply) { + return reply.code(401).send({ error: 'Unauthorized' }) + }) + + fastify.addHook('preValidation', async function (request, reply) { + throw new Error('kaboom') + }) + + const { statusCode } = await fastify.inject('/') + equal(statusCode, 401) +}) + +test('hooks are applied to not found handlers /2', async ({ equal }) => { + const fastify = Fastify() + + async function plugin (fastify) { + fastify.setErrorHandler(function (_, request, reply) { + return reply.code(401).send({ error: 'Unauthorized' }) + }) + } + + plugin[Symbol.for('skip-override')] = true + + fastify.register(plugin) + + fastify.addHook('preValidation', async function (request, reply) { + throw new Error('kaboom') + }) + + const { statusCode } = await fastify.inject('/') + equal(statusCode, 401) +}) + +test('hooks are applied to not found handlers /3', async ({ equal, fail }) => { + const fastify = Fastify() + + async function plugin (fastify) { + fastify.setNotFoundHandler({ errorHandler }, async () => { + fail('this should never be called') + }) + + function errorHandler (_, request, reply) { + return reply.code(401).send({ error: 'Unauthorized' }) + } + } + + plugin[Symbol.for('skip-override')] = true + + fastify.register(plugin) + + fastify.addHook('preValidation', async function (request, reply) { + throw new Error('kaboom') + }) + + const { statusCode } = await fastify.inject('/') + equal(statusCode, 401) +}) From d3c3ad78060dd356ef174a45c9585fb36643db7e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 18 Aug 2022 16:26:39 +0200 Subject: [PATCH 0045/1295] Bumped v4.5.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index cc72b2f7f9f..da477628eb6 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.5.1' +const VERSION = '4.5.2' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 5907199f63d..c524a6a3b60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.5.1", + "version": "4.5.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 2c27e70478866b90225d8fe80d4cdc054932f2f8 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 19 Aug 2022 12:03:34 +0200 Subject: [PATCH 0046/1295] bumped v5.0.0-dev Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index da477628eb6..2da9ec7b5cc 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.5.2' +const VERSION = '5.0.0-dev' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index c524a6a3b60..24091b9ec85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.5.2", + "version": "5.0.0-dev", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 54d3ccdcd2f65c337156c10117cb81ae0e5f2ed9 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sun, 21 Aug 2022 23:49:26 +0200 Subject: [PATCH 0047/1295] move fastify-secure-session and fastify-soap-client to core plugins (#4212) --- docs/Guides/Ecosystem.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index d6085d23a60..f273b204bdb 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -102,11 +102,15 @@ section. - [`@fastify/schedule`](https://github.com/fastify/fastify-schedule) Plugin for scheduling periodic jobs, based on [toad-scheduler](https://github.com/kibertoad/toad-scheduler). +- [`@fastify/secure-session`](https://github.com/fastify/fastify-secure-session) + Create a secure stateless cookie session for Fastify. - [`@fastify/sensible`](https://github.com/fastify/fastify-sensible) Defaults for Fastify that everyone can agree on. It adds some useful decorators such as HTTP errors and assertions, but also more request and reply methods. - [`@fastify/session`](https://github.com/fastify/session) a session plugin for Fastify. +- [`@fastify/soap-client`](https://github.com/fastify/fastify-soap-client) a SOAP + client plugin for Fastify. - [`@fastify/static`](https://github.com/fastify/fastify-static) Plugin for serving static files as fast as possible. - [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) Plugin for @@ -485,8 +489,6 @@ section. - [`fastify-schema-to-typescript`](https://github.com/thomasthiebaud/fastify-schema-to-typescript) Generate typescript types based on your JSON/YAML validation schemas so they are always in sync. -- [`fastify-secure-session`](https://github.com/mcollina/fastify-secure-session) - Create a secure stateless cookie session for Fastify. - [`fastify-sentry`](https://github.com/alex-ppg/fastify-sentry) Fastify plugin to add the Sentry SDK error handler to requests. - [`fastify-sequelize`](https://github.com/lyquocnam/fastify-sequelize) Fastify @@ -497,8 +499,6 @@ section. `fastify-caching`. - [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik plugin, with this you can use slonik in every part of your server. -- [`fastify-soap-client`](https://github.com/fastify/fastify-soap-client) a SOAP - client plugin for Fastify. - [`fastify-socket.io`](https://github.com/alemagio/fastify-socket.io) a Socket.io plugin for Fastify. - [`fastify-split-validator`](https://github.com/MetCoder95/fastify-split-validator) From 3d156e0ed1f5e28c013a7dd78532fedb65672185 Mon Sep 17 00:00:00 2001 From: Simone Busoli Date: Mon, 22 Aug 2022 14:20:20 +0200 Subject: [PATCH 0048/1295] fix: inject hangs with undefined promise resolve (#4211) * repro: inject fails with undefined promise resolve * chore: bump light-my-request --- package.json | 2 +- test/async-await.test.js | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c524a6a3b60..432ce30c9fa 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "abstract-logging": "^2.0.1", "avvio": "^8.1.3", "find-my-way": "^7.0.0", - "light-my-request": "^5.0.0", + "light-my-request": "^5.5.1", "pino": "^8.0.0", "process-warning": "^2.0.0", "proxy-addr": "^2.0.7", diff --git a/test/async-await.test.js b/test/async-await.test.js index a946f5777d5..be40490a11d 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -423,6 +423,28 @@ test('promise was fulfilled with undefined', t => { }) }) +test('promise was fulfilled with undefined using inject', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'error' + } + }) + + fastify.get('/', async (req, reply) => { + }) + + stream.once('data', line => { + t.fail('should not log an error') + }) + + const res = await fastify.inject('/') + + t.equal(res.body, '') + t.equal(res.statusCode, 200) +}) + test('error is not logged because promise was fulfilled with undefined but response was sent before promise resolution', t => { t.plan(4) From cd20280bed489fe4cdae91b447088c31dcb87916 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 23 Aug 2022 09:45:18 +0200 Subject: [PATCH 0049/1295] use hasOwnProperty from Object.prototype (#4214) --- lib/decorate.js | 4 ++-- test/decorator.test.js | 2 +- test/internals/decorator.test.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/decorate.js b/lib/decorate.js index a688aee0c68..2181c1c4606 100644 --- a/lib/decorate.js +++ b/lib/decorate.js @@ -19,7 +19,7 @@ const { const warning = require('./warnings') function decorate (instance, name, fn, dependencies) { - if (instance.hasOwnProperty(name)) { + if (Object.prototype.hasOwnProperty.call(instance, name)) { throw new FST_ERR_DEC_ALREADY_PRESENT(name) } @@ -37,7 +37,7 @@ function decorate (instance, name, fn, dependencies) { function decorateConstructor (konstructor, name, fn, dependencies) { const instance = konstructor.prototype - if (instance.hasOwnProperty(name) || hasKey(konstructor, name)) { + if (Object.prototype.hasOwnProperty.call(instance, name) || hasKey(konstructor, name)) { throw new FST_ERR_DEC_ALREADY_PRESENT(name) } diff --git a/test/decorator.test.js b/test/decorator.test.js index 88ab6888714..d36e7568397 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -642,7 +642,7 @@ test('should register empty values', t => { fastify.register((instance, opts, done) => { instance.decorate('test', null) - t.ok(instance.hasOwnProperty('test')) + t.ok(Object.prototype.hasOwnProperty.call(instance, 'test')) done() }) diff --git a/test/internals/decorator.test.js b/test/internals/decorator.test.js index adadac1c2d2..db0b207667e 100644 --- a/test/internals/decorator.test.js +++ b/test/internals/decorator.test.js @@ -138,7 +138,7 @@ test('decorate should recognize getter/setter objects', t => { this._a = val } }) - t.equal(one.hasOwnProperty('foo'), true) + t.equal(Object.prototype.hasOwnProperty.call(one, 'foo'), true) t.equal(one.foo, undefined) one.foo = 'a' t.equal(one.foo, 'a') @@ -154,6 +154,6 @@ test('decorate should recognize getter/setter objects', t => { decorator.add.call(two, 'foo', { getter: () => 'a getter' }) - t.equal(two.hasOwnProperty('foo'), true) + t.equal(Object.prototype.hasOwnProperty.call(two, 'foo'), true) t.equal(two.foo, 'a getter') }) From 1e5a62d30e654557419170ddceeaf902a9821b40 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Wed, 24 Aug 2022 19:14:36 +0200 Subject: [PATCH 0050/1295] chore: fastify branch list (#4218) --- CONTRIBUTING.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a8dd4552b6..68d1a8b5a68 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,17 +36,19 @@ There are a few basic ground-rules for contributors: 1. In case it is not possible to reach consensus in a pull-request, the decision is left to the lead maintainer's team. -### Fastify v1.x +### Fastify previous versions -Code for Fastify's **v1.x** is in [branch -1.x](https://github.com/fastify/fastify/tree/1.x), so all Fastify 1.x related -changes should be based on **`branch 1.x`**. +Every Fastify's version is on its own branch. All Fastify related +changes should be based on the corresponding branch. -### Fastify v2.x +We have a [Long Term Support](./docs/Reference/LTS.md) policy that defines +the organization efforts for each Fastify's version. -Code for Fastify's **v2.x** is in [branch -2.x](https://github.com/fastify/fastify/tree/2.x), so all Fastify 2.x related -changes should be based on **`branch 2.x`**. +|Version|Branch| +|-------|------| +**v1.x**|[branch 1.x](https://github.com/fastify/fastify/tree/1.x)| +**v2.x**|[branch 2.x](https://github.com/fastify/fastify/tree/2.x)| +**v3.x**|[branch 3.x](https://github.com/fastify/fastify/tree/3.x)| ## Releases From cf29428ff7630877598de5fd4eea3ccf435e14b6 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Fri, 26 Aug 2022 12:20:56 +0200 Subject: [PATCH 0051/1295] chore: add support for TypeScript 4.8 (#4222) --- types/instance.d.ts | 2 +- types/route.d.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/types/instance.d.ts b/types/instance.d.ts index 94f13e50f33..6db78ddd618 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -174,7 +174,7 @@ export interface FastifyInstance< route< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, - SchemaCompiler = FastifySchema, + SchemaCompiler extends FastifySchema = FastifySchema, >(opts: RouteOptions): FastifyInstance; get: RouteShorthandMethod; diff --git a/types/route.d.ts b/types/route.d.ts index a76659f5a00..bb58ede4788 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -24,7 +24,7 @@ export interface RouteShorthandOptions< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, - SchemaCompiler = FastifySchema, + SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > { @@ -84,7 +84,7 @@ export interface RouteShorthandOptionsWithHandler< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, - SchemaCompiler = FastifySchema, + SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > extends RouteShorthandOptions { @@ -100,16 +100,16 @@ export interface RouteShorthandMethod< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { - ( + ( path: string, opts: RouteShorthandOptions, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, opts: RouteShorthandOptionsWithHandler ): FastifyInstance; @@ -124,7 +124,7 @@ export interface RouteOptions< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, - SchemaCompiler = FastifySchema, + SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyLoggerInstance = FastifyLoggerInstance > extends RouteShorthandOptions { From 0824881947ca7ccf9a947ced2dc539630aba47d5 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 26 Aug 2022 12:24:55 +0200 Subject: [PATCH 0052/1295] Bumped v4.5.3 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index da477628eb6..3f6750e6982 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.5.2' +const VERSION = '4.5.3' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 432ce30c9fa..e21946438fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.5.2", + "version": "4.5.3", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 1a7acfd92b414c988f59d043242fd15774910e8f Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sat, 27 Aug 2022 11:00:10 +0200 Subject: [PATCH 0053/1295] replace FastifyLoggerInstance occurences in typings with FastifyBaseLogger (#4224) --- types/hooks.d.ts | 50 ++++++++++++++++++++++----------------------- types/instance.d.ts | 42 ++++++++++++++++++------------------- types/logger.d.ts | 2 +- types/plugin.d.ts | 6 +++--- types/reply.d.ts | 4 ++-- types/request.d.ts | 4 ++-- types/route.d.ts | 18 ++++++++-------- 7 files changed, 63 insertions(+), 63 deletions(-) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 54f8f634c33..03efbf8f368 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -5,7 +5,7 @@ import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyD import { FastifyRequest } from './request' import { FastifyReply } from './reply' import { FastifyError } from '@fastify/error' -import { FastifyLoggerInstance } from './logger' +import { FastifyBaseLogger } from './logger' import { FastifyTypeProvider, FastifyTypeProviderDefault @@ -34,7 +34,7 @@ export interface onRequestHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -52,7 +52,7 @@ export interface onRequestAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -73,7 +73,7 @@ export interface preParsingHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -92,7 +92,7 @@ export interface preParsingAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -113,7 +113,7 @@ export interface preValidationHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -131,7 +131,7 @@ export interface preValidationAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -151,7 +151,7 @@ export interface preHandlerHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -169,7 +169,7 @@ export interface preHandlerAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -198,7 +198,7 @@ export interface preSerializationHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -218,7 +218,7 @@ export interface preSerializationAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -241,7 +241,7 @@ export interface onSendHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -261,7 +261,7 @@ export interface onSendAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -283,7 +283,7 @@ export interface onResponseHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -301,7 +301,7 @@ export interface onResponseAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -322,7 +322,7 @@ export interface onTimeoutHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -340,7 +340,7 @@ export interface onTimeoutAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -364,7 +364,7 @@ export interface onErrorHookHandler< TError extends Error = FastifyError, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -384,7 +384,7 @@ export interface onErrorAsyncHookHandler< TError extends Error = FastifyError, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -407,7 +407,7 @@ export interface onRouteHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -424,7 +424,7 @@ export interface onRegisterHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Options extends FastifyPluginOptions = FastifyPluginOptions > { @@ -442,7 +442,7 @@ export interface onReadyHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -455,7 +455,7 @@ export interface onReadyAsyncHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -469,7 +469,7 @@ export interface onCloseHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -482,7 +482,7 @@ export interface onCloseAsyncHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( diff --git a/types/instance.d.ts b/types/instance.d.ts index 6db78ddd618..10693669c6d 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -4,7 +4,7 @@ import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' -import { FastifyLoggerInstance } from './logger' +import { FastifyBaseLogger } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' import { FastifyRequest } from './request' @@ -84,7 +84,7 @@ export interface FastifyInstance< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { server: RawServer; @@ -198,7 +198,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onRequest', hook: onRequestHookHandler @@ -208,7 +208,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onRequest', hook: onRequestAsyncHookHandler @@ -222,7 +222,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preParsing', hook: preParsingHookHandler @@ -232,7 +232,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preParsing', hook: preParsingAsyncHookHandler @@ -245,7 +245,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preValidation', hook: preValidationHookHandler @@ -255,7 +255,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preValidation', hook: preValidationAsyncHookHandler @@ -268,7 +268,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preHandler', hook: preHandlerHookHandler @@ -278,7 +278,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preHandler', hook: preHandlerAsyncHookHandler @@ -293,7 +293,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preSerialization', hook: preSerializationHookHandler @@ -304,7 +304,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preSerialization', hook: preSerializationAsyncHookHandler @@ -319,7 +319,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onSend', hook: onSendHookHandler @@ -330,7 +330,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onSend', hook: onSendAsyncHookHandler @@ -344,7 +344,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onResponse', hook: onResponseHookHandler @@ -354,7 +354,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onResponse', hook: onResponseAsyncHookHandler @@ -368,7 +368,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onTimeout', hook: onTimeoutHookHandler @@ -378,7 +378,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onTimeout', hook: onTimeoutAsyncHookHandler @@ -394,7 +394,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onError', hook: onErrorHookHandler @@ -404,7 +404,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onError', hook: onErrorAsyncHookHandler @@ -419,7 +419,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onRoute', hook: onRouteHookHandler diff --git a/types/logger.d.ts b/types/logger.d.ts index d25f01da424..5fccf5b9c19 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -23,7 +23,7 @@ export type FastifyBaseLogger = pino.BaseLogger & { child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger } -// TODO delete FastifyLoggerInstance in the next major release. It seems that it is enough to have only FastifyBaseLogger. +// TODO delete FastifyBaseLogger in the next major release. It seems that it is enough to have only FastifyBaseLogger. /** * @deprecated Use FastifyBaseLogger instead */ diff --git a/types/plugin.d.ts b/types/plugin.d.ts index 302422b57f6..77bce4e1182 100644 --- a/types/plugin.d.ts +++ b/types/plugin.d.ts @@ -1,7 +1,7 @@ import { FastifyInstance } from './instance' import { RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault } from './utils' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' -import { FastifyLoggerInstance } from './logger' +import { FastifyBaseLogger } from './logger' export type FastifyPluginOptions = Record @@ -11,7 +11,7 @@ export type FastifyPluginOptions = Record * 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. */ export type FastifyPluginCallback, Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> = ( - instance: FastifyInstance, RawReplyDefaultExpression, FastifyLoggerInstance, TypeProvider>, + instance: FastifyInstance, RawReplyDefaultExpression, FastifyBaseLogger, TypeProvider>, opts: Options, done: (err?: Error) => void ) => void @@ -26,7 +26,7 @@ export type FastifyPluginAsync< Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > = ( - instance: FastifyInstance, RawReplyDefaultExpression, FastifyLoggerInstance, TypeProvider>, + instance: FastifyInstance, RawReplyDefaultExpression, FastifyBaseLogger, TypeProvider>, opts: Options ) => Promise; diff --git a/types/reply.d.ts b/types/reply.d.ts index 8b637160022..5cc08f41d8e 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -1,7 +1,7 @@ import { RawReplyDefaultExpression, RawServerBase, RawServerDefault, ContextConfigDefault, RawRequestDefaultExpression, ReplyDefault } from './utils' import { FastifyReplyType, ResolveFastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' import { FastifyContext } from './context' -import { FastifyLoggerInstance } from './logger' +import { FastifyBaseLogger } from './logger' import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' import { FastifyInstance } from './instance' @@ -28,7 +28,7 @@ export interface FastifyReply< > { raw: RawReply; context: FastifyContext; - log: FastifyLoggerInstance; + log: FastifyBaseLogger; request: FastifyRequest; server: FastifyInstance; code(statusCode: number): FastifyReply; diff --git a/types/request.d.ts b/types/request.d.ts index 35418ef2a40..50ca80c4ef0 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,4 +1,4 @@ -import { FastifyLoggerInstance } from './logger' +import { FastifyBaseLogger } from './logger' import { ContextConfigDefault, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './utils' import { RouteGenericInterface } from './route' import { FastifyInstance } from './instance' @@ -24,7 +24,7 @@ export interface FastifyRequest // ^ Temporary Note: RequestType has been re-ordered to be the last argument in // generic list. This generic argument is now considered optional as it can be diff --git a/types/route.d.ts b/types/route.d.ts index bb58ede4788..f8d2675cb43 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -11,7 +11,7 @@ import { FastifyTypeProviderDefault, ResolveFastifyReplyReturnType } from './type-provider' -import { FastifyLoggerInstance, LogLevel } from './logger' +import { FastifyBaseLogger, LogLevel } from './logger' export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface {} @@ -26,7 +26,7 @@ export interface RouteShorthandOptions< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > { schema?: SchemaCompiler, // originally FastifySchema attachValidation?: boolean; @@ -67,7 +67,7 @@ export type RouteHandlerMethod< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > = ( this: FastifyInstance, request: FastifyRequest, @@ -86,7 +86,7 @@ export interface RouteShorthandOptionsWithHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > extends RouteShorthandOptions { handler: RouteHandlerMethod; } @@ -100,16 +100,16 @@ export interface RouteShorthandMethod< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { - ( + ( path: string, opts: RouteShorthandOptions, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, opts: RouteShorthandOptionsWithHandler ): FastifyInstance; @@ -126,7 +126,7 @@ export interface RouteOptions< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > extends RouteShorthandOptions { method: HTTPMethods | HTTPMethods[]; url: string; @@ -141,7 +141,7 @@ export type RouteHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyLoggerInstance = FastifyLoggerInstance + Logger extends FastifyBaseLogger = FastifyBaseLogger > = ( this: FastifyInstance, request: FastifyRequest, From 20adbbff927ce012abf2a2dab994063b2586dc85 Mon Sep 17 00:00:00 2001 From: Superchupu <53496941+SuperchupuDev@users.noreply.github.com> Date: Sat, 27 Aug 2022 18:54:58 +0100 Subject: [PATCH 0054/1295] fix(types): allow `fastify.https` to be `null` (#4226) --- fastify.d.ts | 2 +- test/types/fastify.test-d.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/fastify.d.ts b/fastify.d.ts index c708e179e8f..76a3895e04f 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -84,7 +84,7 @@ export type FastifyHttpsOptions< Server extends https.Server, Logger extends FastifyBaseLogger = FastifyLoggerInstance > = FastifyServerOptions & { - https: https.ServerOptions + https: https.ServerOptions | null } type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2 diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 515d5c3b936..750e4d25472 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -24,6 +24,7 @@ expectType & PromiseLike>>(fastify({})) // https server expectType & PromiseLike>>(fastify({ https: {} })) +expectType & PromiseLike>>(fastify({ https: null })) // http2 server expectType & PromiseLike>>(fastify({ http2: true, http2SessionTimeout: 1000 })) expectType & PromiseLike>>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 })) From a3433a238df8cf5c07cc881a691c6518058dbf14 Mon Sep 17 00:00:00 2001 From: YoungTaek Date: Sun, 28 Aug 2022 03:01:38 +0900 Subject: [PATCH 0055/1295] chore: fix typo in docs for typescript chapter (#4227) --- docs/Reference/TypeScript.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 9143de1c6eb..9b374e7ebb0 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -1237,8 +1237,8 @@ const plugin: FastifyPlugin<{ option2: boolean; }> = function (instance, opts, done) { } -fastify().register(plugin, {}) // Error - options object is missing required properties -fastify().register(plugin, { option1: '', option2: true }) // OK - options object contains required properties +server().register(plugin, {}) // Error - options object is missing required properties +server().register(plugin, { option1: '', option2: true }) // OK - options object contains required properties ``` See the Learn By Example, [Plugins](#plugins) section for more detailed examples From 72fe4d8402531e1eb862facb439a9eee9e08c1b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 14:19:01 +0000 Subject: [PATCH 0056/1295] build(deps-dev): bump tsd from 0.22.0 to 0.23.0 (#4231) Bumps [tsd](https://github.com/SamVerschueren/tsd) from 0.22.0 to 0.23.0. - [Release notes](https://github.com/SamVerschueren/tsd/releases) - [Commits](https://github.com/SamVerschueren/tsd/compare/v0.22.0...v0.23.0) --- updated-dependencies: - dependency-name: tsd dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e21946438fa..e9b46a1578d 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,7 @@ "split2": "^4.1.0", "standard": "^17.0.0-2", "tap": "^16.2.0", - "tsd": "^0.22.0", + "tsd": "^0.23.0", "typescript": "^4.7.2", "undici": "^5.4.0", "vary": "^1.1.2", From e2165c7b31c3ca4f8ec8899858ace47493b01638 Mon Sep 17 00:00:00 2001 From: Leandro Andrade Date: Mon, 29 Aug 2022 15:08:55 -0300 Subject: [PATCH 0057/1295] chore: update dependabot link (#4232) --- docs/Guides/Write-Plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Write-Plugin.md b/docs/Guides/Write-Plugin.md index 5d161b478fa..8cde23730b2 100644 --- a/docs/Guides/Write-Plugin.md +++ b/docs/Guides/Write-Plugin.md @@ -80,7 +80,7 @@ to show that the plugin works as intended. Both Actions](https://github.com/features/actions) are free for open source projects and easy to set up. -In addition, you can enable services like [Dependabot](https://dependabot.com/), +In addition, you can enable services like [Dependabot](https://github.com/dependabot), which will help you keep your dependencies up to date and discover if a new release of Fastify has some issues with your plugin. From 67bee117a7c5c6dad503f656204e49cf329a37af Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Tue, 30 Aug 2022 13:57:04 +0200 Subject: [PATCH 0058/1295] docs: improved migration guide for onRoute (#4233) * docs: improved migration guide. * docs: have consistent examples. --- docs/Guides/Migration-Guide-V4.md | 68 +++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 9968d49709f..83fe25bd831 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -71,28 +71,60 @@ The route registration has been made synchronous from v4. This change was done to provide better error reporting for route definition. As a result if you specify an `onRoute` hook in a plugin you should either: * wrap your routes in a plugin (recommended) + + For example refactor this: + + ``` + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', (routeOptions) => { + const { path, method } = routeOptions; + console.log({ path, method }); + done(); + }); + }); + + fastify.get('/', () => 'hello'); + ``` + + Into this: + + ``` + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', (routeOptions) => { + const { path, method } = routeOptions; + console.log({ path, method }); + done(); + }); + }); + + fastify.register((instance, opts, done) => { + instance.get('/', () => 'hello'); + done(); + }); + ``` + * use `await register(...)` -For example refactor this: -``` -fastify.register((instance, opts, done) => { - instance.addHook('onRoute', (routeOptions) => { - const { path, method } = routeOptions; - console.log({ path, method }); + For example refactor this: + ``` + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', (routeOptions) => { + const { path, method } = routeOptions; + console.log({ path, method }); + }); + done(); }); - done(); -}); -``` -Into this: -``` -await fastify.register((instance, opts, done) => { - instance.addHook('onRoute', (routeOptions) => { - const { path, method } = routeOptions; - console.log({ path, method }); + ``` + Into this: + ``` + await fastify.register((instance, opts, done) => { + instance.addHook('onRoute', (routeOptions) => { + const { path, method } = routeOptions; + console.log({ path, method }); + }); + done(); }); - done(); -}); -``` + ``` ## Non Breaking Changes From adeb2edf909d2457077e9315d2491f434d603719 Mon Sep 17 00:00:00 2001 From: Jacob Pargin Date: Wed, 31 Aug 2022 14:23:15 +0100 Subject: [PATCH 0059/1295] docs: clarify setDefaultRoute scope (#4236) * docs: clarify setDefaultRoute scope * Update docs/Reference/Server.md Co-authored-by: Uzlopak Co-authored-by: Frazer Smith Co-authored-by: Uzlopak --- docs/Reference/Server.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index bf529a0b4a5..c998f7f6cf6 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1020,7 +1020,8 @@ const defaultRoute = fastify.getDefaultRoute() **Note**: The default 404 handler, or one set using `setNotFoundHandler`, will -never trigger if the default route is overridden. Use +never trigger if the default route is overridden. This sets the handler for the +Fastify application, not just the current instance context. Use [setNotFoundHandler](#setnotfoundhandler) if you want to customize 404 handling instead. Method to set the `defaultRoute` for the server: From 6f6f7aa2d069a506b69ca42fbecef170ee667d4b Mon Sep 17 00:00:00 2001 From: Giuseppe Zileni <6775950+gzileni@users.noreply.github.com> Date: Wed, 31 Aug 2022 17:15:14 +0200 Subject: [PATCH 0060/1295] docs(ecosystem): add fastify-aws-timestream and fastify-aws-sns (#4230) --- docs/Guides/Ecosystem.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f273b204bdb..a3790728f93 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -218,6 +218,12 @@ section. directory. - [`fastify-axios`](https://github.com/davidedantonio/fastify-axios) Plugin to send HTTP requests via [axios](https://github.com/axios/axios). +- [fastify-aws-timestream](https://github.com/gzileni/fastify-aws-timestream) + Fastify plugin for managing databases, tables, and querying and creating + scheduled queries with AWS Timestream. +- [fastify-aws-sns](https://github.com/gzileni/fastify-aws-sns) Fastify plugin + for AWS Simple Notification Service (AWS SNS) that coordinates and manages + the delivery or sending of messages to subscribing endpoints or clients. - [`fastify-babel`](https://github.com/cfware/fastify-babel) Fastify plugin for development servers that require Babel transformations of JavaScript sources. - [`fastify-bcrypt`](https://github.com/heply/fastify-bcrypt) A Bcrypt hash From 2fd918298d55aaab74585c0b71081ad40988b280 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 1 Sep 2022 11:53:23 +0100 Subject: [PATCH 0061/1295] docs(guides/migration-guide-v4): update content (#4242) * docs(guides/migration-guide-v4): add lang identifiers to code blocks * docs(guides/migration-guide-v4): general tidy and sentence restructure * docs(guides/migration-guide-v4): add deprecation of variadic listen sig * docs(guides/migration-guide-v4): fix subheading; deprecation !== removal * docs(guides/migration-guide-v4): remove sync async handler examples * docs(guides/migration-guide-v4): tidy schema section * docs(guides/migration-guide-v4): fix linting * docs(guides/migration-guide-v4): line squash --- docs/Guides/Migration-Guide-V4.md | 99 ++++++++++++++++++------------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 83fe25bd831..4efc3ce923b 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -10,12 +10,11 @@ work after upgrading. ### Error handling composition ([#3261](https://github.com/fastify/fastify/pull/3261)) -When an error is thrown in a async error handler function, -the upper-level error handler is executed if set. -If there is not a upper-level error handler, the default will -be executed as it was previously. +When an error is thrown in an async error handler function, the upper-level +error handler is executed if set. If there is no upper-level error handler, +the default will be executed as it was previously: -``` +```js import Fastify from 'fastify' const fastify = Fastify() @@ -40,41 +39,41 @@ const res = await fastify.inject('/encapsulated') console.log(res.json().message) // 'wrapped' ``` -### Deprecation of `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506)) +### Removed `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506)) + +With v4 of Fastify, `app.use()` has been removed and the use of middleware is +no longer supported. -Starting this version of Fastify, we have deprecated the use of `app.use()`. We -have decided not to support the use of middlewares. Both -[`@fastify/middie`](https://github.com/fastify/middie) and -[`@fastify/express`](https://github.com/fastify/fastify-express) will still be -there and maintained. Use Fastify's [hooks](../Reference/Hooks.md) instead. +If you need to use middleware, use +[`@fastify/middie`](https://github.com/fastify/middie) or +[`@fastify/express`](https://github.com/fastify/fastify-express), which will +continue to be maintained. +However, it is strongly recommended that you migrate to Fastify's [hooks](../Reference/Hooks.md). ### `reply.res` moved to `reply.raw` If you previously used the `reply.res` attribute to access the underlying Request -object you'll instead need to depend on `reply.raw`. +object you will now need to use `reply.raw`. ### Need to `return reply` to signal a "fork" of the promise chain -In some situations, like when a response is sent asynchronously or when you're -just not explicitly returning a response, you'll need to return the `reply` +In some situations, like when a response is sent asynchronously or when you are +not explicitly returning a response, you will now need to return the `reply` argument from your router handler. ### `exposeHeadRoutes` true by default -Starting from v4, all the `GET` routes will create a sibling `HEAD` route. -You can revert this behaviour by setting the server's option `exposeHeadRoutes` -to `false`. +Starting with v4, every `GET` route will create a sibling `HEAD` route. +You can revert this behavior by setting `exposeHeadRoutes: false` in the server options. -### Synchronous route definitions +### Synchronous route definitions ([#2954](https://github.com/fastify/fastify/pull/2954)) -The route registration has been made synchronous from v4. -This change was done to provide better error reporting for route definition. -As a result if you specify an `onRoute` hook in a plugin you should either: +To improve error reporting in route definitions, route registration is now synchronous. +As a result, if you specify an `onRoute` hook in a plugin you should now either: * wrap your routes in a plugin (recommended) - For example refactor this: - - ``` + For example, refactor this: + ```js fastify.register((instance, opts, done) => { instance.addHook('onRoute', (routeOptions) => { const { path, method } = routeOptions; @@ -83,12 +82,11 @@ As a result if you specify an `onRoute` hook in a plugin you should either: }); }); - fastify.get('/', () => 'hello'); + fastify.get('/', (request, reply) => { reply.send('hello') }); ``` Into this: - - ``` + ```js fastify.register((instance, opts, done) => { instance.addHook('onRoute', (routeOptions) => { const { path, method } = routeOptions; @@ -98,15 +96,15 @@ As a result if you specify an `onRoute` hook in a plugin you should either: }); fastify.register((instance, opts, done) => { - instance.get('/', () => 'hello'); + instance.get('/', (request, reply) => { reply.send('hello') }); done(); }); ``` * use `await register(...)` - For example refactor this: - ``` + For example, refactor this: + ```js fastify.register((instance, opts, done) => { instance.addHook('onRoute', (routeOptions) => { const { path, method } = routeOptions; @@ -115,9 +113,10 @@ As a result if you specify an `onRoute` hook in a plugin you should either: done(); }); ``` + Into this: - ``` - await fastify.register((instance, opts, done) => { + ```js + await fastify.register((instance, opts) => { instance.addHook('onRoute', (routeOptions) => { const { path, method } = routeOptions; console.log({ path, method }); @@ -126,21 +125,39 @@ As a result if you specify an `onRoute` hook in a plugin you should either: }); ``` -## Non Breaking Changes +## Non-Breaking Changes -### Change of schema for multiple types +### Deprecation of variadic `.listen()` signature +The [variadic signature](https://en.wikipedia.org/wiki/Variadic_function) of the +`fastify.listen()` method is now deprecated. -Since Fastify v4 has upgraded to Ajv v8. The "type" keywords with multiple types -(other than with "null") are prohibited. Read more -['here'](https://ajv.js.org/strict-mode.html#strict-types) +Prior to this release, the following invocations of this method were valid: -You may encounter a console warning such as + - `fastify.listen(8000)` + - `fastify.listen(8000, ‘127.0.0.1’)` + - `fastify.listen(8000, ‘127.0.0.1’, 511)` + - `fastify.listen(8000, (err) => { if (err) throw err })` + - `fastify.listen({ port: 8000 }, (err) => { if (err) throw err })` -``` +With Fastify v4, only the following invocations are valid: + + - `fastify.listen()` + - `fastify.listen({ port: 8000 })` + - `fastify.listen({ port: 8000 }, (err) => { if (err) throw err })` + +### Change of schema for multiple types + +Ajv has been upgraded to v8 in Fastify v4, meaning "type" keywords with multiple +types other than "null" +[are now prohibited](https://ajv.js.org/strict-mode.html#strict-types). + +You may encounter a console warning such as: +```sh strict mode: use allowUnionTypes to allow union type keyword at "#/properties/image" (strictTypes) ``` -So schemas like below will need to be changed from + +As such, schemas like below will need to be changed from: ``` type: 'object', properties: { @@ -149,8 +166,8 @@ properties: { } } ``` -to +Into: ``` type: 'object', properties: { From 29a91952879221b03e6c28cd7cf01c12a8949066 Mon Sep 17 00:00:00 2001 From: Giacomo Rebonato Date: Thu, 1 Sep 2022 14:25:04 +0100 Subject: [PATCH 0062/1295] Improve doc about configuring pino-pretty with TypeScript (#4243) * document logging config for ts * best practice * Update docs/Reference/Logging.md Co-authored-by: Uzlopak Co-authored-by: Uzlopak --- docs/Reference/Logging.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 060b4dfab70..486b62485a8 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -22,21 +22,24 @@ const fastify = require('fastify')({ ``` Enabling the logger with appropriate configuration for both local development -and production environment requires bit more configuration: +and production and test environment requires bit more configuration: + ```js +const envToLogger = { + development: { + transport: { + target: 'pino-pretty', + options: { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname', + }, + }, + }, + production: true, + test: false, +} const fastify = require('fastify')({ - logger: { - transport: - environment === 'development' - ? { - target: 'pino-pretty', - options: { - translateTime: 'HH:MM:ss Z', - ignore: 'pid,hostname' - } - } - : undefined - } + logger: envToLogger[environment] ?? true // defaults to true if no entry matches in the map }) ``` ⚠️ `pino-pretty` needs to be installed as a dev dependency, it is not included From d60a18c26d36304c45192525e8f410d6927c8ff4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 14:12:21 +0000 Subject: [PATCH 0063/1295] build(deps): bump jsumners/lock-threads (#4244) Bumps [jsumners/lock-threads](https://github.com/jsumners/lock-threads) from b27edac0ac998d42b2815e122b6c24b32b568321 to 3. This release includes the previously tagged commit. - [Release notes](https://github.com/jsumners/lock-threads/releases) - [Changelog](https://github.com/jsumners/lock-threads/blob/master/CHANGELOG.md) - [Commits](https://github.com/jsumners/lock-threads/compare/b27edac0ac998d42b2815e122b6c24b32b568321...e460dfeb36e731f3aeb214be6b0c9a9d9a67eda6) --- updated-dependencies: - dependency-name: jsumners/lock-threads dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lock-threads.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml index 416efa98817..b76b1ba1f2e 100644 --- a/.github/workflows/lock-threads.yml +++ b/.github/workflows/lock-threads.yml @@ -16,7 +16,7 @@ jobs: action: runs-on: ubuntu-latest steps: - - uses: jsumners/lock-threads@b27edac0ac998d42b2815e122b6c24b32b568321 + - uses: jsumners/lock-threads@e460dfeb36e731f3aeb214be6b0c9a9d9a67eda6 with: issue-inactive-days: '90' exclude-any-issue-labels: 'discussion,good first issue,help wanted' From 65899b18c7e7bc255a48162ab418e85bfb94ebaf Mon Sep 17 00:00:00 2001 From: KaKa Date: Thu, 1 Sep 2022 22:47:46 +0800 Subject: [PATCH 0064/1295] Revert "build(deps): bump jsumners/lock-threads (#4244)" (#4245) This reverts commit d60a18c26d36304c45192525e8f410d6927c8ff4. --- .github/workflows/lock-threads.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml index b76b1ba1f2e..416efa98817 100644 --- a/.github/workflows/lock-threads.yml +++ b/.github/workflows/lock-threads.yml @@ -16,7 +16,7 @@ jobs: action: runs-on: ubuntu-latest steps: - - uses: jsumners/lock-threads@e460dfeb36e731f3aeb214be6b0c9a9d9a67eda6 + - uses: jsumners/lock-threads@b27edac0ac998d42b2815e122b6c24b32b568321 with: issue-inactive-days: '90' exclude-any-issue-labels: 'discussion,good first issue,help wanted' From 8971520989d771deebe5ee4e5c8a65adcdd66288 Mon Sep 17 00:00:00 2001 From: Max Smolens Date: Sat, 3 Sep 2022 00:09:01 -0700 Subject: [PATCH 0065/1295] Remove Ajv configuration from TypeBox Type Provider examples (#4249) --- docs/Reference/Type-Providers.md | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 3ec4215686d..4cfe8dcd32d 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -155,14 +155,7 @@ import Fastify from 'fastify' import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox' import { Type } from '@sinclair/typebox' -const server = Fastify({ - ajv: { - customOptions: { - strict: 'log', - keywords: ['kind', 'modifier'], - }, - }, -}).withTypeProvider() +const server = Fastify().withTypeProvider() server.register(plugin1) // wrong server.register(plugin2) // correct @@ -213,14 +206,7 @@ import Fastify from 'fastify' import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox' import { registerRoutes } from './routes' -const server = Fastify({ - ajv: { - customOptions: { - strict: 'log', - keywords: ['kind', 'modifier'], - }, - }, -}).withTypeProvider() +const server = Fastify().withTypeProvider() registerRoutes(server) From fb682090f3236b870df53ddeff7e34f4c03b5f5e Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 3 Sep 2022 12:37:53 +0200 Subject: [PATCH 0066/1295] fix: visit schemas with custom prototype (#4248) --- lib/schemas.js | 1 + test/schema-special-usage.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/schemas.js b/lib/schemas.js index a28d4e82c38..52c44647ef2 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -62,6 +62,7 @@ function normalizeSchema (routeSchemas, serverOptions) { // let's check if our schemas have a custom prototype for (const key of ['headers', 'querystring', 'params', 'body']) { if (typeof routeSchemas[key] === 'object' && Object.getPrototypeOf(routeSchemas[key]) !== Object.prototype) { + routeSchemas[kSchemaVisited] = true return routeSchemas } } diff --git a/test/schema-special-usage.test.js b/test/schema-special-usage.test.js index d2cb65d56b5..8f438c2721d 100644 --- a/test/schema-special-usage.test.js +++ b/test/schema-special-usage.test.js @@ -2,6 +2,7 @@ const { test } = require('tap') const Joi = require('joi') +const yup = require('yup') const AJV = require('ajv') const S = require('fluent-json-schema') const Fastify = require('..') @@ -673,3 +674,31 @@ test('JOI validation overwrite request headers', t => { }) }) }) + +test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t => { + const fastify = Fastify() + const handler = () => { } + + fastify.get('/the/url', { + schema: { + query: yup.object({ + foo: yup.string() + }) + }, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return function (data) { + // with option strict = false, yup `validateSync` function returns the coerced value if validation was successful, or throws if validation failed + try { + const result = schema.validateSync(data, {}) + return { value: result } + } catch (e) { + return { error: e } + } + } + }, + handler + }) + + await fastify.ready() + t.pass('fastify is ready') +}) From c05f261584795dc0156045f9fe13fb52fdf810b0 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sun, 4 Sep 2022 15:21:12 +0200 Subject: [PATCH 0067/1295] feat: add hasRoute (#4238) * add hasRoute * add documentation to hasRoute * improve documentation * Update docs/Reference/Server.md * Update docs/Reference/Server.md Co-authored-by: James Sumners * improve documentation * improve documentation Co-authored-by: James Sumners --- docs/Reference/Server.md | 21 +++++++++++ fastify.js | 3 ++ lib/route.js | 9 +++++ test/has-route.test.js | 77 ++++++++++++++++++++++++++++++++++++++ test/types/route.test-d.ts | 11 ++++++ types/instance.d.ts | 6 +++ 6 files changed, 127 insertions(+) create mode 100644 test/has-route.test.js diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index c998f7f6cf6..b18a1bd21c9 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -54,6 +54,7 @@ describes the properties available in that options object. - [setDefaultRoute](#setdefaultroute) - [routing](#routing) - [route](#route) + - [hasRoute](#hasRoute) - [close](#close) - [decorate*](#decorate) - [register](#register) @@ -1049,6 +1050,26 @@ fastify.routing(req, res) Method to add routes to the server, it also has shorthand functions, check [here](./Routes.md). +#### hasRoute + + +Method to check if a route is already registered to the internal router. It +expects an object as payload. `url` and `method` are mandatory fields. It is +possible to also specify `constraints`. The method returns true if the route is +registered, and false if it is not registered. + +```js +const routeExists = fastify.hasRoute({ + url: '/', + method: 'GET', + constraints: { version: '1.0.0' } // optional +}) + +if (routeExists === false) { + // add route +} +``` + #### close diff --git a/fastify.js b/fastify.js index 3f6750e6982..c4b54e92ee4 100644 --- a/fastify.js +++ b/fastify.js @@ -267,6 +267,9 @@ function fastify (options) { // otherwise we should bind it after the declaration return router.route.call(this, { options }) }, + hasRoute: function _route (options) { + return router.hasRoute.call(this, { options }) + }, // expose logger instance log: logger, // type provider diff --git a/lib/route.js b/lib/route.js index ef9f9209724..a0f9fc167af 100644 --- a/lib/route.js +++ b/lib/route.js @@ -83,6 +83,7 @@ function buildRouting (options) { }, routing: router.lookup.bind(router), // router func to find the right handler to call route, // configure a route in the fastify instance + hasRoute, prepareRoute, getDefaultRoute: function () { return router.defaultRoute @@ -141,6 +142,14 @@ function buildRouting (options) { return route.call(this, { options, isFastify }) } + function hasRoute ({ options }) { + return router.find( + options.method, + options.url || '', + options.constraints + ) !== null + } + // Route management function route ({ options, isFastify }) { // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator diff --git a/test/has-route.test.js b/test/has-route.test.js new file mode 100644 index 00000000000..28e1ca943a7 --- /dev/null +++ b/test/has-route.test.js @@ -0,0 +1,77 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('../fastify') + +test('hasRoute', t => { + t.plan(4) + const test = t.test + const fastify = Fastify() + + test('hasRoute - invalid options', t => { + t.plan(3) + + t.equal(fastify.hasRoute({ }), false) + + t.equal(fastify.hasRoute({ method: 'GET' }), false) + + t.equal(fastify.hasRoute({ constraints: [] }), false) + }) + + test('hasRoute - primitive method', t => { + t.plan(2) + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + + t.equal(fastify.hasRoute({ + method: 'GET', + url: '/' + }), true) + + t.equal(fastify.hasRoute({ + method: 'POST', + url: '/' + }), false) + }) + + test('hasRoute - with constraints', t => { + t.plan(2) + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + t.equal(fastify.hasRoute({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' } + }), true) + + t.equal(fastify.hasRoute({ + method: 'GET', + url: '/', + constraints: { version: '1.3.0' } + }), false) + }) + + test('hasRoute - parametric route regexp with constraints', t => { + t.plan(1) + // parametric with regexp + fastify.get('/example/:file(^\\d+).png', function (request, reply) { }) + + t.equal(fastify.hasRoute({ + method: 'GET', + url: '/example/12345.png' + }), true) + }) +}) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 0b10b9fa612..697105dca8a 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -230,3 +230,14 @@ expectType(fastify().route({ method: 'GET', handler: routeHandlerWithReturnValue })) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET' +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { version: '1.2.0' } +})) diff --git a/types/instance.d.ts b/types/instance.d.ts index 10693669c6d..0d562717cf6 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -186,6 +186,12 @@ export interface FastifyInstance< patch: RouteShorthandMethod; all: RouteShorthandMethod; + hasRoute< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + >(opts: Pick, 'method' | 'url' | 'constraints'>): boolean; + // addHook: overloads // Lifecycle addHooks From a46aead95e1ce2de3928d376a39f79c9d1d5d2ca Mon Sep 17 00:00:00 2001 From: Wilbert Abreu Date: Tue, 6 Sep 2022 08:17:55 +0200 Subject: [PATCH 0068/1295] docs(serverReference): fixing ajv docs url (#4253) --- docs/Reference/Server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index b18a1bd21c9..248e5a105cd 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -668,7 +668,7 @@ the incoming request as usual. Configure the Ajv v8 instance used by Fastify without providing a custom one. The default configuration is explained in the -[#schema-validator](Validation-and-Serialization.md#schema-validator) section. +[#schema-validator](./Validation-and-Serialization.md#schema-validator) section. ```js const fastify = require('fastify')({ From 8073957fee3644f6b7b184d383c4ffed35c52307 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 6 Sep 2022 08:40:25 +0200 Subject: [PATCH 0069/1295] chore(doc): fix format (#4255) * chore(doc): fix format * fix: alphabetical order * fuuuuu --- docs/Guides/Ecosystem.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index a3790728f93..5d8ef72b988 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -216,14 +216,14 @@ section. - [`fastify-autoroutes`](https://github.com/GiovanniCardamone/fastify-autoroutes) Plugin to scan and load routes based on filesystem path from a custom directory. -- [`fastify-axios`](https://github.com/davidedantonio/fastify-axios) Plugin to - send HTTP requests via [axios](https://github.com/axios/axios). -- [fastify-aws-timestream](https://github.com/gzileni/fastify-aws-timestream) - Fastify plugin for managing databases, tables, and querying and creating - scheduled queries with AWS Timestream. -- [fastify-aws-sns](https://github.com/gzileni/fastify-aws-sns) Fastify plugin +- [`fastify-aws-sns`](https://github.com/gzileni/fastify-aws-sns) Fastify plugin for AWS Simple Notification Service (AWS SNS) that coordinates and manages the delivery or sending of messages to subscribing endpoints or clients. +- [`fastify-aws-timestream`](https://github.com/gzileni/fastify-aws-timestream) + Fastify plugin for managing databases, tables, and querying and creating + scheduled queries with AWS Timestream. +- [`fastify-axios`](https://github.com/davidedantonio/fastify-axios) Plugin to + send HTTP requests via [axios](https://github.com/axios/axios). - [`fastify-babel`](https://github.com/cfware/fastify-babel) Fastify plugin for development servers that require Babel transformations of JavaScript sources. - [`fastify-bcrypt`](https://github.com/heply/fastify-bcrypt) A Bcrypt hash From 16b25c8fdb0f38bcf9c94731b71a4d4cff5d68a3 Mon Sep 17 00:00:00 2001 From: Chris Crewdson Date: Tue, 6 Sep 2022 17:10:04 -0700 Subject: [PATCH 0070/1295] chore: export RouteGenericInterface (#4234) * chore: export RouteGenericInterface * add test for re-exported type * Update test/types/fastify.test-d.ts Co-authored-by: Uzlopak Co-authored-by: Chris Crewdson Co-authored-by: Uzlopak --- fastify.d.ts | 2 +- test/types/fastify.test-d.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/fastify.d.ts b/fastify.d.ts index 76a3895e04f..41afbb8f9e8 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -197,7 +197,7 @@ export { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, Fastif export { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' export { FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel } from './types/logger' export { FastifyContext, FastifyContextConfig } from './types/context' -export { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route' +export { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route' export * from './types/register' export { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser' export { FastifyError } from '@fastify/error' diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 750e4d25472..d30146d3990 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -8,6 +8,7 @@ import fastify, { LightMyRequestResponse, LightMyRequestCallback, InjectOptions, FastifyBaseLogger, + RouteGenericInterface, ValidationResult } from '../../fastify' import { ErrorObject as AjvErrorObject } from 'ajv' @@ -232,3 +233,10 @@ const ajvErrorObject: AjvErrorObject = { message: '' } expectAssignable(ajvErrorObject) + +const routeGeneric: RouteGenericInterface = {} +expectType(routeGeneric.Body) +expectType(routeGeneric.Headers) +expectType(routeGeneric.Params) +expectType(routeGeneric.Querystring) +expectType(routeGeneric.Reply) From af27b78e89f07861ed28890008f17a65139d443c Mon Sep 17 00:00:00 2001 From: Noor Eldeen Salah Date: Wed, 7 Sep 2022 23:45:22 +0200 Subject: [PATCH 0071/1295] chore: improve `Ecosystem.md` linter to check for improper module name patterns (#4257) * chore: improve `Ecosystem.md` linting script Improve ecosystem linting script by returning an error in case of an improperly added module name pattern (i.e., not enclosed with backticks) * Improve ecosystem linting to include all mis-formatted in error msg * Check for misformatted module patterns in all ecosystem file sections * fixup! Improve ecosystem linting to include all mis-formatted in error msg * revert: limit ecosystem linting to community section only --- .github/scripts/lint-ecosystem.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/scripts/lint-ecosystem.js b/.github/scripts/lint-ecosystem.js index f81546e9b77..6a8d50f2c60 100644 --- a/.github/scripts/lint-ecosystem.js +++ b/.github/scripts/lint-ecosystem.js @@ -16,26 +16,36 @@ module.exports = async function ({ core }) { const moduleNameRegex = /^\- \[\`(.+)\`\]/ let hasOutOfOrderItem = false let lineNumber = 0 - let inCommmunitySection = false + let inCommunitySection = false let modules = [] + let hasImproperFormat = false for await (const line of rl) { lineNumber += 1 if (line.startsWith('#### [Community]')) { - inCommmunitySection = true + inCommunitySection = true } if (line.startsWith('#### [Community Tools]')) { - inCommmunitySection = false + inCommunitySection = false } - if (inCommmunitySection === false) { + if (inCommunitySection === false) { continue } - if (line.startsWith('- [`') !== true) { + if (line.startsWith('- [') !== true) { continue } - const moduleName = moduleNameRegex.exec(line)[1] + const moduleNameTest = moduleNameRegex.exec(line) + + if (moduleNameTest === null) + { + core.error(`line ${lineNumber}: improper pattern, module name should be enclosed with backticks`) + hasImproperFormat = true + continue + } + + const moduleName = moduleNameTest[1] if (modules.length > 0) { if (compare(moduleName, modules.at(-1)) > 0) { core.error(`line ${lineNumber}: ${moduleName} not listed in alphabetical order`) @@ -48,6 +58,10 @@ module.exports = async function ({ core }) { if (hasOutOfOrderItem === true) { core.setFailed('Some ecosystem modules are not in alphabetical order.') } + + if (hasImproperFormat === true) { + core.setFailed('Some ecosystem modules are improperly formatted.') + } } function compare(current, previous) { From 91564f7fbcc6ffc68cd9bd69347d1621d5689d8b Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Thu, 8 Sep 2022 13:47:21 -0300 Subject: [PATCH 0072/1295] test: remove assert to invalid HTTP version (#4260) --- test/unsupported-httpversion.test.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/test/unsupported-httpversion.test.js b/test/unsupported-httpversion.test.js index 9a984c5133c..ef93fef7d4f 100644 --- a/test/unsupported-httpversion.test.js +++ b/test/unsupported-httpversion.test.js @@ -4,32 +4,6 @@ const net = require('net') const t = require('tap') const Fastify = require('../fastify') -t.test('Will return 505 HTTP error if HTTP version (default) is not supported', t => { - const fastify = Fastify() - - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', (req, reply) => { - reply.send({ hello: 'world' }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - const port = fastify.server.address().port - const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/5.1\r\n\r\n') - - client.once('data', data => { - t.match(data.toString(), /505 HTTP Version Not Supported/i) - client.end(() => { - t.end() - }) - }) - }) - }) -}) - t.test('Will return 505 HTTP error if HTTP version (2.0 when server is 1.1) is not supported', t => { const fastify = Fastify() From 16e4b5bb1dbad1772572d9247694259f2419510e Mon Sep 17 00:00:00 2001 From: Noor Eldeen Salah Date: Fri, 9 Sep 2022 08:57:08 +0200 Subject: [PATCH 0073/1295] chore: improve `Ecosystem.md` linter to lint all sections (#4258) * chore: improve `Ecosystem.md` linter to validate all sections * Cache module name detection boolean to a variable * Extend ecosystem linter ordering functionality to all sections * Fix out of order modules in `Core` section in `Ecosystem.md` * refactor: refactor `ecosystem.md` linter --- .github/scripts/lint-ecosystem.js | 11 ++++------- docs/Guides/Ecosystem.md | 16 ++++++++-------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/scripts/lint-ecosystem.js b/.github/scripts/lint-ecosystem.js index 6a8d50f2c60..395fb97afc1 100644 --- a/.github/scripts/lint-ecosystem.js +++ b/.github/scripts/lint-ecosystem.js @@ -16,20 +16,17 @@ module.exports = async function ({ core }) { const moduleNameRegex = /^\- \[\`(.+)\`\]/ let hasOutOfOrderItem = false let lineNumber = 0 - let inCommunitySection = false let modules = [] let hasImproperFormat = false for await (const line of rl) { lineNumber += 1 if (line.startsWith('#### [Community]')) { - inCommunitySection = true + modules = [] } + if (line.startsWith('#### [Community Tools]')) { - inCommunitySection = false - } - if (inCommunitySection === false) { - continue + modules = [] } if (line.startsWith('- [') !== true) { @@ -58,7 +55,7 @@ module.exports = async function ({ core }) { if (hasOutOfOrderItem === true) { core.setFailed('Some ecosystem modules are not in alphabetical order.') } - + if (hasImproperFormat === true) { core.setFailed('Some ecosystem modules are improperly formatted.') } diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 5d8ef72b988..760a8f029ac 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -75,6 +75,9 @@ section. connection pool across every part of your server. - [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) Multipart support for Fastify. +- [`@fastify/nextjs`](https://github.com/fastify/fastify-nextjs) React + server-side rendering support for Fastify with + [Next](https://github.com/zeit/next.js/). - [`@fastify/oauth2`](https://github.com/fastify/fastify-oauth2) Wrap around [`simple-oauth2`](https://github.com/lelylan/simple-oauth2). - [`@fastify/postgres`](https://github.com/fastify/fastify-postgres) Fastify @@ -82,6 +85,11 @@ section. connection pool in every part of your server. - [`@fastify/rate-limit`](https://github.com/fastify/fastify-rate-limit) A low overhead rate limiter for your routes. +- [`@fastify/redis`](https://github.com/fastify/fastify-redis) Fastify Redis + connection plugin, with which you can share the same Redis connection across + every part of your server. +- [`@fastify/reply-from`](https://github.com/fastify/fastify-reply-from) Plugin + to forward the current HTTP request to another server. - [`@fastify/request-context`](https://github.com/fastify/fastify-request-context) Request-scoped storage, based on [AsyncLocalStorage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage) @@ -89,14 +97,6 @@ section. providing functionality similar to thread-local storages. - [`@fastify/response-validation`](https://github.com/fastify/fastify-response-validation) A simple plugin that enables response validation for Fastify. -- [`@fastify/nextjs`](https://github.com/fastify/fastify-nextjs) React - server-side rendering support for Fastify with - [Next](https://github.com/zeit/next.js/). -- [`@fastify/redis`](https://github.com/fastify/fastify-redis) Fastify Redis - connection plugin, with which you can share the same Redis connection across - every part of your server. -- [`@fastify/reply-from`](https://github.com/fastify/fastify-reply-from) Plugin - to forward the current HTTP request to another server. - [`@fastify/routes`](https://github.com/fastify/fastify-routes) Plugin that provides a `Map` of routes. - [`@fastify/schedule`](https://github.com/fastify/fastify-schedule) Plugin for From aa43e2de2cac18d7654c6a88e5437f8b0854872e Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Sat, 10 Sep 2022 09:49:00 +0200 Subject: [PATCH 0074/1295] docs: fix typescript example (#4261) Signed-off-by: iifawzi Signed-off-by: iifawzi --- examples/typescript-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/typescript-server.ts b/examples/typescript-server.ts index 71f7ee0ba62..5be41d7bb3b 100644 --- a/examples/typescript-server.ts +++ b/examples/typescript-server.ts @@ -56,7 +56,7 @@ const opts: RouteShorthandOptions = { }; // Add our route handler with correct types -server.get<{ +server.post<{ Querystring: PingQuerystring; Params: PingParams; Headers: PingHeaders; From c7fdbdec4852ded79a5b46e7f682aae1e6ac7807 Mon Sep 17 00:00:00 2001 From: "Simen A. W. Olsen" Date: Sun, 11 Sep 2022 23:29:18 +0200 Subject: [PATCH 0075/1295] docs: add pubsub-http-handler (#4263) * add pubsub-http-handler * reorder --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 760a8f029ac..0f6b3a9211f 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -580,6 +580,8 @@ section. - [`openapi-validator-middleware`](https://github.com/PayU/openapi-validator-middleware#fastify) Swagger and OpenAPI 3.0 spec-based request validation middleware that supports Fastify. +- [`pubsub-http-handler`](https://github.com/cobraz/pubsub-http-handler) A Fastify + plugin to easily create Google Cloud PubSub endpoints. - [`sequelize-fastify`](https://github.com/hsynlms/sequelize-fastify) A simple and lightweight Sequelize plugin for Fastify. - [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple From 9bac49db2884f31f71724bf7e43405a3575aa0a8 Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Mon, 12 Sep 2022 18:40:46 +0200 Subject: [PATCH 0076/1295] Variadic listen signature allows string port (#4269) --- lib/server.js | 7 ++++++- test/listen.deprecated.test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index 0a23fd022f6..de9b61a3294 100644 --- a/lib/server.js +++ b/lib/server.js @@ -319,7 +319,7 @@ function normalizeListenArgs (args) { options.backlog = argsLength > 1 ? lastArg : undefined } else { /* Deal with listen ([port[, host[, backlog]]]) */ - options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : 0 + options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : normalizePort(firstArg) // This will listen to what localhost is. // It can be 127.0.0.1 or ::1, depending on the operating system. // Fixes https://github.com/fastify/fastify/issues/1022. @@ -330,6 +330,11 @@ function normalizeListenArgs (args) { return options } +function normalizePort (firstArg) { + const port = parseInt(firstArg, 10) + return port >= 0 && !Number.isNaN(port) ? port : 0 +} + function logServerAddress (server) { let address = server.address() const isUnixSocket = typeof address === 'string' diff --git a/test/listen.deprecated.test.js b/test/listen.deprecated.test.js index 8079067dc3b..01f6cc7aa94 100644 --- a/test/listen.deprecated.test.js +++ b/test/listen.deprecated.test.js @@ -200,3 +200,36 @@ test('listen when firstArg is { path: string(pipe) } and with backlog and callba t.equal(address, '\\\\.\\pipe\\testPipe3') }) }) + +test('listen accepts a port as string, and callback', t => { + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + const port = 3000 + fastify.listen(port.toString(), localhost, (err) => { + t.equal(fastify.server.address().port, port) + t.error(err) + }) +}) + +test('listen accepts a port as string, address and callback', t => { + t.plan(3) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + const port = 3000 + fastify.listen(port.toString(), localhost, (err) => { + t.equal(fastify.server.address().port, port) + t.equal(fastify.server.address().address, localhost) + t.error(err) + }) +}) + +test('listen with invalid port string without callback with (address)', t => { + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen('-1') + .then(address => { + t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) + }) +}) From 072ccd6604f811c7f1cfdd0b5f073c032afaa4ee Mon Sep 17 00:00:00 2001 From: Peter Mbanugo Date: Tue, 13 Sep 2022 10:07:50 +0200 Subject: [PATCH 0077/1295] docs: Remove Ajv configuration for TypeBox in TS examples (#4268) --- docs/Reference/TypeScript.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 9b374e7ebb0..8b6cae9a5cb 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -254,18 +254,6 @@ can do it as follows: ) ``` - **Note** For Ajv version 7 and above is required to use the `ajvTypeBoxPlugin`: - - ```typescript - import Fastify from 'fastify' - import { ajvTypeBoxPlugin, TypeBoxTypeProvider } from '@fastify/type-provider-typebox' - - const fastify = Fastify({ - ajv: { - plugins: [ajvTypeBoxPlugin] - } - }).withTypeProvider() - ``` #### Schemas in JSON Files From fa7b5dfbe6cdc3d09184603e6c1ac0d9b7b361e1 Mon Sep 17 00:00:00 2001 From: William Nemencha Date: Tue, 13 Sep 2022 16:23:58 +0200 Subject: [PATCH 0078/1295] docs(ecosystem): add 2 new fastify v3.x+ plugins (#4270) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(docs): add 2 new fastify v3.x+ plugins This commit adds the following plugins to the community section of the plugins' list: - [`@ethicdevs/fastify-custom-session](https://github.com/EthicDevs/fastify-custom-session) - [`@ethicdevs/fastify-git-server](https://github.com/EthicDevs/fastify-git-server) Hope you'll like them 😛 * docs(ecosystem): update list sort --- docs/Guides/Ecosystem.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 0f6b3a9211f..f766542f357 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -146,6 +146,13 @@ section. close the server gracefully on `SIGINT` and `SIGTERM` signals. - [`@eropple/fastify-openapi3`](https://github.com/eropple/fastify-openapi3) Provides easy, developer-friendly OpenAPI 3.1 specs + doc explorer based on your routes. +- [`@ethicdevs/fastify-custom-session`](https://github.com/EthicDevs/fastify-custom-session) + A plugin that let you use session and decide only where to load/save from/to. Has + great TypeScript support + built-in adapters for common ORMs/databases (Firebase, + Prisma Client, Postgres (wip), InMemory) and you can easily make your own adapter! +- [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server) + A plugin to easily create git server and make one/many Git repositories available + for clone/fetch/push through the standard `git` (over http) commands. - [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server) Tiny (~5k), Fast, KISS, and dependency-free Node.JS library to make your Fastify API graceful. From 94fafa07f65dbc4fd8a41cf18fbdfe17441d7d22 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Tue, 13 Sep 2022 17:54:39 +0200 Subject: [PATCH 0079/1295] docs(typescript): fix fastify/fastify#4241 (#4247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix fastify/fastify#4241 add note in typescript doc to prevent ts(2349) warning https://github.com/fastify/fastify/issues/4241 * follow good idée in comment --- docs/Reference/TypeScript.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 8b6cae9a5cb..8793d6afa58 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -61,6 +61,11 @@ in a blank http Fastify server. *Note: Set `target` property in `tsconfig.json` to `es2017` or greater to avoid [FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.* +*Note 2: Avoid using ```"moduleResolution": "NodeNext"``` in tsconfig.json with +```"type": "module"``` in package.json. This combination is currently not +supported by fastify typing system. +[ts(2349)](https://github.com/fastify/fastify/issues/4241) warning.* + 4. Create an `index.ts` file - this will contain the server code 5. Add the following code block to your file: ```typescript From 123753774f0ee6392f41ebc7cce4e790a2a264d9 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 14 Sep 2022 15:01:28 +0200 Subject: [PATCH 0080/1295] Bumped v4.6.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 53 ++++++++++------------------------------- package.json | 2 +- 3 files changed, 15 insertions(+), 42 deletions(-) diff --git a/fastify.js b/fastify.js index c4b54e92ee4..d15988a923a 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.5.3' +const VERSION = '4.6.0' const Avvio = require('avvio') const http = require('http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index 8e8b4f52a1e..63c33edebcf 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -23,14 +23,6 @@ class Serializer { } } - asAny (i) { - return JSON.stringify(i) - } - - asNull () { - return 'null' - } - asInteger (i) { if (typeof i === 'bigint') { return i.toString() @@ -47,10 +39,6 @@ class Serializer { } } - asIntegerNullable (i) { - return i === null ? 'null' : this.asInteger(i) - } - asNumber (i) { const num = Number(i) if (Number.isNaN(num)) { @@ -62,54 +50,43 @@ class Serializer { } } - asNumberNullable (i) { - return i === null ? 'null' : this.asNumber(i) - } - asBoolean (bool) { return bool && 'true' || 'false' // eslint-disable-line } - asBooleanNullable (bool) { - return bool === null ? 'null' : this.asBoolean(bool) - } - asDateTime (date) { if (date === null) return '""' if (date instanceof Date) { return '"' + date.toISOString() + '"' } + if (typeof date === 'string') { + return '"' + date + '"' + } throw new Error(`The value "${date}" cannot be converted to a date-time.`) } - asDateTimeNullable (date) { - return date === null ? 'null' : this.asDateTime(date) - } - asDate (date) { if (date === null) return '""' if (date instanceof Date) { return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"' } + if (typeof date === 'string') { + return '"' + date + '"' + } throw new Error(`The value "${date}" cannot be converted to a date.`) } - asDateNullable (date) { - return date === null ? 'null' : this.asDate(date) - } - asTime (date) { if (date === null) return '""' if (date instanceof Date) { return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"' } + if (typeof date === 'string') { + return '"' + date + '"' + } throw new Error(`The value "${date}" cannot be converted to a time.`) } - asTimeNullable (date) { - return date === null ? 'null' : this.asTime(date) - } - asString (str) { const quotes = '"' if (str instanceof Date) { @@ -129,10 +106,6 @@ class Serializer { } } - asStringNullable (str) { - return str === null ? 'null' : this.asString(str) - } - // magically escape strings for json // relying on their charCodeAt // everything below 32 needs JSON.stringify() @@ -200,7 +173,7 @@ class Serializer { } json += "\"statusCode\"" + ':' - json += serializer.asNumber.bind(serializer)(obj["statusCode"]) + json += serializer.asNumber(obj["statusCode"]) } if (obj["code"] !== undefined) { @@ -212,7 +185,7 @@ class Serializer { } json += "\"code\"" + ':' - json += serializer.asString.bind(serializer)(obj["code"]) + json += serializer.asString(obj["code"]) } if (obj["error"] !== undefined) { @@ -224,7 +197,7 @@ class Serializer { } json += "\"error\"" + ':' - json += serializer.asString.bind(serializer)(obj["error"]) + json += serializer.asString(obj["error"]) } if (obj["message"] !== undefined) { @@ -236,7 +209,7 @@ class Serializer { } json += "\"message\"" + ':' - json += serializer.asString.bind(serializer)(obj["message"]) + json += serializer.asString(obj["message"]) } json += '}' diff --git a/package.json b/package.json index e9b46a1578d..7724ee8ce88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.5.3", + "version": "4.6.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 21f209f9ad5205d91e7a520ef380f51b8587419f Mon Sep 17 00:00:00 2001 From: KaKa Date: Wed, 14 Sep 2022 23:41:16 +0800 Subject: [PATCH 0081/1295] fix: prevent reuse mutated route option for head (#4273) * fix: prevent reuse mutated route option for head * test: ensure no regression * fix: typo --- lib/route.js | 13 ++++++++----- test/route.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/route.js b/lib/route.js index a0f9fc167af..a782c97e85e 100644 --- a/lib/route.js +++ b/lib/route.js @@ -155,6 +155,12 @@ function buildRouting (options) { // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator const opts = { ...options } + const { exposeHeadRoute } = opts + const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null + const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes + // we need to clone a set of initial options for HEAD route + const headOpts = shouldExposeHead && options.method === 'GET' ? { ...options } : null + throwIfAlreadyStarted('Cannot add route when fastify instance is already started!') const path = opts.url || opts.path || '' @@ -342,13 +348,10 @@ function buildRouting (options) { // register head route in sync // we must place it after the `this.after` - const { exposeHeadRoute } = opts - const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null - const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes if (shouldExposeHead && options.method === 'GET' && !hasHEADHandler) { - const onSendHandlers = parseHeadOnSendHandlers(opts.onSend) - prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...opts, onSend: onSendHandlers }, isFastify: true }) + const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend) + prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true }) } else if (hasHEADHandler && exposeHeadRoute) { warning.emit('FSTDEP007') } diff --git a/test/route.test.js b/test/route.test.js index 9bf16eb1b2a..06f02c3b184 100644 --- a/test/route.test.js +++ b/test/route.test.js @@ -1464,3 +1464,32 @@ test('invalid url attribute - non string URL', t => { t.equal(error.code, FST_ERR_INVALID_URL().code) } }) + +test('exposeHeadRoute should not reuse the same route option', async t => { + t.plan(2) + + const fastify = Fastify() + + // we update the onRequest hook in onRoute hook + // if we reuse the same route option + // that means we will append another function inside the array + fastify.addHook('onRoute', function (routeOption) { + if (Array.isArray(routeOption.onRequest)) { + routeOption.onRequest.push(() => {}) + } else { + routeOption.onRequest = [() => {}] + } + }) + + fastify.addHook('onRoute', function (routeOption) { + t.equal(routeOption.onRequest.length, 1) + }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + async handler () { + return 'hello world' + } + }) +}) From 204e4370aebccd2996458f5ce6c5c59fac99988f Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Wed, 14 Sep 2022 18:13:06 +0200 Subject: [PATCH 0082/1295] docs(ecosystem): add fastify-sqlite (#4274) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f766542f357..e11aa2e796f 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -517,6 +517,8 @@ section. - [`fastify-split-validator`](https://github.com/MetCoder95/fastify-split-validator) Small plugin to allow you use multiple validators in one route based on each HTTP part of the request. +- [`fastify-sqlite`](https://github.com/Eomm/fastify-sqlite) connects your + application to a sqlite3 database. - [`fastify-sse`](https://github.com/lolo32/fastify-sse) to provide Server-Sent Events with `reply.sse( … )` to Fastify. - [`fastify-sse-v2`](https://github.com/nodefactoryio/fastify-sse-v2) to provide From c28f37077bd53224c6db0a501c4a41713570f20b Mon Sep 17 00:00:00 2001 From: Brian Baidal <14996002+drakhart@users.noreply.github.com> Date: Thu, 15 Sep 2022 17:11:45 +0200 Subject: [PATCH 0083/1295] docs(ecosystem): add RavenDB to community plugins (#4277) * chore: add ravendb to community plugins * chore: lint fastify-ravendb entry --- docs/Guides/Ecosystem.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index e11aa2e796f..3fa6d598e6c 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -119,10 +119,10 @@ section. - [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) Fastify [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) - for [json-schema-to-ts](https://github.com/ThomasAribart/json-schema-to-ts). + for [json-schema-to-ts](https://github.com/ThomasAribart/json-schema-to-ts). - [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox) Fastify - [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) + [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) for [Typebox](https://github.com/sinclairzx81/typebox). - [`@fastify/under-pressure`](https://github.com/fastify/under-pressure) Measure process load with automatic handling of _"Service Unavailable"_ plugin for @@ -167,7 +167,7 @@ section. Sentry errors handler that just works! Install, add your DSN and you're good to go! - [`@mateonunez/fastify-lyra`](https://github.com/mateonunez/fastify-lyra) - A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine + A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine on Fastify - [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit) A plugin to close the server gracefully @@ -191,7 +191,7 @@ section. - [`cls-rtracer`](https://github.com/puzpuzpuz/cls-rtracer) Fastify middleware for CLS-based request ID generation. An out-of-the-box solution for adding request IDs into your logs. -- [`electron-server`](https://github.com/anonrig/electron-server) A plugin for +- [`electron-server`](https://github.com/anonrig/electron-server) A plugin for using Fastify without the need of consuming a port on Electron apps. - [`fast-water`](https://github.com/tswayne/fast-water) A Fastify plugin for waterline. Decorates Fastify with waterline models. @@ -446,7 +446,7 @@ section. OrientDB connection plugin, with which you can share the OrientDB connection across every part of your server. - [`fastify-osm`](https://github.com/gzileni/fastify-osm) Fastify - OSM plugin to run overpass queries by OpenStreetMap. + OSM plugin to run overpass queries by OpenStreetMap. - [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo) Fastify plugin for memoize responses by expressive settings. - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker @@ -471,6 +471,9 @@ section. [qs](https://github.com/ljharb/qs). - [`fastify-racing`](https://github.com/metcoder95/fastify-racing) Fastify's plugin that adds support to handle an aborted request asynchronous. +- [`fastify-ravendb`](https://github.com/nearform/fastify-ravendb) RavenDB + connection plugin. It exposes the same `DocumentStore` (or multiple ones) + across the whole Fastify application. - [`fastify-raw-body`](https://github.com/Eomm/fastify-raw-body) Add the `request.rawBody` field. - [`fastify-rbac`](https://gitlab.com/m03geek/fastify-rbac) Fastify role-based @@ -543,8 +546,8 @@ section. - [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools) Useful functions for Twitch Extension Backend Services (EBS). - [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod) - Fastify - [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) + Fastify + [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) for [zod](https://github.com/colinhacks/zod). - [`fastify-typeorm-plugin`](https://github.com/inthepocket/fastify-typeorm-plugin) Fastify plugin to work with TypeORM. From 185ba3cfce68051a22198ab00d65188b22b725e3 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 16 Sep 2022 09:17:49 +0200 Subject: [PATCH 0084/1295] ci: reduce ci test when linting fails (#4280) --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd7afc16a59..6a6baf63bc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,10 +54,12 @@ jobs: npm run lint coverage-nix: + needs: linter permissions: contents: read uses: ./.github/workflows/coverage-nix.yml coverage-win: + needs: linter permissions: contents: read uses: ./.github/workflows/coverage-win.yml From e9838a23caa87928e4719c5125f103709cbb537c Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Fri, 16 Sep 2022 11:09:48 -0400 Subject: [PATCH 0085/1295] chore: update dependencies (#4284) --- package.json | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 7724ee8ce88..045eb5ed59e 100644 --- a/package.json +++ b/package.json @@ -126,35 +126,35 @@ "homepage": "https://www.fastify.io/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.24.9", + "@sinclair/typebox": "^0.24.41", "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "^18.0.0", - "@typescript-eslint/eslint-plugin": "^5.27.0", - "@typescript-eslint/parser": "^5.27.0", + "@types/node": "^18.7.18", + "@typescript-eslint/eslint-plugin": "^5.37.0", + "@typescript-eslint/parser": "^5.37.0", "ajv": "^8.11.0", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "branch-comparer": "^1.1.0", - "eslint": "^8.16.0", - "eslint-config-standard": "^17.0.0-1", + "eslint": "^8.23.1", + "eslint-config-standard": "^17.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-n": "^15.2.0", - "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-n": "^15.2.5", + "eslint-plugin-promise": "^6.0.1", "fast-json-body": "^1.1.0", - "fast-json-stringify": "^5.0.0", - "fastify-plugin": "^4.0.0", + "fast-json-stringify": "^5.3.0", + "fastify-plugin": "^4.2.1", "fluent-json-schema": "^3.1.0", "form-data": "^4.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", "joi": "^17.6.0", - "json-schema-to-ts": "^2.5.3", + "json-schema-to-ts": "^2.5.5", "JSONStream": "^1.3.5", "license-checker": "^25.0.1", - "markdownlint-cli2": "^0.5.0", + "markdownlint-cli2": "^0.5.1", "proxyquire": "^2.1.3", "pump": "^3.0.0", "self-cert": "^2.0.0", @@ -162,27 +162,27 @@ "simple-get": "^4.0.1", "snazzy": "^9.0.0", "split2": "^4.1.0", - "standard": "^17.0.0-2", - "tap": "^16.2.0", - "tsd": "^0.23.0", - "typescript": "^4.7.2", - "undici": "^5.4.0", + "standard": "^17.0.0", + "tap": "^16.3.0", + "tsd": "^0.24.1", + "typescript": "^4.8.3", + "undici": "^5.10.0", "vary": "^1.1.2", "yup": "^0.32.11" }, "dependencies": { - "@fastify/ajv-compiler": "^3.1.1", + "@fastify/ajv-compiler": "^3.2.0", "@fastify/error": "^3.0.0", - "@fastify/fast-json-stringify-compiler": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^4.1.0", "abstract-logging": "^2.0.1", - "avvio": "^8.1.3", - "find-my-way": "^7.0.0", - "light-my-request": "^5.5.1", - "pino": "^8.0.0", + "avvio": "^8.2.0", + "find-my-way": "^7.2.0", + "light-my-request": "^5.6.1", + "pino": "^8.5.0", "process-warning": "^2.0.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", - "secure-json-parse": "^2.4.0", + "secure-json-parse": "^2.5.0", "semver": "^7.3.7", "tiny-lru": "^8.0.2" }, @@ -199,4 +199,4 @@ "tsd": { "directory": "test/types" } -} +} \ No newline at end of file From 4109b0363c4f16f777a561aca6e1838fe8048cda Mon Sep 17 00:00:00 2001 From: Magesh Babu Date: Mon, 19 Sep 2022 00:15:40 +0530 Subject: [PATCH 0086/1295] fix: check if route exist before checking Content-Type of body (#4286) * check if route exist before checking content type * added test for route check before content type * pass tests - check if the route is 404 only if the content-parser is undefined by this we can avoid breaking changes. - this also passes previous tests this resolves issue - fastify/fastify-multipart#381 * Update lib/contentTypeParser.js Noted, thanks for the review Co-authored-by: Manuel Spigolon * pass tests from wrong return Co-authored-by: Manuel Spigolon --- lib/contentTypeParser.js | 6 +++++- test/404s.test.js | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 48e293390b0..286aa84b411 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -149,7 +149,11 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply const resource = new AsyncResource('content-type-parser:run', request) if (parser === undefined) { - reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined)) + if (request.is404) { + handler(request, reply) + } else { + reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined)) + } } else if (parser.asString === true || parser.asBuffer === true) { rawBody( request, diff --git a/test/404s.test.js b/test/404s.test.js index 0c945c6806f..94709f6becc 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -6,6 +6,7 @@ const fp = require('fastify-plugin') const sget = require('simple-get').concat const errors = require('http-errors') const split = require('split2') +const FormData = require('form-data') const Fastify = require('..') function getUrl (app) { @@ -18,7 +19,7 @@ function getUrl (app) { } test('default 404', t => { - t.plan(4) + t.plan(5) const test = t.test const fastify = Fastify() @@ -74,6 +75,23 @@ test('default 404', t => { t.equal(response.headers['content-type'], 'application/json; charset=utf-8') }) }) + + test('using post method and multipart/formdata', t => { + t.plan(3) + const form = FormData() + form.append('test-field', 'just some field') + + sget({ + method: 'POST', + url: getUrl(fastify) + '/notSupported', + body: form, + json: false + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 404) + t.equal(response.headers['content-type'], 'application/json; charset=utf-8') + }) + }) }) }) From 9d9d62ca7f8aca18d3714cc72144196f5906a5b0 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 19 Sep 2022 16:31:53 -0400 Subject: [PATCH 0087/1295] perf: replace parseInt with Number (#4289) --- lib/contentTypeParser.js | 2 +- lib/reply.js | 4 ++-- lib/server.js | 2 +- test/internals/reply-serialize.test.js | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 286aa84b411..60577ac3f12 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -189,7 +189,7 @@ function rawBody (request, reply, options, parser, done) { const limit = options.limit === null ? parser.bodyLimit : options.limit const contentLength = request.headers['content-length'] === undefined ? NaN - : Number.parseInt(request.headers['content-length'], 10) + : Number(request.headers['content-length']) if (contentLength > limit) { reply.send(new FST_ERR_CTP_BODY_TOO_LARGE()) diff --git a/lib/reply.js b/lib/reply.js index 7ec650571d7..01c846ee45f 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -292,7 +292,7 @@ Reply.prototype.removeTrailer = function (key) { } Reply.prototype.code = function (code) { - const intValue = parseInt(code) + const intValue = Number(code) if (isNaN(intValue) || intValue < 100 || intValue > 599) { throw new FST_ERR_BAD_STATUS_CODE(code || String(code)) } @@ -562,7 +562,7 @@ function onSendEnd (reply, payload) { const contentLength = reply[kReplyHeaders]['content-length'] if (!contentLength || (req.raw.method !== 'HEAD' && - parseInt(contentLength, 10) !== Buffer.byteLength(payload) + Number(contentLength) !== Buffer.byteLength(payload) ) ) { reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload) diff --git a/lib/server.js b/lib/server.js index de9b61a3294..be997973f3b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -331,7 +331,7 @@ function normalizeListenArgs (args) { } function normalizePort (firstArg) { - const port = parseInt(firstArg, 10) + const port = Number(firstArg) return port >= 0 && !Number.isNaN(port) ? port : 0 } diff --git a/test/internals/reply-serialize.test.js b/test/internals/reply-serialize.test.js index 9da883dc6e4..efab8b95dca 100644 --- a/test/internals/reply-serialize.test.js +++ b/test/internals/reply-serialize.test.js @@ -231,7 +231,7 @@ test('Reply#getSerializationFunction', t => { (req, reply) => { const { id } = req.params - if (parseInt(id) === 1) { + if (Number(id) === 1) { const serialize4xx = reply.getSerializationFunction('4xx') const serialize201 = reply.getSerializationFunction(201) const serializeUndefined = reply.getSerializationFunction(undefined) @@ -308,7 +308,7 @@ test('Reply#getSerializationFunction', t => { (req, reply) => { const { id } = req.params - if (parseInt(id) === 1) { + if (Number(id) === 1) { const serialize = reply.compileSerializationSchema(schemaObj) t.type(serialize, Function) From b5e4157e33d2c96bd931c1adc6306afab8727c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=91=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=BD?= Date: Tue, 20 Sep 2022 02:11:12 +0300 Subject: [PATCH 0088/1295] fix: type of validation function (#4283) * fix: types of validation functions in request * docs: add errors description to validation funcs * Update types/request.d.ts * Apply suggestions from code review Fix lint * Apply suggestions from code review * deps: upgrade @fastify/ajv-compile to v3.3.1 * test: add tests * fix: comments Co-authored-by: Uzlopak --- docs/Reference/Request.md | 15 +++- package.json | 2 +- test/internals/request-validate.test.js | 92 ++++++++++++++++++++++++- types/request.d.ts | 12 +++- 4 files changed, 112 insertions(+), 9 deletions(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index cf4771626cf..d9f05ccad5c 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -98,6 +98,9 @@ it will return a `validation` function that can be used to validate diverse inputs. It returns `undefined` if no serialization function was found using either of the provided inputs. +This function has property errors. Errors encountered during the last validation +are assigned to errors + ```js const validate = request .getValidationFunction({ @@ -108,13 +111,15 @@ const validate = request } } }) -validate({ foo: 'bar' }) // true +console.log(validate({ foo: 'bar' })) // true +console.log(validate.errors) // null // or const validate = request .getValidationFunction('body') -validate({ foo: 0.5 }) // false +console.log(validate({ foo: 0.5 })) // false +console.log(validate.errors) // validation errors ``` See [.compilaValidationSchema(schema, [httpStatus])](#compilevalidationschema) @@ -133,6 +138,8 @@ The optional parameter `httpPart`, if provided, is forwarded directly the `ValidationCompiler`, so it can be used to compile the validation function if a custom `ValidationCompiler` is provided for the route. +This function has property errors. Errors encountered during the last validation +are assigned to errors ```js const validate = request @@ -145,6 +152,7 @@ const validate = request } }) console.log(validate({ foo: 'bar' })) // true +console.log(validate.errors) // null // or @@ -158,6 +166,7 @@ const validate = request } }, 200) console.log(validate({ hello: 'world' })) // false +console.log(validate.errors) // validation errors ``` Note that you should be careful when using this function, as it will cache @@ -247,4 +256,4 @@ request ``` See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema) -for more information on how to compile validation schemas. \ No newline at end of file +for more information on how to compile validation schemas. diff --git a/package.json b/package.json index 045eb5ed59e..4b5c92816d0 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "yup": "^0.32.11" }, "dependencies": { - "@fastify/ajv-compiler": "^3.2.0", + "@fastify/ajv-compiler": "^3.3.1", "@fastify/error": "^3.0.0", "@fastify/fast-json-stringify-compiler": "^4.1.0", "abstract-logging": "^2.0.1", diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js index 89132e5939a..2f904647b59 100644 --- a/test/internals/request-validate.test.js +++ b/test/internals/request-validate.test.js @@ -36,7 +36,7 @@ const requestSchema = { } test('#compileValidationSchema', subtest => { - subtest.plan(5) + subtest.plan(7) subtest.test('Should return a function - Route without schema', async t => { const fastify = Fastify() @@ -59,6 +59,49 @@ test('#compileValidationSchema', subtest => { }) }) + subtest.test('Validate function errors property should be null after validation when input is valid', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.ok(validate({ hello: 'world' })) + t.ok(Object.prototype.hasOwnProperty.call(validate, 'errors')) + t.equal(validate.errors, null) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.notOk(validate({ world: 'foo' })) + t.ok(Object.prototype.hasOwnProperty.call(validate, 'errors')) + t.ok(Array.isArray(validate.errors)) + t.ok(validate.errors.length > 0) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + subtest.test( 'Should reuse the validate fn across multiple invocations - Route without schema', async t => { @@ -206,7 +249,7 @@ test('#compileValidationSchema', subtest => { }) test('#getValidationFunction', subtest => { - subtest.plan(4) + subtest.plan(6) subtest.test('Should return a validation function', async t => { const fastify = Fastify() @@ -228,6 +271,51 @@ test('#getValidationFunction', subtest => { }) }) + subtest.test('Validate function errors property should be null after validation when input is valid', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + req.compileValidationSchema(defaultSchema) + const validate = req.getValidationFunction(defaultSchema) + + t.ok(validate({ hello: 'world' })) + t.ok(Object.prototype.hasOwnProperty.call(validate, 'errors')) + t.equal(validate.errors, null) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + req.compileValidationSchema(defaultSchema) + const validate = req.getValidationFunction(defaultSchema) + + t.notOk(validate({ world: 'foo' })) + t.ok(Object.prototype.hasOwnProperty.call(validate, 'errors')) + t.ok(Array.isArray(validate.errors)) + t.ok(validate.errors.length > 0) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + subtest.test('Should return undefined if no schema compiled', async t => { const fastify = Fastify() diff --git a/types/request.d.ts b/types/request.d.ts index 50ca80c4ef0..4931e72e4b1 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,3 +1,4 @@ +import { ErrorObject } from '@fastify/ajv-compiler' import { FastifyBaseLogger } from './logger' import { ContextConfigDefault, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './utils' import { RouteGenericInterface } from './route' @@ -14,6 +15,11 @@ export interface RequestGenericInterface { Headers?: RequestHeadersDefault; } +export interface ValidationFunction { + (input: any): boolean + errors?: null | ErrorObject[]; +} + /** * FastifyRequest is an instance of the standard http or http2 request objects. * It defaults to http.IncomingMessage, and it also extends the relative request object. @@ -61,9 +67,9 @@ export interface FastifyRequest boolean - getValidationFunction(schema: {[key: string]: any}): (input: any) => boolean - compileValidationSchema(schema: {[key: string]: any}, httpPart?: HTTPRequestPart): (input: any) => boolean + getValidationFunction(httpPart: HTTPRequestPart): ValidationFunction + getValidationFunction(schema: {[key: string]: any}): ValidationFunction + compileValidationSchema(schema: {[key: string]: any}, httpPart?: HTTPRequestPart): ValidationFunction validateInput(input: any, schema: {[key: string]: any}, httpPart?: HTTPRequestPart): boolean validateInput(input: any, httpPart?: HTTPRequestPart): boolean From aa84678dd5e9246decd50ac507dd50396bd377c4 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 20 Sep 2022 07:51:41 +0200 Subject: [PATCH 0089/1295] GitHub Workflows security hardening (#4290) --- .github/workflows/lint-ecosystem-order.yml | 3 +++ .github/workflows/md-lint.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index e93e37422b2..75c7177cd48 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -14,6 +14,9 @@ on: paths: - "**/Ecosystem.md" +permissions: + contents: read # to fetch code (actions/checkout) + jobs: build: name: Lint Ecosystem Order diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index ec0047c9f24..b981705bec8 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -14,6 +14,9 @@ on: paths: - "**/*.md" +permissions: + contents: read # to fetch code (actions/checkout) + jobs: build: name: Lint Markdown From d1a3845ee8db8fc17a2b6bf2c327d7f2dd1bf367 Mon Sep 17 00:00:00 2001 From: Philipp Schmiedel Date: Tue, 20 Sep 2022 11:30:56 +0200 Subject: [PATCH 0090/1295] docs: onRoute hooks in plugins (#4285) * docs: onRoute hooks in plugins * relative links * formatting * linting * Update docs/Guides/Plugins-Guide.md Co-authored-by: Frazer Smith Co-authored-by: Frazer Smith --- docs/Guides/Plugins-Guide.md | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index 6f1d543525b..e46fb1f9801 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -308,6 +308,50 @@ fastify.get('/plugin2', (request, reply) => { ``` Now your hook will run just for the first route! +An alternative approach is to make use of the [onRoute hook](../Reference/Hooks.md#onroute) +to customize application routes dynamically from inside the plugin. Every time +a new route is registered, you can read and modify the route options. For example, +based on a [route config option](../Reference/Routes.md#routes-options): + +```js +fastify.register((instance, opts, done) => { + instance.decorate('util', (request, key, value) => { request[key] = value }) + + function handler(request, reply, done) { + instance.util(request, 'timestamp', new Date()) + done() + } + + instance.addHook('onRoute', (routeOptions) => { + if (routeOptions.config && routeOptions.config.useUtil === true) { + // set or add our handler to the route preHandler hook + if (!routeOptions.preHandler) { + routeOptions.preHandler = [handler] + return + } + if (Array.isArray(routeOptions.preHandler)) { + routeOptions.preHandler.push(handler) + return + } + routeOptions.preHandler = [routeOptions.preHandler, handler] + } + }) + + fastify.get('/plugin1', {config: {useUtil: true}}, (request, reply) => { + reply.send(request) + }) + + fastify.get('/plugin2', (request, reply) => { + reply.send(request) + }) + + done() +}) +``` + +This variant becomes extremely useful if you plan to distribute your plugin, as +described in the next section. + As you probably noticed by now, `request` and `reply` are not the standard Nodejs *request* and *response* objects, but Fastify's objects. From 21eb6cf9f5d49236e88161ef71ece7a7d5926e31 Mon Sep 17 00:00:00 2001 From: Zac Rosenbauer Date: Tue, 20 Sep 2022 07:23:31 -0500 Subject: [PATCH 0091/1295] chore: Lint eco system error (#4275) --- .github/scripts/lint-ecosystem.js | 106 +++++++++++++++++---- .github/workflows/lint-ecosystem-order.yml | 8 +- 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/.github/scripts/lint-ecosystem.js b/.github/scripts/lint-ecosystem.js index 395fb97afc1..033c5a4c51a 100644 --- a/.github/scripts/lint-ecosystem.js +++ b/.github/scripts/lint-ecosystem.js @@ -4,28 +4,41 @@ const path = require('path') const fs = require('fs') const readline = require('readline') -const ecosystemDocFile = path.join(__dirname, '..', '..', 'docs', 'Guides', 'Ecosystem.md') +const basePathEcosystemDocFile = path.join('docs', 'Guides', 'Ecosystem.md') +const ecosystemDocFile = path.join(__dirname, '..', '..', basePathEcosystemDocFile) +const failureTypes = { + improperFormat: 'improperFormat', + outOfOrderItem: 'outOfOrderItem' +} module.exports = async function ({ core }) { + const results = await runCheck() + await handleResults({ core }, results) +} + +async function runCheck () { const stream = await fs.createReadStream(ecosystemDocFile) const rl = readline.createInterface({ input: stream, crlfDelay: Infinity - }); + }) + const failures = [] + const successes = [] const moduleNameRegex = /^\- \[\`(.+)\`\]/ - let hasOutOfOrderItem = false let lineNumber = 0 let modules = [] - let hasImproperFormat = false + let grouping = 'core' for await (const line of rl) { lineNumber += 1 if (line.startsWith('#### [Community]')) { + grouping = 'community' modules = [] } if (line.startsWith('#### [Community Tools]')) { + grouping = 'community-tools' modules = [] } @@ -34,37 +47,94 @@ module.exports = async function ({ core }) { } const moduleNameTest = moduleNameRegex.exec(line) - - if (moduleNameTest === null) - { - core.error(`line ${lineNumber}: improper pattern, module name should be enclosed with backticks`) - hasImproperFormat = true + + if (moduleNameTest === null) { + failures.push({ + lineNumber, + grouping, + moduleName: 'unknown', + type: failureTypes.improperFormat + }) continue } const moduleName = moduleNameTest[1] if (modules.length > 0) { if (compare(moduleName, modules.at(-1)) > 0) { - core.error(`line ${lineNumber}: ${moduleName} not listed in alphabetical order`) - hasOutOfOrderItem = true + failures.push({ + lineNumber, + moduleName, + grouping, + type: failureTypes.outOfOrderItem + }) + } else { + successes.push({ moduleName, lineNumber, grouping }) } + } else { + // We have to push the first item found or we are missing items from the list + successes.push({ moduleName, lineNumber, grouping }) } modules.push(moduleName) } - if (hasOutOfOrderItem === true) { - core.setFailed('Some ecosystem modules are not in alphabetical order.') - } + return { failures, successes } +} + +async function handleResults (scriptLibs, results) { + const { core } = scriptLibs + const { failures, successes } = results; + const isError = !!failures.length; + + await core.summary + .addHeading(isError ? `❌ Ecosystem.md Lint (${failures.length} error${failures.length === 1 ? '' : 's' })` : '✅ Ecosystem Lint (no errors found)') + .addTable([ + [ + { data: 'Status', header: true }, + { data: 'Section', header: true }, + { data: 'Module', header: true }, + { data: 'Details', header: true }], + ...failures.map((failure) => [ + '❌', + failure.grouping, + failure.moduleName, + `Line Number: ${failure.lineNumber.toString()} - ${failure.type}` + ]), + ...successes.map((success) => [ + '✅', + success.grouping, + success.moduleName, + '-' + ]) + ]) + .write() + + if (isError) { + failures.forEach((failure) => { + if (failure.type === failureTypes.improperFormat) { + core.error('The module name should be enclosed with backticks', { + title: 'Improper format', + file: basePathEcosystemDocFile, + startLine: failure.lineNumber + }) + } else if (failure.type === failureTypes.outOfOrderItem) { + core.error(`${failure.moduleName} not listed in alphabetical order`, { + title: 'Out of Order', + file: basePathEcosystemDocFile, + startLine: failure.lineNumber + }) + } else { + core.error('Unknown error') + } + }) - if (hasImproperFormat === true) { - core.setFailed('Some ecosystem modules are improperly formatted.') + core.setFailed('Failed when linting Ecosystem.md') } } -function compare(current, previous) { +function compare (current, previous) { return previous.localeCompare( current, 'en', - {sensitivity: 'base'} + { sensitivity: 'base' } ) } diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index 75c7177cd48..e4aa09c4ee3 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -1,12 +1,6 @@ name: Lint Ecosystem Order on: - push: - branches-ignore: - - master - - main - paths: - - "**/Ecosystem.md" pull_request: branches: - master @@ -31,7 +25,7 @@ jobs: - name: Lint Doc uses: actions/github-script@v6 with: + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const script = require('./.github/scripts/lint-ecosystem.js') await script({ core }) - From 7ea423fca6793697178ddf484fe283955020eb0a Mon Sep 17 00:00:00 2001 From: Noor Eldeen Salah Date: Wed, 21 Sep 2022 08:07:17 +0200 Subject: [PATCH 0092/1295] docs(ecosystem): Add `@fastify/one-line-logger` (#4293) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 3fa6d598e6c..707190f9ac8 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -80,6 +80,8 @@ section. [Next](https://github.com/zeit/next.js/). - [`@fastify/oauth2`](https://github.com/fastify/fastify-oauth2) Wrap around [`simple-oauth2`](https://github.com/lelylan/simple-oauth2). +- [`@fastify/one-line-logger`](https://github.com/fastify/one-line-logger) Helps + Formatting fastify's logs into a nice one line message - [`@fastify/postgres`](https://github.com/fastify/fastify-postgres) Fastify PostgreSQL connection plugin, with this you can share the same PostgreSQL connection pool in every part of your server. From 2797ccda2f589c4e8c3990221080b8aa744bf5b6 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 21 Sep 2022 09:00:03 +0100 Subject: [PATCH 0093/1295] docs(ecosystem): capitalization fixes (#4294) --- docs/Guides/Ecosystem.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 707190f9ac8..6889b018594 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -80,8 +80,8 @@ section. [Next](https://github.com/zeit/next.js/). - [`@fastify/oauth2`](https://github.com/fastify/fastify-oauth2) Wrap around [`simple-oauth2`](https://github.com/lelylan/simple-oauth2). -- [`@fastify/one-line-logger`](https://github.com/fastify/one-line-logger) Helps - Formatting fastify's logs into a nice one line message +- [`@fastify/one-line-logger`](https://github.com/fastify/one-line-logger) Formats + Fastify's logs into a nice one-line message. - [`@fastify/postgres`](https://github.com/fastify/fastify-postgres) Fastify PostgreSQL connection plugin, with this you can share the same PostgreSQL connection pool in every part of your server. From 1c49f3490d3d4696f30738f629c20e1b5b22b041 Mon Sep 17 00:00:00 2001 From: Cristian Teodorescu <81955552+CristiTeo@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:02:40 +0300 Subject: [PATCH 0094/1295] chore: add slow down plugin to comunity plugins (#4292) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 6889b018594..07e0c08398a 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -517,6 +517,8 @@ section. `fastify-caching`. - [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik plugin, with this you can use slonik in every part of your server. +- [`fastify-slow-down`](https://github.com/nearform/fastify-slow-down) A plugin + to delay the response from the server. - [`fastify-socket.io`](https://github.com/alemagio/fastify-socket.io) a Socket.io plugin for Fastify. - [`fastify-split-validator`](https://github.com/MetCoder95/fastify-split-validator) From 8e985b46b84c94e7a3fb44d87e66e16667b30828 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 23 Sep 2022 14:53:43 +0800 Subject: [PATCH 0095/1295] fix: custom validator should not mutate headers schema (#4295) * fix: custom validator should not mutate headers schema * fix: checking to function * chore: property name improve --- lib/route.js | 3 ++- lib/schema-controller.js | 8 +++++++- lib/validation.js | 7 ++++--- test/internals/validation.test.js | 15 +++++++++++++++ test/schema-feature.test.js | 25 +++++++++++++++++++++++++ test/schema-validation.test.js | 19 +++++++++++++++++++ 6 files changed, 72 insertions(+), 5 deletions(-) diff --git a/lib/route.js b/lib/route.js index a782c97e85e..0e686fc5966 100644 --- a/lib/route.js +++ b/lib/route.js @@ -327,7 +327,8 @@ function buildRouting (options) { schemaController.setupValidator(this[kOptions]) } try { - compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler) + const isCustom = typeof opts?.validatorCompiler === 'function' || schemaController.isCustomValidatorCompiler + compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler, isCustom) } catch (error) { throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message) } diff --git a/lib/schema-controller.js b/lib/schema-controller.js index 1484019cb92..78c4437678f 100644 --- a/lib/schema-controller.js +++ b/lib/schema-controller.js @@ -25,7 +25,9 @@ function buildSchemaController (parentSchemaCtrl, opts) { const option = { bucket: (opts && opts.bucket) || buildSchemas, - compilersFactory + compilersFactory, + isCustomValidatorCompiler: typeof opts?.compilersFactory?.buildValidator === 'function', + isCustomSerializerCompiler: typeof opts?.compilersFactory?.buildValidator === 'function' } return new SchemaController(undefined, option) @@ -37,6 +39,8 @@ class SchemaController { this.addedSchemas = false this.compilersFactory = this.opts.compilersFactory + this.isCustomValidatorCompiler = this.opts.isCustomValidatorCompiler || false + this.isCustomSerializerCompiler = this.opts.isCustomSerializerCompiler || false if (parent) { this.schemaBucket = this.opts.bucket(parent.getSchemas()) @@ -65,10 +69,12 @@ class SchemaController { // Schema Controller compilers holder setValidatorCompiler (validatorCompiler) { this.validatorCompiler = validatorCompiler + this.isCustomValidatorCompiler = true } setSerializerCompiler (serializerCompiler) { this.serializerCompiler = serializerCompiler + this.isCustomSerializerCompiler = true } getValidatorCompiler () { diff --git a/lib/validation.js b/lib/validation.js index 46ed81c8043..939b5c53863 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -32,7 +32,7 @@ function compileSchemasForSerialization (context, compile) { }, {}) } -function compileSchemasForValidation (context, compile) { +function compileSchemasForValidation (context, compile, isCustom) { const { schema } = context if (!schema) { return @@ -41,8 +41,9 @@ function compileSchemasForValidation (context, compile) { const { method, url } = context.config || {} const headers = schema.headers - if (headers && Object.getPrototypeOf(headers) !== Object.prototype) { - // do not mess with non-literals, e.g. Joi schemas + // the or part is used for backward compatibility + if (headers && (isCustom || Object.getPrototypeOf(headers) !== Object.prototype)) { + // do not mess with schema when custom validator applied, e.g. Joi, Typebox context[headersSchema] = compile({ schema: headers, method, url, httpPart: 'headers' }) } else if (headers) { // The header keys are case insensitive diff --git a/test/internals/validation.test.js b/test/internals/validation.test.js index 320f39bf5dd..75e99597ea3 100644 --- a/test/internals/validation.test.js +++ b/test/internals/validation.test.js @@ -268,6 +268,21 @@ test('build schema - headers are not lowercased in case of custom object', t => }) }) +test('build schema - headers are not lowercased in case of custom validator provided', t => { + t.plan(1) + + class Headers {} + const opts = { + schema: { + headers: new Headers() + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { + t.type(schema, Headers) + return () => {} + }, true) +}) + test('build schema - uppercased headers are not included', t => { t.plan(1) const opts = { diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index af82cd46514..35c9f46861f 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -1779,3 +1779,28 @@ test('Should return a human-friendly error if response status codes are not spec t.match(err.message, 'Failed building the serialization schema for GET: /, due to error response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') }) }) + +test('setSchemaController: custom validator instance should not mutate headers schema', async t => { + t.plan(2) + class Headers {} + const fastify = Fastify() + + fastify.setSchemaController({ + compilersFactory: { + buildValidator: function () { + return ({ schema, method, url, httpPart }) => { + t.type(schema, Headers) + return () => {} + } + } + } + }) + + fastify.get('/', { + schema: { + headers: new Headers() + } + }, () => {}) + + await fastify.ready() +}) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index 05ce41be5a6..e245a17342d 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -1017,3 +1017,22 @@ test("The same $id in route's schema must not overwrite others", t => { t.same(res.payload, 'ok') }) }) + +test('Custom validator compiler should not mutate schema', async t => { + t.plan(2) + class Headers {} + const fastify = Fastify() + + fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { + t.type(schema, Headers) + return () => {} + }) + + fastify.get('/', { + schema: { + headers: new Headers() + } + }, () => {}) + + await fastify.ready() +}) From 2c97c4c7ad084c228026575eda3a14abbf2876eb Mon Sep 17 00:00:00 2001 From: Kalven Schraut <30308012+kalvenschraut@users.noreply.github.com> Date: Fri, 23 Sep 2022 02:58:37 -0500 Subject: [PATCH 0096/1295] feat: parse request body for http SEARCH requests (#4298) --- docs/Reference/Routes.md | 4 +- lib/handleRequest.js | 4 +- test/search.test.js | 199 +++++++++++++++++++++++++++++++++------ 3 files changed, 173 insertions(+), 34 deletions(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index d3b39f32458..90f17034834 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -41,8 +41,8 @@ fastify.route(options) need to be in [JSON Schema](https://json-schema.org/) format, check [here](./Validation-and-Serialization.md) for more info. - * `body`: validates the body of the request if it is a POST, PUT, or PATCH - method. + * `body`: validates the body of the request if it is a POST, PUT, PATCH, + TRACE, or SEARCH method. * `querystring` or `query`: validates the querystring. This can be a complete JSON Schema object, with the property `type` of `object` and `properties` object of parameters, or simply the values of what would be contained in the diff --git a/lib/handleRequest.js b/lib/handleRequest.js index a8edccc935f..ec9d3decff5 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -18,14 +18,14 @@ function handleRequest (err, request, reply) { const method = request.raw.method const headers = request.headers - if (method === 'GET' || method === 'HEAD' || method === 'SEARCH') { + if (method === 'GET' || method === 'HEAD') { handler(request, reply) return } const contentType = headers['content-type'] - if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE') { + if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH') { if (contentType === undefined) { if ( headers['transfer-encoding'] === undefined && diff --git a/test/search.test.js b/test/search.test.js index 68a8414df40..c0f1ba2bdf3 100644 --- a/test/search.test.js +++ b/test/search.test.js @@ -1,18 +1,17 @@ 'use strict' const t = require('tap') +const sget = require('simple-get').concat const test = t.test const fastify = require('..')() const schema = { - schema: { - response: { - '2xx': { - type: 'object', - properties: { - hello: { - type: 'string' - } + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' } } } @@ -20,35 +19,45 @@ const schema = { } const querySchema = { - schema: { - querystring: { - type: 'object', - properties: { - hello: { - type: 'integer' - } + querystring: { + type: 'object', + properties: { + hello: { + type: 'integer' } } } } const paramsSchema = { - schema: { - params: { - type: 'object', - properties: { - foo: { - type: 'string' - }, - test: { - type: 'integer' - } + params: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + test: { + type: 'integer' } } } } -test('shorthand - search', t => { +const bodySchema = { + body: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + test: { + type: 'integer' + } + } + } +} + +test('search', t => { t.plan(1) try { fastify.route({ @@ -65,13 +74,13 @@ test('shorthand - search', t => { } }) -test('shorthand - search params', t => { +test('search, params schema', t => { t.plan(1) try { fastify.route({ method: 'SEARCH', url: '/params/:foo/:test', - paramsSchema, + schema: paramsSchema, handler: function (request, reply) { reply.code(200).send(request.params) } @@ -82,13 +91,13 @@ test('shorthand - search params', t => { } }) -test('shorthand - get, querystring schema', t => { +test('search, querystring schema', t => { t.plan(1) try { fastify.route({ method: 'SEARCH', url: '/query', - querySchema, + schema: querySchema, handler: function (request, reply) { reply.code(200).send(request.query) } @@ -98,3 +107,133 @@ test('shorthand - get, querystring schema', t => { t.fail() } }) + +test('search, body schema', t => { + t.plan(1) + try { + fastify.route({ + method: 'SEARCH', + url: '/body', + schema: bodySchema, + handler: function (request, reply) { + reply.code(200).send(request.body) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const url = `http://localhost:${fastify.server.address().port}` + + test('request - search', t => { + t.plan(4) + sget({ + method: 'SEARCH', + url + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) + + test('request search params schema', t => { + t.plan(4) + sget({ + method: 'SEARCH', + url: `${url}/params/world/123` + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { foo: 'world', test: 123 }) + }) + }) + + test('request search params schema error', t => { + t.plan(3) + sget({ + method: 'SEARCH', + url: `${url}/params/world/string` + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 400) + t.same(JSON.parse(body), { + error: 'Bad Request', + message: 'params/test must be integer', + statusCode: 400 + }) + }) + }) + + test('request search querystring schema', t => { + t.plan(4) + sget({ + method: 'SEARCH', + url: `${url}/query?hello=123` + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { hello: 123 }) + }) + }) + + test('request search querystring schema error', t => { + t.plan(3) + sget({ + method: 'SEARCH', + url: `${url}/query?hello=world` + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 400) + t.same(JSON.parse(body), { + error: 'Bad Request', + message: 'querystring/hello must be integer', + statusCode: 400 + }) + }) + }) + + test('request search body schema', t => { + t.plan(4) + const replyBody = { foo: 'bar', test: 5 } + sget({ + method: 'SEARCH', + url: `${url}/body`, + body: JSON.stringify(replyBody), + headers: { 'content-type': 'application/json' } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), replyBody) + }) + }) + + test('request search body schema error', t => { + t.plan(4) + sget({ + method: 'SEARCH', + url: `${url}/body`, + body: JSON.stringify({ foo: 'bar', test: 'test' }), + headers: { 'content-type': 'application/json' } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 400) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { + error: 'Bad Request', + message: 'body/test must be integer', + statusCode: 400 + }) + }) + }) +}) From 450416e834838b85d9189de8091a243b854ad6a3 Mon Sep 17 00:00:00 2001 From: Denis Yakovenko Date: Sun, 25 Sep 2022 10:45:37 +0300 Subject: [PATCH 0097/1295] chore: fix typo in the comment (#4301) --- lib/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index 8fdc7d241b2..b68c1126649 100644 --- a/lib/context.js +++ b/lib/context.js @@ -15,7 +15,7 @@ const { kReplySerializeWeakMap } = require('./symbols.js') -// Objects that holds the context of every request +// Object that holds the context of every request // Every route holds an instance of this object. function Context ({ schema, From 9afd5882529297de65c35720bed6a50ac7b47f0e Mon Sep 17 00:00:00 2001 From: Sami Al-Dury <57627858+samialdury@users.noreply.github.com> Date: Sun, 25 Sep 2022 22:01:21 +0200 Subject: [PATCH 0098/1295] docs(type-providers): replace FastifyLoggerInstance with FastifyBaseLogger (#4304) --- docs/Reference/Type-Providers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 4cfe8dcd32d..5884c34f4dc 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -218,7 +218,7 @@ server.listen({ port: 3000 }) import { Type } from '@sinclair/typebox' import { FastifyInstance, - FastifyLoggerInstance, + FastifyBaseLogger, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault @@ -229,7 +229,7 @@ type FastifyTypebox = FastifyInstance< RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, - FastifyLoggerInstance, + FastifyBaseLogger, TypeBoxTypeProvider >; From c849d6ccbae313a4a9f4ca007b7f4154473a7b1a Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 26 Sep 2022 10:06:19 +0200 Subject: [PATCH 0099/1295] docs(contributing): clarify teams for joiners (#4303) --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68d1a8b5a68..94ecb08182b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,11 +85,13 @@ The Fastify structure is detailed in the [GOVERNANCE](GOVERNANCE.md) document. Welcome to the team! We are happy to have you. Before you start, please complete the following tasks: 1. Set up 2 factor authentication for GitHub and NPM - - [GitHub + - [GitHub 2FA](https://help.github.com/en/articles/securing-your-account-with-two-factor-authentication-2fa) - - [NPM 2FA](https://docs.npmjs.com/about-two-factor-authentication) + - [NPM 2FA](https://docs.npmjs.com/about-two-factor-authentication) 2. Choose which team to join *(more than one is ok!)* based on how you want to help. + - Core team: maintains the Fastify core and its documentation + - Plugins team: maintains the Fastify's plugins and its ecosystem 3. Open a pull request to [`fastify/fastify:HEAD`](https://github.com/fastify/fastify/pulls) that adds your name, username, and email to the team you have choosen in the From ca1aa699383f511cbc1b944b8732e99fc18f7b49 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Tue, 27 Sep 2022 12:53:17 -0400 Subject: [PATCH 0100/1295] test: add number coersion related tests (#4297) --- lib/server.js | 2 +- test/reply-code.test.js | 59 +++++++++++++++++++++++++++++++++++++++++ test/server.test.js | 54 +++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 test/reply-code.test.js create mode 100644 test/server.test.js diff --git a/lib/server.js b/lib/server.js index be997973f3b..ca1107abe05 100644 --- a/lib/server.js +++ b/lib/server.js @@ -332,7 +332,7 @@ function normalizeListenArgs (args) { function normalizePort (firstArg) { const port = Number(firstArg) - return port >= 0 && !Number.isNaN(port) ? port : 0 + return port >= 0 && !Number.isNaN(port) && Number.isInteger(port) ? port : 0 } function logServerAddress (server) { diff --git a/test/reply-code.test.js b/test/reply-code.test.js new file mode 100644 index 00000000000..1e709322e4d --- /dev/null +++ b/test/reply-code.test.js @@ -0,0 +1,59 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') + +test('code should handle null/undefined/float', t => { + t.plan(8) + + const fastify = Fastify() + + fastify.get('/null', function (request, reply) { + reply.status(null).send() + }) + + fastify.get('/undefined', function (request, reply) { + reply.status(undefined).send() + }) + + fastify.get('/404.5', function (request, reply) { + reply.status(404.5).send() + }) + + fastify.inject({ + method: 'GET', + url: '/null' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 500) + t.same(res.json(), { + statusCode: 500, + code: 'FST_ERR_BAD_STATUS_CODE', + error: 'Internal Server Error', + message: 'Called reply with an invalid status code: null' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/undefined' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 500) + t.same(res.json(), { + statusCode: 500, + code: 'FST_ERR_BAD_STATUS_CODE', + error: 'Internal Server Error', + message: 'Called reply with an invalid status code: undefined' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/404.5' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 404) + }) +}) diff --git a/test/server.test.js b/test/server.test.js new file mode 100644 index 00000000000..59194914304 --- /dev/null +++ b/test/server.test.js @@ -0,0 +1,54 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') + +test('listen should accept null port', t => { + t.plan(1) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: null }, (err) => { + t.error(err) + }) +}) + +test('listen should accept undefined port', t => { + t.plan(1) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: undefined }, (err) => { + t.error(err) + }) +}) + +test('listen should accept stringified number port', t => { + t.plan(1) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: '1234' }, (err) => { + t.error(err) + }) +}) + +test('listen should reject string port', async (t) => { + t.plan(2) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + try { + await fastify.listen({ port: 'hello-world' }) + } catch (error) { + t.same(error.message, 'options.port should be >= 0 and < 65536. Received hello-world.') + } + + try { + await fastify.listen({ port: '1234hello' }) + } catch (error) { + t.same(error.message, 'options.port should be >= 0 and < 65536. Received 1234hello.') + } +}) From 7ffefaf272e1b0da5c0889e0c7c54ac8203756f2 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Wed, 28 Sep 2022 00:04:24 +0200 Subject: [PATCH 0101/1295] feat: add routeSchema and routeConfig + switching context handling (#4216) * bumped v5.0.0-dev Signed-off-by: Matteo Collina * feat: initial implementation of context handling switch * refactor: hide context for reply/request * fix: remove trailling space * tests: adjust tests based on new way of handling context * refactor: rename route context symbol * feat: expose route context through request object * chore: add temporary context * refactor: reverting log-level * refactor: export config/schema into its own naming * docs: update docs reflecting changes to request/reply * tests: extend coverage * refactor: initial cleanup * tests: remove todo and adjust config test * chore: emit warning on access to Request#context * refactor: apply review * docs: restore context docs Signed-off-by: Matteo Collina Co-authored-by: Matteo Collina Co-authored-by: KaKa --- docs/Reference/Reply.md | 10 +-- docs/Reference/Request.md | 10 ++- lib/contentTypeParser.js | 5 +- lib/context.js | 28 ++++++- lib/error-handler.js | 10 ++- lib/handleRequest.js | 24 +++--- lib/reply.js | 72 +++++++++-------- lib/request.js | 54 ++++++++----- lib/route.js | 10 ++- lib/symbols.js | 2 + lib/warnings.js | 2 + test/context-config.test.js | 3 +- test/handler-context.test.js | 42 +++------- test/internals/contentTypeParser.test.js | 6 +- test/internals/handleRequest.test.js | 7 +- test/internals/reply-serialize.test.js | 14 ++-- test/internals/reply.test.js | 19 ++--- test/internals/request-validate.test.js | 14 ++-- test/internals/request.test.js | 60 +++++++++++++- test/plugin.test.js | 2 +- test/router-options.test.js | 99 ++++++++---------------- 21 files changed, 283 insertions(+), 210 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 2e601dbe5ae..886cc150881 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -7,22 +7,22 @@ - [.statusCode](#statuscode) - [.server](#server) - [.header(key, value)](#headerkey-value) - - [set-cookie](#set-cookie) - [.headers(object)](#headersobject) - [.getHeader(key)](#getheaderkey) - [.getHeaders()](#getheaders) + - [set-cookie](#set-cookie) - [.removeHeader(key)](#removeheaderkey) - [.hasHeader(key)](#hasheaderkey) - [.trailer(key, function)](#trailerkey-function) - [.hasTrailer(key)](#hastrailerkey) - [.removeTrailer(key)](#removetrailerkey) - - [.redirect([code,] dest)](#redirectcode--dest) + - [.redirect([code ,] dest)](#redirectcode--dest) - [.callNotFound()](#callnotfound) - [.getResponseTime()](#getresponsetime) - [.type(contentType)](#typecontenttype) - - [.getSerializationFunction(schema | httpStatus)](#getserializationfunction) - - [.compileSerializationSchema(schema, httpStatus)](#compileserializationschema) - - [.serializeInput(data, [schema | httpStatus], [httpStatus])](#serializeinput) + - [.getSerializationFunction(schema | httpStatus)](#getserializationfunctionschema--httpstatus) + - [.compileSerializationSchema(schema, httpStatus)](#compileserializationschemaschema-httpstatus) + - [.serializeInput(data, [schema | httpStatus], [httpStatus])](#serializeinputdata-schema--httpstatus-httpstatus) - [.serializer(func)](#serializerfunc) - [.raw](#raw) - [.sent](#sent) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index d9f05ccad5c..93af618f9cf 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -35,6 +35,13 @@ Request is a core Fastify object containing the following fields: - `connection` - Deprecated, use `socket` instead. The underlying connection of the incoming request. - `socket` - the underlying connection of the incoming request +- `context` - A Fastify internal object. You should not use it directly or + modify it. It is useful to access one special key: + - `context.config` - The route [`config`](./Routes.md#routes-config) object. +- `routeSchema` - the scheme definition set for the router that is + handling the request +- `routeConfig` - The route [`config`](./Routes.md#routes-config) + object. - [.getValidationFunction(schema | httpPart)](#getvalidationfunction) - Returns a validation function for the specified schema or http part, if any of either are set or cached. @@ -48,9 +55,6 @@ Request is a core Fastify object containing the following fields: schema and returns the serialized payload. If the optional `httpPart` is provided, the function will use the serializer function given for that HTTP Status Code. Defaults to `null`. -- `context` - A Fastify internal object. You should not use it directly or - modify it. It is useful to access one special key: - - `context.config` - The route [`config`](./Routes.md#routes-config) object. ### Headers diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 60577ac3f12..3db0bdc686b 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -15,7 +15,8 @@ const { kRequestPayloadStream, kState, kTestInternals, - kReplyIsError + kReplyIsError, + kRouteContext } = require('./symbols') const { @@ -158,7 +159,7 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply rawBody( request, reply, - reply.context._parserOptions, + reply[kRouteContext]._parserOptions, parser, done ) diff --git a/lib/context.js b/lib/context.js index b68c1126649..91953f947c1 100644 --- a/lib/context.js +++ b/lib/context.js @@ -12,7 +12,8 @@ const { kContentTypeParser, kRouteByFastify, kRequestValidateWeakMap, - kReplySerializeWeakMap + kReplySerializeWeakMap, + kPublicRouteContext } = require('./symbols.js') // Object that holds the context of every request @@ -56,7 +57,10 @@ function Context ({ this[kFourOhFourContext] = null this.attachValidation = attachValidation this[kReplySerializerDefault] = replySerializer - this.schemaErrorFormatter = schemaErrorFormatter || server[kSchemaErrorFormatter] || defaultSchemaErrorFormatter + this.schemaErrorFormatter = + schemaErrorFormatter || + server[kSchemaErrorFormatter] || + defaultSchemaErrorFormatter this[kRouteByFastify] = isFastify this[kRequestValidateWeakMap] = null @@ -64,9 +68,29 @@ function Context ({ this.validatorCompiler = validatorCompiler || null this.serializerCompiler = serializerCompiler || null + // Route + Userland configurations for the route + this[kPublicRouteContext] = getPublicRouteContext(this) + this.server = server } +function getPublicRouteContext (context) { + return Object.create(null, { + schema: { + enumerable: true, + get () { + return context.schema + } + }, + config: { + enumerable: true, + get () { + return context.config + } + } + }) +} + function defaultSchemaErrorFormatter (errors, dataVar) { let text = '' const separator = ', ' diff --git a/lib/error-handler.js b/lib/error-handler.js index dcc64ececc2..190489b13da 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -3,7 +3,11 @@ const statusCodes = require('http').STATUS_CODES const wrapThenable = require('./wrapThenable') const { - kReplyHeaders, kReplyNextErrorHandler, kReplyIsRunningOnErrorHook, kReplyHasStatusCode + kReplyHeaders, + kReplyNextErrorHandler, + kReplyIsRunningOnErrorHook, + kReplyHasStatusCode, + kRouteContext } = require('./symbols.js') const { @@ -24,7 +28,7 @@ const rootErrorHandler = { function handleError (reply, error, cb) { reply[kReplyIsRunningOnErrorHook] = false - const context = reply.context + const context = reply[kRouteContext] if (reply[kReplyNextErrorHandler] === false) { fallbackErrorHandler(error, reply, function (reply, payload) { try { @@ -90,7 +94,7 @@ function fallbackErrorHandler (error, reply, cb) { const statusCode = reply.statusCode let payload try { - const serializerFn = getSchemaSerializer(reply.context, statusCode) + const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode) payload = (serializerFn === false) ? serializeError({ error: statusCodes[statusCode + ''], diff --git a/lib/handleRequest.js b/lib/handleRequest.js index ec9d3decff5..e74c3ae53f6 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -4,7 +4,8 @@ const { validate: validateSchema } = require('./validation') const { hookRunner, hookIterator } = require('./hooks') const wrapThenable = require('./wrapThenable') const { - kReplyIsError + kReplyIsError, + kRouteContext } = require('./symbols') function handleRequest (err, request, reply) { @@ -17,6 +18,7 @@ function handleRequest (err, request, reply) { const method = request.raw.method const headers = request.headers + const context = request[kRouteContext] if (method === 'GET' || method === 'HEAD') { handler(request, reply) @@ -33,10 +35,10 @@ function handleRequest (err, request, reply) { ) { // Request has no body to parse handler(request, reply) } else { - reply.context.contentTypeParser.run('', handler, request, reply) + context.contentTypeParser.run('', handler, request, reply) } } else { - reply.context.contentTypeParser.run(contentType, handler, request, reply) + context.contentTypeParser.run(contentType, handler, request, reply) } return } @@ -49,7 +51,7 @@ function handleRequest (err, request, reply) { headers['content-length'] !== undefined ) ) { - reply.context.contentTypeParser.run(contentType, handler, request, reply) + context.contentTypeParser.run(contentType, handler, request, reply) } else { handler(request, reply) } @@ -62,9 +64,9 @@ function handleRequest (err, request, reply) { function handler (request, reply) { try { - if (reply.context.preValidation !== null) { + if (request[kRouteContext].preValidation !== null) { hookRunner( - reply.context.preValidation, + request[kRouteContext].preValidation, hookIterator, request, reply, @@ -87,9 +89,9 @@ function preValidationCallback (err, request, reply) { return } - const result = validateSchema(reply.context, request) + const result = validateSchema(reply[kRouteContext], request) if (result) { - if (reply.context.attachValidation === false) { + if (reply[kRouteContext].attachValidation === false) { reply.send(result) return } @@ -98,9 +100,9 @@ function preValidationCallback (err, request, reply) { } // preHandler hook - if (reply.context.preHandler !== null) { + if (request[kRouteContext].preHandler !== null) { hookRunner( - reply.context.preHandler, + request[kRouteContext].preHandler, hookIterator, request, reply, @@ -123,7 +125,7 @@ function preHandlerCallback (err, request, reply) { let result try { - result = reply.context.handler(request, reply) + result = request[kRouteContext].handler(request, reply) } catch (err) { reply[kReplyIsError] = true reply.send(err) diff --git a/lib/reply.js b/lib/reply.js index 01c846ee45f..9420f78d78a 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -20,7 +20,8 @@ const { kSchemaResponse, kReplySerializeWeakMap, kSchemaController, - kOptions + kOptions, + kRouteContext } = require('./symbols.js') const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks') @@ -63,14 +64,21 @@ function Reply (res, request, log) { Reply.props = [] Object.defineProperties(Reply.prototype, { + [kRouteContext]: { + get () { + return this.request[kRouteContext] + } + }, + // TODO: remove once v5 is done + // Is temporary to avoid constant conflicts between `next` and `main` context: { get () { - return this.request.context + return this.request[kRouteContext] } }, server: { get () { - return this.request.context.server + return this.request[kRouteContext].server } }, sent: { @@ -308,9 +316,9 @@ Reply.prototype.getSerializationFunction = function (schemaOrStatus) { let serialize if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') { - serialize = this.context[kSchemaResponse]?.[schemaOrStatus] + serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus] } else if (typeof schemaOrStatus === 'object') { - serialize = this.context[kReplySerializeWeakMap]?.get(schemaOrStatus) + serialize = this[kRouteContext][kReplySerializeWeakMap]?.get(schemaOrStatus) } return serialize @@ -321,11 +329,11 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null const { method, url } = request // Check if serialize function already compiled - if (this.context[kReplySerializeWeakMap]?.has(schema)) { - return this.context[kReplySerializeWeakMap].get(schema) + if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) { + return this[kRouteContext][kReplySerializeWeakMap].get(schema) } - const serializerCompiler = this.context.serializerCompiler || + const serializerCompiler = this[kRouteContext].serializerCompiler || this.server[kSchemaController].serializerCompiler || ( // We compile the schemas if no custom serializerCompiler is provided @@ -346,11 +354,11 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null // if it is not used // TODO: Explore a central cache for all the schemas shared across // encapsulated contexts - if (this.context[kReplySerializeWeakMap] == null) { - this.context[kReplySerializeWeakMap] = new WeakMap() + if (this[kRouteContext][kReplySerializeWeakMap] == null) { + this[kRouteContext][kReplySerializeWeakMap] = new WeakMap() } - this.context[kReplySerializeWeakMap].set(schema, serializeFn) + this[kRouteContext][kReplySerializeWeakMap].set(schema, serializeFn) return serializeFn } @@ -362,13 +370,13 @@ Reply.prototype.serializeInput = function (input, schema, httpStatus) { : httpStatus if (httpStatus != null) { - serialize = this.context[kSchemaResponse]?.[httpStatus] + serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus] if (serialize == null) throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus) } else { // Check if serialize function already compiled - if (this.context[kReplySerializeWeakMap]?.has(schema)) { - serialize = this.context[kReplySerializeWeakMap].get(schema) + if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) { + serialize = this[kRouteContext][kReplySerializeWeakMap].get(schema) } else { serialize = this.compileSerializationSchema(schema, httpStatus) } @@ -381,10 +389,10 @@ Reply.prototype.serialize = function (payload) { if (this[kReplySerializer] !== null) { return this[kReplySerializer](payload) } else { - if (this.context && this.context[kReplySerializerDefault]) { - return this.context[kReplySerializerDefault](payload, this.raw.statusCode) + if (this[kRouteContext] && this[kRouteContext][kReplySerializerDefault]) { + return this[kRouteContext][kReplySerializerDefault](payload, this.raw.statusCode) } else { - return serialize(this.context, payload, this.raw.statusCode) + return serialize(this[kRouteContext], payload, this.raw.statusCode) } } } @@ -450,9 +458,9 @@ Reply.prototype.then = function (fulfilled, rejected) { } function preserializeHook (reply, payload) { - if (reply.context.preSerialization !== null) { + if (reply[kRouteContext].preSerialization !== null) { onSendHookRunner( - reply.context.preSerialization, + reply[kRouteContext].preSerialization, reply.request, reply, payload, @@ -472,10 +480,10 @@ function preserializeHookEnd (err, request, reply, payload) { try { if (reply[kReplySerializer] !== null) { payload = reply[kReplySerializer](payload) - } else if (reply.context && reply.context[kReplySerializerDefault]) { - payload = reply.context[kReplySerializerDefault](payload, reply.raw.statusCode) + } else if (reply[kRouteContext] && reply[kRouteContext][kReplySerializerDefault]) { + payload = reply[kRouteContext][kReplySerializerDefault](payload, reply.raw.statusCode) } else { - payload = serialize(reply.context, payload, reply.raw.statusCode) + payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode) } } catch (e) { wrapSeralizationError(e, reply) @@ -487,13 +495,13 @@ function preserializeHookEnd (err, request, reply, payload) { } function wrapSeralizationError (error, reply) { - error.serialization = reply.context.config + error.serialization = reply[kRouteContext].config } function onSendHook (reply, payload) { - if (reply.context.onSend !== null) { + if (reply[kRouteContext].onSend !== null) { onSendHookRunner( - reply.context.onSend, + reply[kRouteContext].onSend, reply.request, reply, payload, @@ -660,10 +668,10 @@ function sendStreamTrailer (payload, res, reply) { } function onErrorHook (reply, error, cb) { - if (reply.context.onError !== null && !reply[kReplyNextErrorHandler]) { + if (reply[kRouteContext].onError !== null && !reply[kReplyNextErrorHandler]) { reply[kReplyIsRunningOnErrorHook] = true onSendHookRunner( - reply.context.onError, + reply[kRouteContext].onError, reply.request, reply, error, @@ -682,7 +690,7 @@ function setupResponseListeners (reply) { reply.raw.removeListener('finish', onResFinished) reply.raw.removeListener('error', onResFinished) - const ctx = reply.context + const ctx = reply[kRouteContext] if (ctx && ctx.onResponse !== null) { hookRunner( @@ -759,18 +767,18 @@ function buildReply (R) { } function notFound (reply) { - if (reply.context[kFourOhFourContext] === null) { + if (reply[kRouteContext][kFourOhFourContext] === null) { reply.log.warn('Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.') reply.code(404).send('404 Not Found') return } - reply.request.context = reply.context[kFourOhFourContext] + reply.request[kRouteContext] = reply[kRouteContext][kFourOhFourContext] // preHandler hook - if (reply.context.preHandler !== null) { + if (reply[kRouteContext].preHandler !== null) { hookRunner( - reply.context.preHandler, + reply[kRouteContext].preHandler, hookIterator, reply.request, reply, diff --git a/lib/request.js b/lib/request.js index b86990c4820..ab47c6f0300 100644 --- a/lib/request.js +++ b/lib/request.js @@ -11,7 +11,9 @@ const { kSchemaQuerystring, kSchemaController, kOptions, - kRequestValidateWeakMap + kRequestValidateWeakMap, + kRouteContext, + kPublicRouteContext } = require('./symbols') const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors') @@ -25,7 +27,7 @@ const HTTP_PART_SYMBOL_MAP = { function Request (id, params, req, query, log, context) { this.id = id - this.context = context + this[kRouteContext] = context this.params = params this.raw = req this.query = query @@ -66,7 +68,7 @@ function buildRegularRequest (R) { const props = [...R.props] function _Request (id, params, req, query, log, context) { this.id = id - this.context = context + this[kRouteContext] = context this.params = params this.raw = req this.query = query @@ -139,7 +141,7 @@ function buildRequestWithTrustProxy (R, trustProxy) { Object.defineProperties(Request.prototype, { server: { get () { - return this.context.server + return this[kRouteContext].server } }, url: { @@ -152,19 +154,35 @@ Object.defineProperties(Request.prototype, { return this.raw.method } }, + context: { + get () { + warning.emit('FSTDEP012') + return this[kRouteContext] + } + }, routerPath: { get () { - return this.context.config.url + return this[kRouteContext].config.url } }, routerMethod: { get () { - return this.context.config.method + return this[kRouteContext].config.method + } + }, + routeConfig: { + get () { + return this[kRouteContext][kPublicRouteContext].config + } + }, + routeSchema: { + get () { + return this[kRouteContext][kPublicRouteContext].schema } }, is404: { get () { - return this.context.config.url === undefined + return this[kRouteContext].config.url === undefined } }, connection: { @@ -215,9 +233,9 @@ Object.defineProperties(Request.prototype, { value: function (httpPartOrSchema) { if (typeof httpPartOrSchema === 'string') { const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema] - return this.context[symbol] + return this[kRouteContext][symbol] } else if (typeof httpPartOrSchema === 'object') { - return this.context[kRequestValidateWeakMap]?.get(httpPartOrSchema) + return this[kRouteContext][kRequestValidateWeakMap]?.get(httpPartOrSchema) } } }, @@ -225,11 +243,11 @@ Object.defineProperties(Request.prototype, { value: function (schema, httpPart = null) { const { method, url } = this - if (this.context[kRequestValidateWeakMap]?.has(schema)) { - return this.context[kRequestValidateWeakMap].get(schema) + if (this[kRouteContext][kRequestValidateWeakMap]?.has(schema)) { + return this[kRouteContext][kRequestValidateWeakMap].get(schema) } - const validatorCompiler = this.context.validatorCompiler || + const validatorCompiler = this[kRouteContext].validatorCompiler || this.server[kSchemaController].validatorCompiler || ( // We compile the schemas if no custom validatorCompiler is provided @@ -250,11 +268,11 @@ Object.defineProperties(Request.prototype, { // if it is not used // TODO: Explore a central cache for all the schemas shared across // encapsulated contexts - if (this.context[kRequestValidateWeakMap] == null) { - this.context[kRequestValidateWeakMap] = new WeakMap() + if (this[kRouteContext][kRequestValidateWeakMap] == null) { + this[kRouteContext][kRequestValidateWeakMap] = new WeakMap() } - this.context[kRequestValidateWeakMap].set(schema, validateFn) + this[kRouteContext][kRequestValidateWeakMap].set(schema, validateFn) return validateFn } @@ -268,7 +286,7 @@ Object.defineProperties(Request.prototype, { if (symbol) { // Validate using the HTTP Request Part schema - validate = this.context[symbol] + validate = this[kRouteContext][symbol] } // We cannot compile if the schema is missed @@ -280,8 +298,8 @@ Object.defineProperties(Request.prototype, { } if (validate == null) { - if (this.context[kRequestValidateWeakMap]?.has(schema)) { - validate = this.context[kRequestValidateWeakMap].get(schema) + if (this[kRouteContext][kRequestValidateWeakMap]?.has(schema)) { + validate = this[kRouteContext][kRequestValidateWeakMap].get(schema) } else { // We proceed to compile if there's no validate function yet validate = this.compileValidationSchema(schema, httpPart) diff --git a/lib/route.js b/lib/route.js index 0e686fc5966..e89a3c4fa49 100644 --- a/lib/route.js +++ b/lib/route.js @@ -8,7 +8,6 @@ const { supportedMethods } = require('./httpMethods') const { normalizeSchema } = require('./schemas') const { parseHeadOnSendHandlers } = require('./headRoute') const warning = require('./warnings') -const { kRequestAcceptVersion, kRouteByFastify } = require('./symbols') const { compileSchemasForValidation, @@ -37,7 +36,10 @@ const { kDisableRequestLogging, kSchemaErrorFormatter, kErrorHandler, - kHasBeenDecorated + kHasBeenDecorated, + kRequestAcceptVersion, + kRouteByFastify, + kRouteContext } = require('./symbols.js') const { buildErrorHandler } = require('./error-handler') @@ -494,8 +496,8 @@ function runPreParsing (err, request, reply) { request[kRequestPayloadStream] = request.raw - if (reply.context.preParsing !== null) { - preParsingHookRunner(reply.context.preParsing, request, reply, handleRequest) + if (request[kRouteContext].preParsing !== null) { + preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest) } else { handleRequest(null, request, reply) } diff --git a/lib/symbols.js b/lib/symbols.js index 23df25e7527..c82b4e05769 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -14,6 +14,8 @@ const keys = { kOptions: Symbol('fastify.options'), kDisableRequestLogging: Symbol('fastify.disableRequestLogging'), kPluginNameChain: Symbol('fastify.pluginNameChain'), + kRouteContext: Symbol('fastify.context'), + kPublicRouteContext: Symbol('fastify.routeOptions'), // Schema kSchemaController: Symbol('fastify.schemaController'), kSchemaHeaders: Symbol('headers-schema'), diff --git a/lib/warnings.js b/lib/warnings.js index f6bbbcafbdc..76d15e1513c 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -21,4 +21,6 @@ warning.create('FastifyDeprecation', 'FSTDEP010', 'Modifying the "reply.sent" pr warning.create('FastifyDeprecation', 'FSTDEP011', 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.') +warning.create('FastifyDeprecation', 'FSTDEP012', 'Request#context property access is deprecated. Please use "Request#routeConfig" or "Request#routeSchema" instead for accessing Route settings. The "Request#context" will be removed in `fastify@5`.') + module.exports = warning diff --git a/test/context-config.test.js b/test/context-config.test.js index f50acc2cb77..4f8592ed65b 100644 --- a/test/context-config.test.js +++ b/test/context-config.test.js @@ -2,6 +2,7 @@ const t = require('tap') const test = t.test +const { kRouteContext } = require('../lib/symbols') const Fastify = require('..') const schema = { @@ -13,7 +14,7 @@ const schema = { } function handler (req, reply) { - reply.send(reply.context.config) + reply.send(reply[kRouteContext].config) } test('config', t => { diff --git a/test/handler-context.test.js b/test/handler-context.test.js index 0836cfa8abd..048fe92d3b5 100644 --- a/test/handler-context.test.js +++ b/test/handler-context.test.js @@ -1,19 +1,9 @@ 'use strict' - -const http = require('http') const test = require('tap').test +const { kRouteContext } = require('../lib/symbols') const fastify = require('../') -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} - -test('handlers receive correct `this` context', (t) => { +test('handlers receive correct `this` context', async (t) => { t.plan(4) // simulate plugin that uses fastify-plugin @@ -32,32 +22,24 @@ test('handlers receive correct `this` context', (t) => { reply.send() }) - instance.listen({ port: 0 }, (err) => { - instance.server.unref() - if (err) t.threw(err) - t.ok(instance.foo) - t.equal(instance.foo, 'foo') + await instance.inject('/') - http.get(getUrl(instance), () => {}).on('error', t.threw) - }) + t.ok(instance.foo) + t.equal(instance.foo, 'foo') }) -test('handlers have access to the internal context', (t) => { +test('handlers have access to the internal context', async (t) => { t.plan(5) const instance = fastify() instance.get('/', { config: { foo: 'bar' } }, function (req, reply) { - t.ok(reply.context) - t.ok(reply.context.config) - t.type(reply.context.config, Object) - t.ok(reply.context.config.foo) - t.equal(reply.context.config.foo, 'bar') + t.ok(reply[kRouteContext]) + t.ok(reply[kRouteContext].config) + t.type(reply[kRouteContext].config, Object) + t.ok(reply[kRouteContext].config.foo) + t.equal(reply[kRouteContext].config.foo, 'bar') reply.send() }) - instance.listen({ port: 0 }, (err) => { - instance.server.unref() - if (err) t.threw(err) - http.get(getUrl(instance), () => {}).on('error', t.threw) - }) + await instance.inject('/') }) diff --git a/test/internals/contentTypeParser.test.js b/test/internals/contentTypeParser.test.js index a24f67e15fd..54547132bbd 100644 --- a/test/internals/contentTypeParser.test.js +++ b/test/internals/contentTypeParser.test.js @@ -4,7 +4,7 @@ const t = require('tap') const proxyquire = require('proxyquire') const test = t.test const { Readable } = require('stream') -const { kTestInternals } = require('../../lib/symbols') +const { kTestInternals, kRouteContext } = require('../../lib/symbols') const Request = require('../../lib/request') const Reply = require('../../lib/reply') @@ -50,7 +50,7 @@ test('rawBody function', t => { internals.rawBody( request, reply, - reply.context._parserOptions, + reply[kRouteContext]._parserOptions, parser, done ) @@ -103,7 +103,7 @@ test('Should support Webpack and faux modules', t => { internals.rawBody( request, reply, - reply.context._parserOptions, + reply[kRouteContext]._parserOptions, parser, done ) diff --git a/test/internals/handleRequest.test.js b/test/internals/handleRequest.test.js index ac16b9cc33f..6635a952fe8 100644 --- a/test/internals/handleRequest.test.js +++ b/test/internals/handleRequest.test.js @@ -5,6 +5,7 @@ const handleRequest = require('../../lib/handleRequest') const internals = require('../../lib/handleRequest')[Symbol.for('internals')] const Request = require('../../lib/request') const Reply = require('../../lib/reply') +const { kRouteContext } = require('../../lib/symbols') const buildSchema = require('../../lib/validation').compileSchemasForValidation const sget = require('simple-get').concat @@ -69,7 +70,7 @@ test('handler function - invalid schema', t => { buildSchema(context, schemaValidator) const request = { body: { hello: 'world' }, - context + [kRouteContext]: context } internals.handler(request, new Reply(res, request)) }) @@ -96,7 +97,7 @@ test('handler function - reply', t => { onError: [] } buildSchema(context, schemaValidator) - internals.handler({}, new Reply(res, { context })) + internals.handler({ [kRouteContext]: context }, new Reply(res, { [kRouteContext]: context })) }) test('handler function - preValidationCallback with finished response', t => { @@ -121,7 +122,7 @@ test('handler function - preValidationCallback with finished response', t => { onError: [] } buildSchema(context, schemaValidator) - internals.handler({}, new Reply(res, { context })) + internals.handler({ [kRouteContext]: context }, new Reply(res, { [kRouteContext]: context })) }) test('request should be defined in onSend Hook on post request with content type application/json', t => { diff --git a/test/internals/reply-serialize.test.js b/test/internals/reply-serialize.test.js index efab8b95dca..66ecd7aee9d 100644 --- a/test/internals/reply-serialize.test.js +++ b/test/internals/reply-serialize.test.js @@ -1,7 +1,7 @@ 'use strict' const { test } = require('tap') -const { kReplySerializeWeakMap } = require('../../lib/symbols') +const { kReplySerializeWeakMap, kRouteContext } = require('../../lib/symbols') const Fastify = require('../../fastify') function getDefaultSchema () { @@ -172,9 +172,9 @@ test('Reply#compileSerializationSchema', t => { fastify.get('/', (req, reply) => { const input = { hello: 'world' } - t.equal(reply.context[kReplySerializeWeakMap], null) + t.equal(reply[kRouteContext][kReplySerializeWeakMap], null) t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) - t.type(reply.context[kReplySerializeWeakMap], WeakMap) + t.type(reply[kRouteContext][kReplySerializeWeakMap], WeakMap) t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) reply.send({ hello: 'world' }) @@ -352,9 +352,9 @@ test('Reply#getSerializationFunction', t => { fastify.get('/', (req, reply) => { t.notOk(reply.getSerializationFunction(getDefaultSchema())) - t.equal(reply.context[kReplySerializeWeakMap], null) + t.equal(reply[kRouteContext][kReplySerializeWeakMap], null) t.notOk(reply.getSerializationFunction('200')) - t.equal(reply.context[kReplySerializeWeakMap], null) + t.equal(reply[kRouteContext][kReplySerializeWeakMap], null) reply.send({ hello: 'world' }) }) @@ -568,9 +568,9 @@ test('Reply#serializeInput', t => { fastify.get('/', (req, reply) => { const input = { hello: 'world' } - t.equal(reply.context[kReplySerializeWeakMap], null) + t.equal(reply[kRouteContext][kReplySerializeWeakMap], null) t.equal(reply.serializeInput(input, getDefaultSchema()), JSON.stringify(input)) - t.type(reply.context[kReplySerializeWeakMap], WeakMap) + t.type(reply[kRouteContext][kReplySerializeWeakMap], WeakMap) reply.send({ hello: 'world' }) }) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 085b67b2803..7d4b61c4b68 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -12,7 +12,8 @@ const { kReplyHeaders, kReplySerializer, kReplyIsError, - kReplySerializerDefault + kReplySerializerDefault, + kRouteContext } = require('../../lib/symbols') const fs = require('fs') const path = require('path') @@ -34,7 +35,7 @@ test('Once called, Reply should return an object with methods', t => { t.plan(13) const response = { res: 'res' } const context = {} - const request = { context } + const request = { [kRouteContext]: context } const reply = new Reply(response, request) t.equal(typeof reply, 'object') t.equal(typeof reply[kReplyIsError], 'boolean') @@ -47,7 +48,7 @@ test('Once called, Reply should return an object with methods', t => { t.equal(typeof reply.getResponseTime, 'function') t.equal(typeof reply[kReplyHeaders], 'object') t.same(reply.raw, response) - t.equal(reply.context, context) + t.equal(reply[kRouteContext], context) t.equal(reply.request, request) }) @@ -76,7 +77,7 @@ test('reply.send will logStream error and destroy the stream', t => { warn: () => {} } - const reply = new Reply(response, { context: { onSend: null } }, log) + const reply = new Reply(response, { [kRouteContext]: { onSend: null } }, log) reply.send(payload) payload.destroy(new Error('stream error')) @@ -93,7 +94,7 @@ test('reply.send throw with circular JSON', t => { write: () => {}, end: () => {} } - const reply = new Reply(response, { context: { onSend: [] } }) + const reply = new Reply(response, { [kRouteContext]: { onSend: [] } }) t.throws(() => { const obj = {} obj.obj = obj @@ -111,7 +112,7 @@ test('reply.send returns itself', t => { write: () => {}, end: () => {} } - const reply = new Reply(response, { context: { onSend: [] } }) + const reply = new Reply(response, { [kRouteContext]: { onSend: [] } }) t.equal(reply.send('hello'), reply) }) @@ -152,7 +153,7 @@ test('reply.serialize should serialize payload', t => { t.plan(1) const response = { statusCode: 200 } const context = {} - const reply = new Reply(response, { context }) + const reply = new Reply(response, { [kRouteContext]: context }) t.equal(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') }) @@ -161,7 +162,7 @@ test('reply.serialize should serialize payload with a custom serializer', t => { let customSerializerCalled = false const response = { statusCode: 200 } const context = {} - const reply = new Reply(response, { context }) + const reply = new Reply(response, { [kRouteContext]: context }) reply.serializer((x) => (customSerializerCalled = true) && JSON.stringify(x)) t.equal(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') t.equal(customSerializerCalled, true, 'custom serializer not called') @@ -172,7 +173,7 @@ test('reply.serialize should serialize payload with a context default serializer let customSerializerCalled = false const response = { statusCode: 200 } const context = { [kReplySerializerDefault]: (x) => (customSerializerCalled = true) && JSON.stringify(x) } - const reply = new Reply(response, { context }) + const reply = new Reply(response, { [kRouteContext]: context }) t.equal(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') t.equal(customSerializerCalled, true, 'custom serializer not called') }) diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js index 2f904647b59..44cbb4b3cd4 100644 --- a/test/internals/request-validate.test.js +++ b/test/internals/request-validate.test.js @@ -2,7 +2,7 @@ const { test } = require('tap') const Ajv = require('ajv') -const { kRequestValidateWeakMap } = require('../../lib/symbols') +const { kRequestValidateWeakMap, kRouteContext } = require('../../lib/symbols') const Fastify = require('../../fastify') const defaultSchema = { @@ -231,11 +231,11 @@ test('#compileValidationSchema', subtest => { t.plan(5) fastify.get('/', (req, reply) => { - t.equal(req.context[kRequestValidateWeakMap], null) + t.equal(req[kRouteContext][kRequestValidateWeakMap], null) t.type(req.compileValidationSchema(defaultSchema), Function) - t.type(req.context[kRequestValidateWeakMap], WeakMap) + t.type(req[kRouteContext][kRequestValidateWeakMap], WeakMap) t.type(req.compileValidationSchema(Object.assign({}, defaultSchema)), Function) - t.type(req.context[kRequestValidateWeakMap], WeakMap) + t.type(req[kRouteContext][kRequestValidateWeakMap], WeakMap) reply.send({ hello: 'world' }) }) @@ -424,7 +424,7 @@ test('#getValidationFunction', subtest => { req.getValidationFunction(defaultSchema) req.getValidationFunction('body') - t.equal(req.context[kRequestValidateWeakMap], null) + t.equal(req[kRouteContext][kRequestValidateWeakMap], null) reply.send({ hello: 'world' }) }) @@ -724,9 +724,9 @@ test('#validate', subtest => { t.plan(3) fastify.get('/', (req, reply) => { - t.equal(req.context[kRequestValidateWeakMap], null) + t.equal(req[kRouteContext][kRequestValidateWeakMap], null) t.equal(req.validateInput({ hello: 'world' }, defaultSchema), true) - t.type(req.context[kRequestValidateWeakMap], WeakMap) + t.type(req[kRouteContext][kRequestValidateWeakMap], WeakMap) reply.send({ hello: 'world' }) }) diff --git a/test/internals/request.test.js b/test/internals/request.test.js index a5b2d3d2ab9..38c7a1e9ed8 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -3,6 +3,12 @@ const { test } = require('tap') const Request = require('../../lib/request') +const Context = require('../../lib/context') +const { + kPublicRouteContext, + kReply, + kRequest +} = require('../../lib/symbols') process.removeAllListeners('warning') @@ -16,8 +22,28 @@ test('Regular request', t => { socket: { remoteAddress: 'ip' }, headers } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request + } + }) req.connection = req.socket - const request = new Request('id', 'params', req, 'query', 'log') + const request = new Request('id', 'params', req, 'query', 'log', context) t.type(request, Request) t.type(request.validateInput, Function) t.type(request.getValidationFunction, Function) @@ -36,6 +62,10 @@ test('Regular request', t => { t.equal(request.url, '/') t.equal(request.socket, req.socket) t.equal(request.protocol, 'http') + t.equal(request.routerPath, context.config.url) + t.equal(request.routerMethod, context.config.method) + t.equal(request.routeConfig, context[kPublicRouteContext].config) + t.equal(request.routeSchema, context[kPublicRouteContext].schema) // This will be removed, it's deprecated t.equal(request.connection, req.connection) @@ -77,7 +107,7 @@ test('Regular request - host header has precedence over authority', t => { }) test('Request with trust proxy', t => { - t.plan(18) + t.plan(22) const headers = { 'x-forwarded-for': '2.2.2.2, 1.1.1.1', 'x-forwarded-host': 'example.com' @@ -88,9 +118,29 @@ test('Request with trust proxy', t => { socket: { remoteAddress: 'ip' }, headers } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request + } + }) const TpRequest = Request.buildRequest(Request, true) - const request = new TpRequest('id', 'params', req, 'query', 'log') + const request = new TpRequest('id', 'params', req, 'query', 'log', context) t.type(request, TpRequest) t.equal(request.id, 'id') t.equal(request.params, 'params') @@ -109,6 +159,10 @@ test('Request with trust proxy', t => { t.type(request.validateInput, Function) t.type(request.getValidationFunction, Function) t.type(request.compileValidationSchema, Function) + t.equal(request.routerPath, context.config.url) + t.equal(request.routerMethod, context.config.method) + t.equal(request.routeConfig, context[kPublicRouteContext].config) + t.equal(request.routeSchema, context[kPublicRouteContext].schema) }) test('Request with trust proxy, encrypted', t => { diff --git a/test/plugin.test.js b/test/plugin.test.js index a64a5d6c79b..d4f56bc01ed 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -885,7 +885,7 @@ test('pluginTimeout', t => { }) }) -test('pluginTimeout - named function', { only: true }, t => { +test('pluginTimeout - named function', t => { t.plan(5) const fastify = Fastify({ pluginTimeout: 10 diff --git a/test/router-options.test.js b/test/router-options.test.js index ee7523de696..c26c0094dcf 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -1,20 +1,10 @@ 'use strict' const test = require('tap').test -const sget = require('simple-get') const Fastify = require('../') const { FST_ERR_BAD_URL } = require('../lib/errors') -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} - -test('Should honor ignoreTrailingSlash option', t => { +test('Should honor ignoreTrailingSlash option', async t => { t.plan(4) const fastify = Fastify({ ignoreTrailingSlash: true @@ -24,27 +14,16 @@ test('Should honor ignoreTrailingSlash option', t => { res.send('test') }) - fastify.listen({ port: 0 }, (err) => { - t.teardown(() => { fastify.close() }) - if (err) t.threw(err) - - const baseUrl = getUrl(fastify) - - sget.concat(baseUrl + '/test', (err, res, data) => { - if (err) t.threw(err) - t.equal(res.statusCode, 200) - t.equal(data.toString(), 'test') - }) + let res = await fastify.inject('/test') + t.equal(res.statusCode, 200) + t.equal(res.payload.toString(), 'test') - sget.concat(baseUrl + '/test/', (err, res, data) => { - if (err) t.threw(err) - t.equal(res.statusCode, 200) - t.equal(data.toString(), 'test') - }) - }) + res = await fastify.inject('/test/') + t.equal(res.statusCode, 200) + t.equal(res.payload.toString(), 'test') }) -test('Should honor ignoreDuplicateSlashes option', t => { +test('Should honor ignoreDuplicateSlashes option', async t => { t.plan(4) const fastify = Fastify({ ignoreDuplicateSlashes: true @@ -54,27 +33,16 @@ test('Should honor ignoreDuplicateSlashes option', t => { res.send('test') }) - fastify.listen({ port: 0 }, (err) => { - t.teardown(() => { fastify.close() }) - if (err) t.threw(err) - - const baseUrl = getUrl(fastify) - - sget.concat(baseUrl + '/test/test/test', (err, res, data) => { - if (err) t.threw(err) - t.equal(res.statusCode, 200) - t.equal(data.toString(), 'test') - }) + let res = await fastify.inject('/test/test/test') + t.equal(res.statusCode, 200) + t.equal(res.payload.toString(), 'test') - sget.concat(baseUrl + '/test//test///test', (err, res, data) => { - if (err) t.threw(err) - t.equal(res.statusCode, 200) - t.equal(data.toString(), 'test') - }) - }) + res = await fastify.inject('/test//test///test') + t.equal(res.statusCode, 200) + t.equal(res.payload.toString(), 'test') }) -test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', t => { +test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', async t => { t.plan(4) const fastify = Fastify({ ignoreTrailingSlash: true, @@ -85,24 +53,13 @@ test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', t => res.send('test') }) - fastify.listen({ port: 0 }, (err) => { - t.teardown(() => { fastify.close() }) - if (err) t.threw(err) - - const baseUrl = getUrl(fastify) + let res = await fastify.inject('/test/test/test/') + t.equal(res.statusCode, 200) + t.equal(res.payload.toString(), 'test') - sget.concat(baseUrl + '/test/test/test/', (err, res, data) => { - if (err) t.threw(err) - t.equal(res.statusCode, 200) - t.equal(data.toString(), 'test') - }) - - sget.concat(baseUrl + '/test//test///test//', (err, res, data) => { - if (err) t.threw(err) - t.equal(res.statusCode, 200) - t.equal(data.toString(), 'test') - }) - }) + res = await fastify.inject('/test//test///test//') + t.equal(res.statusCode, 200) + t.equal(res.payload.toString(), 'test') }) test('Should honor maxParamLength option', t => { @@ -131,12 +88,22 @@ test('Should honor maxParamLength option', t => { }) test('Should expose router options via getters on request and reply', t => { - t.plan(7) + t.plan(10) const fastify = Fastify() + const expectedSchema = { + params: { + id: { type: 'integer' } + } + } - fastify.get('/test/:id', (req, reply) => { + fastify.get('/test/:id', { + schema: expectedSchema + }, (req, reply) => { t.equal(reply.context.config.url, '/test/:id') t.equal(reply.context.config.method, 'GET') + t.equal(req.routeConfig.url, '/test/:id') + t.equal(req.routeConfig.method, 'GET') + t.same(req.routeSchema, expectedSchema) t.equal(req.routerPath, '/test/:id') t.equal(req.routerMethod, 'GET') t.equal(req.is404, false) From c5974bddb9641a2070c8240e4eddec5bafe3558c Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Thu, 29 Sep 2022 17:07:50 +0300 Subject: [PATCH 0102/1295] Add fastify-s3-buckets to the ecosystem (#4311) --- docs/Guides/Ecosystem.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 07e0c08398a..d7fb8c464da 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -501,7 +501,9 @@ section. - [`fastify-rob-config`](https://github.com/jeromemacias/fastify-rob-config) Fastify Rob-Config integration. - [`fastify-route-group`](https://github.com/TakNePoidet/fastify-route-group) - Convenient grouping and inheritance of routes + Convenient grouping and inheritance of routes. +- [`fastify-s3-buckets`](https://github.com/kibertoad/fastify-s3-buckets) + Ensure the existence of defined S3 buckets on the application startup. - [`fastify-schema-constraint`](https://github.com/Eomm/fastify-schema-constraint) Choose the JSON schema to use based on request parameters. - [`fastify-schema-to-typescript`](https://github.com/thomasthiebaud/fastify-schema-to-typescript) From aacd099f3e71308dff86059b86500cd65673ce97 Mon Sep 17 00:00:00 2001 From: Soonho Kwon Date: Fri, 30 Sep 2022 04:11:39 +0900 Subject: [PATCH 0103/1295] fix: Fix typo in docs/Reference/Type-Providers.md (#4312) --- docs/Reference/Type-Providers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 5884c34f4dc..724a4291260 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -148,7 +148,7 @@ fastify.register(pluginWithTypebox) It's also important to mention that once the types don't propagate globally, _currently_ is not possible to avoid multiple registrations on routes when -dealing with several scopes, see bellow: +dealing with several scopes, see below: ```ts import Fastify from 'fastify' From 3dd23fec705c6d2662fbf4fdf1dfc1480fa03793 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Sep 2022 18:24:34 +0200 Subject: [PATCH 0104/1295] build(deps): bump tiny-lru from 8.0.2 to 9.0.2 (#4305) * build(deps): bump tiny-lru from 8.0.2 to 9.0.2 Bumps [tiny-lru](https://github.com/avoidwork/tiny-lru) from 8.0.2 to 9.0.2. - [Release notes](https://github.com/avoidwork/tiny-lru/releases) - [Changelog](https://github.com/avoidwork/tiny-lru/blob/master/CHANGELOG.md) - [Commits](https://github.com/avoidwork/tiny-lru/compare/8.0.2...9.0.2) --- updated-dependencies: - dependency-name: tiny-lru dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * fix lru error * remove webpack patch Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: uzlopak --- lib/contentTypeParser.js | 6 +----- package.json | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 3db0bdc686b..44193a77742 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -1,11 +1,7 @@ 'use strict' const { AsyncResource } = require('async_hooks') -let lru = require('tiny-lru') -// Needed to handle Webpack and faux modules -// See https://github.com/fastify/fastify/issues/2356 -// and https://github.com/fastify/fastify/discussions/2907. -lru = typeof lru === 'function' ? lru : lru.default +const lru = require('tiny-lru').lru const secureJson = require('secure-json-parse') const { diff --git a/package.json b/package.json index 4b5c92816d0..50ddceb5d83 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "rfdc": "^1.3.0", "secure-json-parse": "^2.5.0", "semver": "^7.3.7", - "tiny-lru": "^8.0.2" + "tiny-lru": "^9.0.2" }, "standard": { "ignore": [ From 2394f76b1eebf9fca3ce4f78e17be0a6a81b79dd Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 30 Sep 2022 18:26:56 +0200 Subject: [PATCH 0105/1295] Bumped v4.7.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index d15988a923a..5367624a804 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.6.0' +const VERSION = '4.7.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 50ddceb5d83..cfdcababd3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.6.0", + "version": "4.7.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", @@ -199,4 +199,4 @@ "tsd": { "directory": "test/types" } -} \ No newline at end of file +} From a4bb8ad161f6de802e4617fff6b4477fd1c084b2 Mon Sep 17 00:00:00 2001 From: Vano Devium Date: Tue, 4 Oct 2022 15:00:47 +0300 Subject: [PATCH 0106/1295] Correct github url for fastify-qs package (#4321) --- docs/Guides/Ecosystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index d7fb8c464da..a65a75a0f29 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -468,7 +468,7 @@ section. Fastify and protobufjs, together at last. Uses protobufjs by default. - [`fastify-qrcode`](https://github.com/chonla/fastify-qrcode) This plugin utilizes [qrcode](https://github.com/soldair/node-qrcode) to generate QR Code. -- [`fastify-qs`](https://github.com/webdevium/fastify-qs) A plugin for Fastify +- [`fastify-qs`](https://github.com/vanodevium/fastify-qs) A plugin for Fastify that adds support for parsing URL query parameters with [qs](https://github.com/ljharb/qs). - [`fastify-racing`](https://github.com/metcoder95/fastify-racing) Fastify's From 62d2c94a26ac619e95e16d99e5d6d2646a60a119 Mon Sep 17 00:00:00 2001 From: Cristian Teodorescu <81955552+CristiTeo@users.noreply.github.com> Date: Thu, 6 Oct 2022 12:40:40 +0300 Subject: [PATCH 0107/1295] docs: add test examples with undici and fetch (#4300) Co-authored-by: Cristian Teodorescu <87984709+CristianTeodorescu@users.noreply.github.com> --- docs/Guides/Testing.md | 67 +++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/docs/Guides/Testing.md b/docs/Guides/Testing.md index 039cbb8ea6e..421a1e16e3a 100644 --- a/docs/Guides/Testing.md +++ b/docs/Guides/Testing.md @@ -243,33 +243,66 @@ after initializing routes and plugins with `fastify.ready()`. Uses **app.js** from the previous example. -**test-listen.js** (testing with -[`Request`](https://www.npmjs.com/package/request)) +**test-listen.js** (testing with [`undici`](https://www.npmjs.com/package/undici)) ```js const tap = require('tap') -const request = require('request') +const { Client } = require('undici') const buildFastify = require('./app') -tap.test('GET `/` route', t => { - t.plan(5) +tap.test('should work with undici', async t => { + t.plan(2) const fastify = buildFastify() - t.teardown(() => fastify.close()) + await fastify.listen() - fastify.listen({ port: 0 }, (err) => { - t.error(err) + const client = new Client( + 'http://localhost:' + fastify.server.address().port, { + keepAliveTimeout: 10, + keepAliveMaxTimeout: 10 + } + ) - request({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(body), { hello: 'world' }) - }) + t.teardown(() => { + fastify.close() + client.close() }) + + const response = await client.request({ method: 'GET', path: '/' }) + + t.equal(await response.body.text(), '{"hello":"world"}') + t.equal(response.statusCode, 200) +}) +``` + +Alternatively, starting with Node.js 18, +[`fetch`](https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch) +may be used without requiring any extra dependencies: + +**test-listen.js** +```js +const tap = require('tap') +const buildFastify = require('./app') + +tap.test('should work with fetch', async t => { + t.plan(3) + + const fastify = buildFastify() + + t.teardown(() => fastify.close()) + + await fastify.listen() + + const response = await fetch( + 'http://localhost:' + fastify.server.address().port + ) + + t.equal(response.status, 200) + t.equal( + response.headers.get('content-type'), + 'application/json; charset=utf-8' + ) + t.has(await response.json(), { hello: 'world' }) }) ``` From da7471f1e43822cba2a2b090cd34a54a6e1469dd Mon Sep 17 00:00:00 2001 From: Matthias Keckl <53833818+matthyk@users.noreply.github.com> Date: Thu, 6 Oct 2022 17:50:32 +0200 Subject: [PATCH 0108/1295] docs: update onRoute hook docs (#4322) * update docs * fix lint error --- docs/Reference/Hooks.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 90dec038061..d46f6d0245c 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -449,6 +449,33 @@ fastify.addHook('onRoute', (routeOptions) => { }) ``` +If you want to add more routes within an onRoute hook, you have to tag these +routes properly. If you don't, the hook will run into an infinite loop. The +recommended approach is shown below. + +```js +const kRouteAlreadyProcessed = Symbol('route-already-processed') + +fastify.addHook('onRoute', function (routeOptions) { + const { url, method } = routeOptions + + const isAlreadyProcessed = (routeOptions.custom && routeOptions.custom[kRouteAlreadyProcessed]) || false + + if (!isAlreadyProcessed) { + this.route({ + url, + method, + custom: { + [kRouteAlreadyProcessed]: true + }, + handler: () => {} + }) + } +}) +``` + +For more details, see this [issue](https://github.com/fastify/fastify/issues/4319). + ### onRegister From 6511ef405a4ba887922d1a446d03dde05fb55ddd Mon Sep 17 00:00:00 2001 From: Artem Fitiskin Date: Sat, 8 Oct 2022 00:02:50 +0300 Subject: [PATCH 0109/1295] Export error codes (#4266) --- docs/Reference/Errors.md | 260 +++++++++++++++++++++++++++-------- fastify.d.ts | 6 + fastify.js | 14 +- lib/errors.js | 62 +++++---- test/types/fastify.test-d.ts | 6 +- types/errors.d.ts | 53 +++++++ 6 files changed, 308 insertions(+), 93 deletions(-) create mode 100644 types/errors.d.ts diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index ec969cd63c8..ac700209211 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -83,15 +83,52 @@ Some things to consider in your custom error handler: ### Fastify Error Codes -#### FST_ERR_BAD_URL - - -The router received an invalid url. - - -#### FST_ERR_DUPLICATED_ROUTE - -The HTTP method already has a registered controller for that URL +You can access `errorCodes` for mapping: +```js +// ESM +import { errorCodes } from 'fastify' + +// CommonJs +const errorCodes = require('fastify').errorCodes +``` + +For example: +```js +const Fastify = require('./fastify') + +// Instantiate the framework +const fastify = Fastify({ + logger: true +}) + +// Declare a route +fastify.get('/', function (request, reply) { + reply.code('bad status code').send({ hello: 'world' }) +}) + +fastify.setErrorHandler(function (error, request, reply) { + if (error instanceof Fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) { + // Log error + this.log.error(error) + // Send error response + reply.status(500).send({ ok: false }) + } +}) + +// Run the server! +fastify.listen({ port: 3000 }, function (err, address) { + if (err) { + fastify.log.error(err) + process.exit(1) + } + // Server is now listening on ${address} +}) +``` + +#### FST_ERR_NOT_FOUND + + +404 Not Found. #### FST_ERR_CTP_ALREADY_PRESENT @@ -99,130 +136,233 @@ The HTTP method already has a registered controller for that URL The parser for this content type was already registered. -#### FST_ERR_CTP_BODY_TOO_LARGE - - -The request body is larger than the provided limit. +#### FST_ERR_CTP_INVALID_TYPE + -This setting can be defined in the Fastify server instance: -[`bodyLimit`](./Server.md#bodylimit) +The `Content-Type` should be a string. #### FST_ERR_CTP_EMPTY_TYPE The content type cannot be an empty string. -#### FST_ERR_CTP_INVALID_CONTENT_LENGTH - - -Request body size did not match Content-Length. - #### FST_ERR_CTP_INVALID_HANDLER An invalid handler was passed for the content type. +#### FST_ERR_CTP_INVALID_PARSE_TYPE + + +The provided parse type is not supported. Accepted values are `string` or +`buffer`. + +#### FST_ERR_CTP_BODY_TOO_LARGE + + +The request body is larger than the provided limit. + +This setting can be defined in the Fastify server instance: +[`bodyLimit`](./Server.md#bodylimit) + #### FST_ERR_CTP_INVALID_MEDIA_TYPE The received media type is not supported (i.e. there is no suitable `Content-Type` parser for it). -#### FST_ERR_CTP_INVALID_PARSE_TYPE - +#### FST_ERR_CTP_INVALID_CONTENT_LENGTH + -The provided parse type is not supported. Accepted values are `string` or -`buffer`. +Request body size did not match `Content-Length`. -#### FST_ERR_CTP_INVALID_TYPE - +#### FST_ERR_CTP_EMPTY_JSON_BODY + -The `Content-Type` should be a string. +Body cannot be empty when content-type is set to `application/json`. #### FST_ERR_DEC_ALREADY_PRESENT A decorator with the same name is already registered. +#### FST_ERR_DEC_DEPENDENCY_INVALID_TYPE + + +The dependencies of decorator must be of type `Array`. + #### FST_ERR_DEC_MISSING_DEPENDENCY The decorator cannot be registered due to a missing dependency. -#### FST_ERR_HOOK_INVALID_HANDLER - +#### FST_ERR_DEC_AFTER_START + -The hook callback must be a function. +The decorator cannot be added after start. #### FST_ERR_HOOK_INVALID_TYPE The hook name must be a string. +#### FST_ERR_HOOK_INVALID_HANDLER + + +The hook callback must be a function. + +#### FST_ERR_MISSING_MIDDLEWARE + + +You must register a plugin for handling middlewares, +visit [`Middleware`](./Middleware.md) for more info. + + +#### FST_ERR_HOOK_TIMEOUT + +A callback for a hook timed out + #### FST_ERR_LOG_INVALID_DESTINATION The logger accepts either a `'stream'` or a `'file'` as the destination. -#### FST_ERR_PROMISE_NOT_FULFILLED - +#### FST_ERR_REP_INVALID_PAYLOAD_TYPE + -A promise may not be fulfilled with 'undefined' when statusCode is not 204. +Reply payload can be either a `string` or a `Buffer`. #### FST_ERR_REP_ALREADY_SENT A response was already sent. -#### FST_ERR_REP_INVALID_PAYLOAD_TYPE - +#### FST_ERR_REP_SENT_VALUE + -Reply payload can be either a `string` or a `Buffer`. +The only possible value for `reply.sent` is `true`. -#### FST_ERR_SCH_ALREADY_PRESENT - +#### FST_ERR_SEND_INSIDE_ONERR + -A schema with the same `$id` already exists. +You cannot use `send` inside the `onError` hook. + +#### FST_ERR_SEND_UNDEFINED_ERR + + +Undefined error has occurred. + +#### FST_ERR_BAD_STATUS_CODE + + +Called `reply` with an invalid status code. + +#### FST_ERR_BAD_TRAILER_NAME + + +Called `reply.trailer` with an invalid header name. + +#### FST_ERR_BAD_TRAILER_VALUE + + +Called `reply.trailer` with an invalid type. Expected a function. + +#### FST_ERR_MISSING_SERIALIZATION_FN + + +Missing serialization function. + +#### FST_ERR_REQ_INVALID_VALIDATION_INVOCATION + + +Invalid validation invocation. Missing validation function for +HTTP part nor schema provided. #### FST_ERR_SCH_MISSING_ID The schema provided does not have `$id` property. -#### FST_ERR_SCH_SERIALIZATION_BUILD - +#### FST_ERR_SCH_ALREADY_PRESENT + -The JSON schema provided for serialization of a route response is not valid. +A schema with the same `$id` already exists. + +#### FST_ERR_SCH_DUPLICATE + + +Schema with the same `$id` already present! #### FST_ERR_SCH_VALIDATION_BUILD The JSON schema provided for validation to a route is not valid. -#### FST_ERR_SEND_INSIDE_ONERR - +#### FST_ERR_SCH_SERIALIZATION_BUILD + -You cannot use `send` inside the `onError` hook. +The JSON schema provided for serialization of a route response is not valid. -#### FST_ERR_SEND_UNDEFINED_ERR - +#### FST_ERR_HTTP2_INVALID_VERSION + -Undefined error has occurred. +HTTP2 is available only from node >= 8.8.1. - -#### FST_ERR_PLUGIN_NOT_VALID +#### FST_ERR_INIT_OPTS_INVALID + -Plugin must be a function or a promise. +Invalid initialization options. - -#### FST_ERR_PLUGIN_TIMEOUT +#### FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE + -Plugin did not start in time. Default timeout (in millis): `10000` +Cannot set forceCloseConnections to `idle` as your HTTP server +does not support `closeIdleConnections` method. - -#### FST_ERR_HOOK_TIMEOUT + +#### FST_ERR_DUPLICATED_ROUTE -A callback for a hook timed out +The HTTP method already has a registered controller for that URL + +#### FST_ERR_BAD_URL + + +The router received an invalid url. + +#### FST_ERR_DEFAULT_ROUTE_INVALID_TYPE + + +The `defaultRoute` type should be a function. + +#### FST_ERR_INVALID_URL + + +URL must be a string. + +#### FST_ERR_REOPENED_CLOSE_SERVER + + +Fastify has already been closed and cannot be reopened. + +#### FST_ERR_REOPENED_SERVER + + +Fastify is already listening. + +#### FST_ERR_PLUGIN_VERSION_MISMATCH + + +Installed Fastify plugin mismatched expected version. + + +#### FST_ERR_PLUGIN_CALLBACK_NOT_FN + +Callback for a hook is not a function (mapped directly from `avvio`) + + +#### FST_ERR_PLUGIN_NOT_VALID + +Plugin must be a function or a promise. #### FST_ERR_ROOT_PLG_BOOTED @@ -234,7 +374,7 @@ Root plugin has already booted (mapped directly from `avvio`) Impossible to load plugin because the parent (mapped directly from `avvio`) - -#### FST_ERR_PLUGIN_CALLBACK_NOT_FN + +#### FST_ERR_PLUGIN_TIMEOUT -Callback for a hook is not a function (mapped directly from `avvio`) +Plugin did not start in time. Default timeout (in millis): `10000` \ No newline at end of file diff --git a/fastify.d.ts b/fastify.d.ts index 41afbb8f9e8..aa0cc012d22 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -20,6 +20,7 @@ import { SerializerCompiler } from '@fastify/fast-json-stringify-compiler' import { FastifySchema } from './types/schema' import { FastifyContextConfig } from './types/context' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider' +import { FastifyErrorCodes } from './types/errors' /** * Fastify factory function for the standard fastify http, https, or http2 server instance. @@ -61,6 +62,10 @@ declare function fastify< TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, >(opts?: FastifyServerOptions): FastifyInstance & PromiseLike> +declare namespace fastify { + export const errorCodes: FastifyErrorCodes; +} + export default fastify export type FastifyHttp2SecureOptions< @@ -206,4 +211,5 @@ export { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaul export * from './types/hooks' export { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' export { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider' +export { FastifyErrorCodes } from './types/errors' export { fastify } diff --git a/fastify.js b/fastify.js index 5367624a804..679129db1fa 100644 --- a/fastify.js +++ b/fastify.js @@ -48,14 +48,18 @@ const getSecuredInitialConfig = require('./lib/initialConfigValidation') const override = require('./lib/pluginOverride') const warning = require('./lib/warnings') const noopSet = require('./lib/noop-set') +const { + appendStackTrace, + AVVIO_ERRORS_MAP, + ...errorCodes +} = require('./lib/errors') + const { defaultInitOptions } = getSecuredInitialConfig const { FST_ERR_BAD_URL, - FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE, - AVVIO_ERRORS_MAP, - appendStackTrace -} = require('./lib/errors') + FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE +} = errorCodes const { buildErrorHandler } = require('./lib/error-handler.js') @@ -720,6 +724,8 @@ function fastify (options) { } } +fastify.errorCodes = errorCodes + function validateSchemaErrorFormatter (schemaErrorFormatter) { if (typeof schemaErrorFormatter !== 'function') { throw new Error(`schemaErrorFormatter option should be a function, instead got ${typeof schemaErrorFormatter}`) diff --git a/lib/errors.js b/lib/errors.js index 66fb1ec8b48..d06288473b0 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -1,6 +1,7 @@ 'use strict' const createError = require('@fastify/error') + const codes = { /** * Basic @@ -258,37 +259,42 @@ const codes = { ), /** - * Avvio Errors map + * Avvio Errors */ - AVVIO_ERRORS_MAP: { - AVV_ERR_CALLBACK_NOT_FN: createError( - 'FST_ERR_PLUGIN_CALLBACK_NOT_FN', - 'fastify-plugin: %s' - ), - AVV_ERR_PLUGIN_NOT_VALID: createError( - 'FST_ERR_PLUGIN_NOT_VALID', - 'fastify-plugin: %s' - ), - AVV_ERR_ROOT_PLG_BOOTED: createError( - 'FST_ERR_ROOT_PLG_BOOTED', - 'fastify-plugin: %s' - ), - AVV_ERR_PARENT_PLG_LOADED: createError( - 'FST_ERR_PARENT_PLUGIN_BOOTED', - 'fastify-plugin: %s' - ), - AVV_ERR_READY_TIMEOUT: createError( - 'FST_ERR_PLUGIN_TIMEOUT', - 'fastify-plugin: %s' - ) - }, + FST_ERR_PLUGIN_CALLBACK_NOT_FN: createError( + 'FST_ERR_PLUGIN_CALLBACK_NOT_FN', + 'fastify-plugin: %s' + ), + FST_ERR_PLUGIN_NOT_VALID: createError( + 'FST_ERR_PLUGIN_NOT_VALID', + 'fastify-plugin: %s' + ), + FST_ERR_ROOT_PLG_BOOTED: createError( + 'FST_ERR_ROOT_PLG_BOOTED', + 'fastify-plugin: %s' + ), + FST_ERR_PARENT_PLUGIN_BOOTED: createError( + 'FST_ERR_PARENT_PLUGIN_BOOTED', + 'fastify-plugin: %s' + ), + FST_ERR_PLUGIN_TIMEOUT: createError( + 'FST_ERR_PLUGIN_TIMEOUT', + 'fastify-plugin: %s' + ) +} - // Util function - appendStackTrace (oldErr, newErr) { - newErr.cause = oldErr +function appendStackTrace (oldErr, newErr) { + newErr.cause = oldErr - return newErr - } + return newErr } module.exports = codes +module.exports.appendStackTrace = appendStackTrace +module.exports.AVVIO_ERRORS_MAP = { + AVV_ERR_CALLBACK_NOT_FN: codes.FST_ERR_PLUGIN_CALLBACK_NOT_FN, + AVV_ERR_PLUGIN_NOT_VALID: codes.FST_ERR_PLUGIN_NOT_VALID, + AVV_ERR_ROOT_PLG_BOOTED: codes.FST_ERR_ROOT_PLG_BOOTED, + AVV_ERR_PARENT_PLG_LOADED: codes.FST_ERR_PARENT_PLUGIN_BOOTED, + AVV_ERR_READY_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT +} diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index d30146d3990..63f65360c6b 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -9,7 +9,8 @@ import fastify, { LightMyRequestCallback, InjectOptions, FastifyBaseLogger, RouteGenericInterface, - ValidationResult + ValidationResult, + FastifyErrorCodes } from '../../fastify' import { ErrorObject as AjvErrorObject } from 'ajv' import * as http from 'http' @@ -240,3 +241,6 @@ expectType(routeGeneric.Headers) expectType(routeGeneric.Params) expectType(routeGeneric.Querystring) expectType(routeGeneric.Reply) + +// ErrorCodes +expectType(fastify.errorCodes) diff --git a/types/errors.d.ts b/types/errors.d.ts new file mode 100644 index 00000000000..170acfef33c --- /dev/null +++ b/types/errors.d.ts @@ -0,0 +1,53 @@ +import { FastifyErrorConstructor } from '@fastify/error' + +export type FastifyErrorCodes = Record< +'FST_ERR_NOT_FOUND' | +'FST_ERR_CTP_ALREADY_PRESENT' | +'FST_ERR_CTP_INVALID_TYPE' | +'FST_ERR_CTP_EMPTY_TYPE' | +'FST_ERR_CTP_INVALID_HANDLER' | +'FST_ERR_CTP_INVALID_PARSE_TYPE' | +'FST_ERR_CTP_BODY_TOO_LARGE' | +'FST_ERR_CTP_INVALID_MEDIA_TYPE' | +'FST_ERR_CTP_INVALID_CONTENT_LENGTH' | +'FST_ERR_CTP_EMPTY_JSON_BODY' | +'FST_ERR_DEC_ALREADY_PRESENT' | +'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE' | +'FST_ERR_DEC_MISSING_DEPENDENCY' | +'FST_ERR_DEC_AFTER_START' | +'FST_ERR_HOOK_INVALID_TYPE' | +'FST_ERR_HOOK_INVALID_HANDLER' | +'FST_ERR_MISSING_MIDDLEWARE' | +'FST_ERR_HOOK_TIMEOUT' | +'FST_ERR_LOG_INVALID_DESTINATION' | +'FST_ERR_REP_INVALID_PAYLOAD_TYPE' | +'FST_ERR_REP_ALREADY_SENT' | +'FST_ERR_REP_SENT_VALUE'| +'FST_ERR_SEND_INSIDE_ONERR'| +'FST_ERR_SEND_UNDEFINED_ERR'| +'FST_ERR_BAD_STATUS_CODE'| +'FST_ERR_BAD_TRAILER_NAME' | +'FST_ERR_BAD_TRAILER_VALUE' | +'FST_ERR_MISSING_SERIALIZATION_FN' | +'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION' | +'FST_ERR_SCH_MISSING_ID' | +'FST_ERR_SCH_ALREADY_PRESENT' | +'FST_ERR_SCH_DUPLICATE' | +'FST_ERR_SCH_VALIDATION_BUILD' | +'FST_ERR_SCH_SERIALIZATION_BUILD' | +'FST_ERR_HTTP2_INVALID_VERSION' | +'FST_ERR_INIT_OPTS_INVALID' | +'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE' | +'FST_ERR_DUPLICATED_ROUTE' | +'FST_ERR_BAD_URL' | +'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE' | +'FST_ERR_INVALID_URL' | +'FST_ERR_REOPENED_CLOSE_SERVER' | +'FST_ERR_REOPENED_SERVER' | +'FST_ERR_PLUGIN_VERSION_MISMATCH' | +'FST_ERR_PLUGIN_CALLBACK_NOT_FN' | +'FST_ERR_PLUGIN_NOT_VALID' | +'FST_ERR_ROOT_PLG_BOOTED' | +'FST_ERR_PARENT_PLUGIN_BOOTED' | +'FST_ERR_PLUGIN_TIMEOUT' +, FastifyErrorConstructor> From 2df8712dd92d5cf9e24292417f43059481f78535 Mon Sep 17 00:00:00 2001 From: KaKa Date: Mon, 10 Oct 2022 15:06:21 +0800 Subject: [PATCH 0110/1295] feat: support async constraint (#4323) --- docs/Reference/Errors.md | 5 ++ docs/Reference/Routes.md | 54 +++++++++++++++ docs/Reference/Server.md | 3 + fastify.js | 67 +++++++++++++------ lib/errors.js | 5 ++ lib/route.js | 7 +- package.json | 2 +- test/constrained-routes.test.js | 114 ++++++++++++++++++++++++++++++++ test/router-options.test.js | 58 +++++++++++++++- 9 files changed, 291 insertions(+), 24 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index ac700209211..54b224795cd 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -329,6 +329,11 @@ The HTTP method already has a registered controller for that URL The router received an invalid url. +### FST_ERR_ASYNC_CONSTRAINT + + +The router received error when using asynchronous constraints. + #### FST_ERR_DEFAULT_ROUTE_INVALID_TYPE diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 90f17034834..03969d5091c 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -732,6 +732,60 @@ fastify.route({ }) ``` +#### Asynchronous Custom Constraints + +You can provide your custom constraints and the `constraint` criteria can be +fetched from other source, for example `database`. Usage of asynchronous custom +constraint should place at the last resort since it impacts the router +performance. + +```js +function databaseOperation(field, done) { + done(null, field) +} + +const secret = { + // strategy name for referencing in the route handler `constraints` options + name: 'secret', + // storage factory for storing routes in the find-my-way route tree + storage: function () { + let handlers = {} + return { + get: (type) => { return handlers[type] || null }, + set: (type, store) => { handlers[type] = store } + } + }, + // function to get the value of the constraint from each incoming request + deriveConstraint: (req, ctx, done) => { + databaseOperation(req.headers['secret'], done) + }, + // optional flag marking if handlers without constraints can match requests that have a value for this constraint + mustMatchWhenDerived: true +} +``` + +> ## ⚠ Security Notice +> When using with asynchronous constraint. It is highly recommend never return error +> inside the callback. If the error is not preventable, it is recommended to provide +> a custom `frameworkErrors` handler to deal with it. Otherwise, you route selection +> may break or expose sensitive information to attackers. +> +> ```js +> const Fastify = require('fastify') +> +> const fastify = Fastify({ +> frameworkErrors: function(err, res, res) { +> if(err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) { +> res.code(400) +> return res.send("Invalid header provided") +> } else { +> res.send(err) +> } +> } +> }) +> ``` + + ### ⚠ HTTP version check Fastify will check the HTTP version of every request, based on configuration diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 248e5a105cd..c3150dfcddd 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -731,6 +731,9 @@ const fastify = require('fastify')({ if (error instanceof FST_ERR_BAD_URL) { res.code(400) return res.send("Provided url is not valid") + } else if(error instanceof FST_ERR_ASYNC_CONSTRAINT) { + res.code(400) + return res.send("Provided header is not valid") } else { res.send(err) } diff --git a/fastify.js b/fastify.js index 679129db1fa..20b9b80dba6 100644 --- a/fastify.js +++ b/fastify.js @@ -57,6 +57,7 @@ const { const { defaultInitOptions } = getSecuredInitialConfig const { + FST_ERR_ASYNC_CONSTRAINT, FST_ERR_BAD_URL, FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE } = errorCodes @@ -182,7 +183,7 @@ function fastify (options) { const fourOhFour = build404(options) // HTTP server and its handler - const httpHandler = wrapRouting(router.routing, options) + const httpHandler = wrapRouting(router, options) // we need to set this before calling createServer options.http2SessionTimeout = initialConfig.http2SessionTimeout @@ -666,6 +667,30 @@ function fastify (options) { res.end(body) } + function buildAsyncConstraintCallback (isAsync, req, res) { + if (isAsync === false) return undefined + return function onAsyncConstraintError (err) { + if (err) { + if (frameworkErrors) { + const id = genReqId(req) + const childLogger = logger.child({ reqId: id }) + + childLogger.info({ req }, 'incoming request') + + const request = new Request(id, null, req, null, childLogger, onBadUrlContext) + const reply = new Reply(res, request, childLogger) + return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply) + } + const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}' + res.writeHead(500, { + 'Content-Type': 'application/json', + 'Content-Length': body.length + }) + res.end(body) + } + } + } + function setNotFoundHandler (opts, handler) { throwIfAlreadyStarted('Cannot call "setNotFoundHandler" when fastify instance is already started!') @@ -722,6 +747,27 @@ function fastify (options) { opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta return router.printRoutes(opts) } + + function wrapRouting (router, { rewriteUrl, logger }) { + let isAsync + return function preRouting (req, res) { + // only call isAsyncConstraint once + if (isAsync === undefined) isAsync = router.isAsyncConstraint() + if (rewriteUrl) { + const originalUrl = req.url + const url = rewriteUrl(req) + if (originalUrl !== url) { + logger.debug({ originalUrl, url }, 'rewrite url') + if (typeof url === 'string') { + req.url = url + } else { + req.destroy(new Error(`Rewrite url for "${req.url}" needs to be of type "string" but received "${typeof url}"`)) + } + } + } + router.routing(req, res, buildAsyncConstraintCallback(isAsync, req, res)) + } + } } fastify.errorCodes = errorCodes @@ -734,25 +780,6 @@ function validateSchemaErrorFormatter (schemaErrorFormatter) { } } -function wrapRouting (httpHandler, { rewriteUrl, logger }) { - if (!rewriteUrl) { - return httpHandler - } - return function preRouting (req, res) { - const originalUrl = req.url - const url = rewriteUrl(req) - if (originalUrl !== url) { - logger.debug({ originalUrl, url }, 'rewrite url') - if (typeof url === 'string') { - req.url = url - } else { - req.destroy(new Error(`Rewrite url for "${req.url}" needs to be of type "string" but received "${typeof url}"`)) - } - } - httpHandler(req, res) - } -} - /** * These export configurations enable JS and TS developers * to consumer fastify in whatever way best suits their needs. diff --git a/lib/errors.js b/lib/errors.js index d06288473b0..a992a202eaf 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -226,6 +226,11 @@ const codes = { "'%s' is not a valid url component", 400 ), + FST_ERR_ASYNC_CONSTRAINT: createError( + 'FST_ERR_ASYNC_CONSTRAINT', + 'Unexpected error from async constraint', + 500 + ), FST_ERR_DEFAULT_ROUTE_INVALID_TYPE: createError( 'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE', 'The defaultRoute type should be a function', diff --git a/lib/route.js b/lib/route.js index e89a3c4fa49..5aeef399e5f 100644 --- a/lib/route.js +++ b/lib/route.js @@ -101,7 +101,8 @@ function buildRouting (options) { closeRoutes: () => { closing = true }, printRoutes: router.prettyPrint.bind(router), addConstraintStrategy, - hasConstraintStrategy + hasConstraintStrategy, + isAsyncConstraint } function addConstraintStrategy (strategy) { @@ -113,6 +114,10 @@ function buildRouting (options) { return router.hasConstraintStrategy(strategyName) } + function isAsyncConstraint () { + return router.constrainer.asyncStrategiesInUse.size > 0 + } + // Convert shorthand to extended route declaration function prepareRoute ({ method, url, options, handler, isFastify }) { if (typeof url !== 'string') { diff --git a/package.json b/package.json index cfdcababd3f..24ac69daf84 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "@fastify/fast-json-stringify-compiler": "^4.1.0", "abstract-logging": "^2.0.1", "avvio": "^8.2.0", - "find-my-way": "^7.2.0", + "find-my-way": "^7.3.0", "light-my-request": "^5.6.1", "pino": "^8.5.0", "process-warning": "^2.0.0", diff --git a/test/constrained-routes.test.js b/test/constrained-routes.test.js index 11fd919a111..867b6d9b371 100644 --- a/test/constrained-routes.test.js +++ b/test/constrained-routes.test.js @@ -660,3 +660,117 @@ test('allows separate constrained and unconstrained HEAD routes', async (t) => { t.ok(true) }) + +test('allow async constraints', async (t) => { + t.plan(5) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(null, req.headers['x-secret']) + }, + validate () { return true } + } + + const fastify = Fastify({ constraints: { secret: constraint } }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'beta' }, + handler: (req, reply) => { + reply.send({ hello: 'from beta' }) + } + }) + + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'alpha' } }) + t.same(JSON.parse(payload), { hello: 'from alpha' }) + t.equal(statusCode, 200) + } + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'beta' } }) + t.same(JSON.parse(payload), { hello: 'from beta' }) + t.equal(statusCode, 200) + } + { + const { statusCode } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'gamma' } }) + t.equal(statusCode, 404) + } +}) + +test('error in async constraints', async (t) => { + t.plan(8) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const fastify = Fastify({ constraints: { secret: constraint } }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'beta' }, + handler: (req, reply) => { + reply.send({ hello: 'from beta' }) + } + }) + + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'alpha' } }) + t.same(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.equal(statusCode, 500) + } + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'beta' } }) + t.same(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.equal(statusCode, 500) + } + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'gamma' } }) + t.same(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.equal(statusCode, 500) + } + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/' }) + t.same(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.equal(statusCode, 500) + } +}) diff --git a/test/router-options.test.js b/test/router-options.test.js index c26c0094dcf..0b83aabcea1 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -2,7 +2,10 @@ const test = require('tap').test const Fastify = require('../') -const { FST_ERR_BAD_URL } = require('../lib/errors') +const { + FST_ERR_BAD_URL, + FST_ERR_ASYNC_CONSTRAINT +} = require('../lib/errors') test('Should honor ignoreTrailingSlash option', async t => { t.plan(4) @@ -137,7 +140,7 @@ test('Should set is404 flag for unmatched paths', t => { }) }) -test('Should honor frameworkErrors option', t => { +test('Should honor frameworkErrors option - FST_ERR_BAD_URL', t => { t.plan(3) const fastify = Fastify({ frameworkErrors: function (err, req, res) { @@ -165,3 +168,54 @@ test('Should honor frameworkErrors option', t => { } ) }) + +test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', t => { + t.plan(3) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const fastify = Fastify({ + frameworkErrors: function (err, req, res) { + if (err instanceof FST_ERR_ASYNC_CONSTRAINT) { + t.ok(true) + } else { + t.fail() + } + res.send(`${err.message} - ${err.code}`) + }, + constraints: { secret: constraint } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.inject( + { + method: 'GET', + url: '/' + }, + (err, res) => { + t.error(err) + t.equal(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + } + ) +}) From 5053ad91177f16fb4bcfd06ca65f6a6df6acd2b1 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 10 Oct 2022 09:07:15 +0200 Subject: [PATCH 0111/1295] Bumped v4.8.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 20b9b80dba6..db58b7fc673 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.7.0' +const VERSION = '4.8.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 24ac69daf84..77606aa598e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.7.0", + "version": "4.8.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From fbb07e8dfad74c69cd4cd2211aedab87194618e3 Mon Sep 17 00:00:00 2001 From: Tomas Della Vedova Date: Mon, 10 Oct 2022 09:24:46 +0200 Subject: [PATCH 0112/1295] Merge pull request from GHSA-455w-c45v-86rg * Add safeguard against malicious content types * Update lib/contentTypeParser.js Co-authored-by: Manuel Spigolon * Apply spelling fixes from code review Co-authored-by: James Sumners Co-authored-by: Manuel Spigolon Co-authored-by: Frazer Smith Co-authored-by: James Sumners --- lib/contentTypeParser.js | 39 +++++++++++---------- test/content-parser.test.js | 70 +++++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 21 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 44193a77742..a1eebeb3a02 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -29,9 +29,10 @@ const { function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) { this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning) - this.customParsers = {} - this.customParsers['application/json'] = new Parser(true, false, bodyLimit, this[kDefaultJsonParse]) - this.customParsers['text/plain'] = new Parser(true, false, bodyLimit, defaultPlainTextParser) + // using a map instead of a plain object to avoid prototype hijack attacks + this.customParsers = new Map() + this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse])) + this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser)) this.parserList = ['application/json', 'text/plain'] this.parserRegExpList = [] this.cache = lru(100) @@ -62,35 +63,35 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { ) if (contentTypeIsString && contentType === '*') { - this.customParsers[''] = parser + this.customParsers.set('', parser) } else { if (contentTypeIsString) { this.parserList.unshift(contentType) } else { this.parserRegExpList.unshift(contentType) } - this.customParsers[contentType] = parser + this.customParsers.set(contentType.toString(), parser) } } ContentTypeParser.prototype.hasParser = function (contentType) { - return contentType in this.customParsers + return this.customParsers.has(typeof contentType === 'string' ? contentType : contentType.toString()) } ContentTypeParser.prototype.existingParser = function (contentType) { - if (contentType === 'application/json') { - return this.customParsers['application/json'] && this.customParsers['application/json'].fn !== this[kDefaultJsonParse] + if (contentType === 'application/json' && this.customParsers.has(contentType)) { + return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse] } - if (contentType === 'text/plain') { - return this.customParsers['text/plain'] && this.customParsers['text/plain'].fn !== defaultPlainTextParser + if (contentType === 'text/plain' && this.customParsers.has(contentType)) { + return this.customParsers.get(contentType).fn !== defaultPlainTextParser } - return contentType in this.customParsers + return this.hasParser(contentType) } ContentTypeParser.prototype.getParser = function (contentType) { - if (contentType in this.customParsers) { - return this.customParsers[contentType] + if (this.hasParser(contentType)) { + return this.customParsers.get(contentType) } if (this.cache.has(contentType)) { @@ -101,7 +102,7 @@ ContentTypeParser.prototype.getParser = function (contentType) { for (var i = 0; i !== this.parserList.length; ++i) { const parserName = this.parserList[i] if (contentType.indexOf(parserName) !== -1) { - const parser = this.customParsers[parserName] + const parser = this.customParsers.get(parserName) this.cache.set(contentType, parser) return parser } @@ -111,17 +112,17 @@ ContentTypeParser.prototype.getParser = function (contentType) { for (var j = 0; j !== this.parserRegExpList.length; ++j) { const parserRegExp = this.parserRegExpList[j] if (parserRegExp.test(contentType)) { - const parser = this.customParsers[parserRegExp] + const parser = this.customParsers.get(parserRegExp.toString()) this.cache.set(contentType, parser) return parser } } - return this.customParsers[''] + return this.customParsers.get('') } ContentTypeParser.prototype.removeAll = function () { - this.customParsers = {} + this.customParsers = new Map() this.parserRegExpList = [] this.parserList = [] this.cache = lru(100) @@ -130,7 +131,7 @@ ContentTypeParser.prototype.removeAll = function () { ContentTypeParser.prototype.remove = function (contentType) { if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() - delete this.customParsers[contentType] + this.customParsers.delete(contentType.toString()) const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList @@ -289,7 +290,7 @@ function Parser (asString, asBuffer, bodyLimit, fn) { function buildContentTypeParser (c) { const contentTypeParser = new ContentTypeParser() contentTypeParser[kDefaultJsonParse] = c[kDefaultJsonParse] - Object.assign(contentTypeParser.customParsers, c.customParsers) + contentTypeParser.customParsers = new Map(c.customParsers.entries()) contentTypeParser.parserList = c.parserList.slice() return contentTypeParser } diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 87f79bd4058..fd24d195f6f 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -196,7 +196,7 @@ test('add', t => { const contentTypeParser = fastify[keys.kContentTypeParser] contentTypeParser.add('*', {}, first) - t.equal(contentTypeParser.customParsers[''].fn, first) + t.equal(contentTypeParser.customParsers.get('').fn, first) }) t.end() @@ -306,7 +306,7 @@ test('remove', t => { contentTypeParser.remove('image/png') - t.same(Object.keys(contentTypeParser.customParsers).length, 2) + t.same(contentTypeParser.customParsers.size, 2) }) t.end() @@ -329,3 +329,69 @@ test('remove all should remove all existing parsers and reset cache', t => { t.same(contentTypeParser.parserRegExpList.length, 0) t.same(Object.keys(contentTypeParser.customParsers).length, 0) }) + +test('Safeguard against malicious content-type / 1', async t => { + const badNames = Object.getOwnPropertyNames({}.__proto__) // eslint-disable-line + t.plan(badNames.length) + + const fastify = Fastify() + + fastify.post('/', async () => { + return 'ok' + }) + + for (const prop of badNames) { + const response = await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': prop + }, + body: '' + }) + + t.same(response.statusCode, 415) + } +}) + +test('Safeguard against malicious content-type / 2', async t => { + t.plan(1) + + const fastify = Fastify() + + fastify.post('/', async () => { + return 'ok' + }) + + const response = await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': '\\u0063\\u006fnstructor' + }, + body: '' + }) + + t.same(response.statusCode, 415) +}) + +test('Safeguard against malicious content-type / 3', async t => { + t.plan(1) + + const fastify = Fastify() + + fastify.post('/', async () => { + return 'ok' + }) + + const response = await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'constructor; charset=utf-8' + }, + body: '' + }) + + t.same(response.statusCode, 415) +}) From 6b2aff6c6808bdc162b461ce68c025e00c58e878 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 10 Oct 2022 09:25:40 +0200 Subject: [PATCH 0113/1295] Bumped v4.8.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index db58b7fc673..29ba8d756bd 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.8.0' +const VERSION = '4.8.1' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 77606aa598e..e172d2bcc59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.8.0", + "version": "4.8.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 90fb718ea8239e59b3abdf6906fb82554d8cc0db Mon Sep 17 00:00:00 2001 From: KaKa Date: Mon, 10 Oct 2022 18:37:47 +0800 Subject: [PATCH 0114/1295] fix: error handler content-type guessing (#4329) * fix: error handler content-type guessing * test: more assertion * refactor: direct mutation --- lib/error-handler.js | 2 ++ test/content-type.test.js | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 test/content-type.test.js diff --git a/lib/error-handler.js b/lib/error-handler.js index 190489b13da..68c89097339 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -49,6 +49,8 @@ function handleError (reply, error, cb) { // In case the error handler throws, we set the next errorHandler so we can error again reply[kReplyNextErrorHandler] = Object.getPrototypeOf(errorHandler) + // we need to remove content-type to allow content-type guessing for serialization + delete reply[kReplyHeaders]['content-type'] delete reply[kReplyHeaders]['content-length'] const func = errorHandler.func diff --git a/test/content-type.test.js b/test/content-type.test.js new file mode 100644 index 00000000000..06e91f9b544 --- /dev/null +++ b/test/content-type.test.js @@ -0,0 +1,43 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') + +test('should remove content-type for setErrorHandler', async t => { + t.plan(8) + let count = 0 + + const fastify = Fastify() + fastify.setErrorHandler(function (error, request, reply) { + t.same(error.message, 'kaboom') + t.same(reply.hasHeader('content-type'), false) + reply.code(400).send({ foo: 'bar' }) + }) + fastify.addHook('onSend', async function (request, reply, payload) { + count++ + t.same(typeof payload, 'string') + switch (count) { + case 1: { + // should guess the correct content-type based on payload + t.same(reply.getHeader('content-type'), 'text/plain; charset=utf-8') + throw Error('kaboom') + } + case 2: { + // should guess the correct content-type based on payload + t.same(reply.getHeader('content-type'), 'application/json; charset=utf-8') + return payload + } + default: { + t.fail('should not reach') + } + } + }) + fastify.get('/', function (request, reply) { + reply.send('plain-text') + }) + + const { statusCode, body } = await fastify.inject({ method: 'GET', path: '/' }) + t.same(statusCode, 400) + t.same(body, JSON.stringify({ foo: 'bar' })) +}) From acba25acc3e97e3598daf79537a39a5272dba7b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 15:12:00 +0000 Subject: [PATCH 0115/1295] build(deps-dev): bump fluent-json-schema from 3.1.0 to 4.0.0 (#4331) Bumps [fluent-json-schema](https://github.com/fastify/fluent-json-schema) from 3.1.0 to 4.0.0. - [Release notes](https://github.com/fastify/fluent-json-schema/releases) - [Commits](https://github.com/fastify/fluent-json-schema/compare/v3.1.0...v4.0.0) --- updated-dependencies: - dependency-name: fluent-json-schema dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e172d2bcc59..cdcdfe7a6ff 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "fast-json-body": "^1.1.0", "fast-json-stringify": "^5.3.0", "fastify-plugin": "^4.2.1", - "fluent-json-schema": "^3.1.0", + "fluent-json-schema": "^4.0.0", "form-data": "^4.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", From d3e65c92782fbd65a7899392ea205f38c4a6a0ad Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Fri, 14 Oct 2022 21:57:46 +0200 Subject: [PATCH 0116/1295] feat: Supporting different content-type responses (#4264) --- docs/Reference/Reply.md | 75 ++++-- docs/Reference/Routes.md | 4 +- docs/Reference/Server.md | 2 +- .../Reference/Validation-and-Serialization.md | 40 ++- lib/errors.js | 8 + lib/reply.js | 43 +++- lib/schemas.js | 54 +++- lib/validation.js | 28 ++- test/internals/reply-serialize.test.js | 124 +++++++++- test/schema-serialization.test.js | 230 ++++++++++++++++++ types/reply.d.ts | 8 +- types/schema.d.ts | 1 + 12 files changed, 561 insertions(+), 56 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 886cc150881..b80c515b659 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -20,9 +20,9 @@ - [.callNotFound()](#callnotfound) - [.getResponseTime()](#getresponsetime) - [.type(contentType)](#typecontenttype) - - [.getSerializationFunction(schema | httpStatus)](#getserializationfunctionschema--httpstatus) - - [.compileSerializationSchema(schema, httpStatus)](#compileserializationschemaschema-httpstatus) - - [.serializeInput(data, [schema | httpStatus], [httpStatus])](#serializeinputdata-schema--httpstatus-httpstatus) + - [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus) + - [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus) + - [.serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])](#serializeinputdata-schema--httpstatus-httpstatus) - [.serializer(func)](#serializerfunc) - [.raw](#raw) - [.sent](#sent) @@ -63,16 +63,17 @@ object that exposes the following functions and properties: - `.serialize(payload)` - Serializes the specified payload using the default JSON serializer or using the custom serializer (if one is set) and returns the serialized payload. -- `.getSerializationFunction(schema | httpStatus)` - Returns the serialization +- `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the serialization function for the specified schema or http status, if any of either are set. -- `.compileSerializationSchema(schema, httpStatus)` - Compiles the specified - schema and returns a serialization function using the default (or customized) - `SerializerCompiler`. The optional `httpStatus` is forwarded to the - `SerializerCompiler` if provided, default to `undefined`. -- `.serializeInput(data, schema, [,httpStatus])` - Serializes the specified data - using the specified schema and returns the serialized payload. - If the optional `httpStatus` is provided, the function will use the serializer - function given for that HTTP Status Code. Default to `undefined`. +- `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles + the specified schema and returns a serialization function using the default + (or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded + to the `SerializerCompiler` if provided, default to `undefined`. +- `.serializeInput(data, schema, [,httpStatus], [contentType])` - Serializes + the specified data using the specified schema and returns the serialized payload. + If the optional `httpStatus`, and `contentType` are provided, the function + will use the serializer function given for that specific content type and + HTTP Status Code. Default to `undefined`. - `.serializer(function)` - Sets a custom serializer for the payload. - `.send(payload)` - Sends the payload to the user, could be a plain text, a buffer, JSON, stream, or an Error object. @@ -339,12 +340,12 @@ reply.type('text/html') If the `Content-Type` has a JSON subtype, and the charset parameter is not set, `utf-8` will be used as the charset by default. -### .getSerializationFunction(schema | httpStatus) +### .getSerializationFunction(schema | httpStatus, [contentType]) By calling this function using a provided `schema` or `httpStatus`, -it will return a `serialzation` function that can be used to -serialize diverse inputs. It returns `undefined` if no +and the optional `contentType`, it will return a `serialzation` function +that can be used to serialize diverse inputs. It returns `undefined` if no serialization function was found using either of the provided inputs. This heavily depends of the `schema#responses` attached to the route, or @@ -367,12 +368,18 @@ serialize({ foo: 'bar' }) // '{"foo":"bar"}' const serialize = reply .getSerializationFunction(200) serialize({ foo: 'bar' }) // '{"foo":"bar"}' + +// or + +const serialize = reply + .getSerializationFunction(200, 'application/json') +serialize({ foo: 'bar' }) // '{"foo":"bar"}' ``` -See [.compileSerializationSchema(schema, [httpStatus])](#compileserializationschema) +See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema) for more information on how to compile serialization schemas. -### .compileSerializationSchema(schema, httpStatus) +### .compileSerializationSchema(schema, [httpStatus], [contentType]) This function will compile a serialization schema and @@ -381,9 +388,9 @@ The function returned (a.k.a. _serialization function_) returned is compiled by using the provided `SerializerCompiler`. Also this is cached by using a `WeakMap` for reducing compilation calls. -The optional paramater `httpStatus`, if provided, is forwarded directly -the `SerializerCompiler`, so it can be used to compile the serialization -function if a custom `SerializerCompiler` is used. +The optional paramaters `httpStatus` and `contentType`, if provided, +are forwarded directly to the `SerializerCompiler`, so it can be used +to compile the serialization function if a custom `SerializerCompiler` is used. This heavily depends of the `schema#responses` attached to the route, or the serialization functions compiled by using `compileSerializationSchema`. @@ -412,6 +419,23 @@ const serialize = reply } }, 200) serialize({ foo: 'bar' }) // '{"foo":"bar"}' + +// or + +const serialize = reply + .compileSerializationSchema({ + '3xx': { + content: { + 'application/json': { + schema: { + name: { type: 'string' }, + phone: { type: 'number' } + } + } + } + } + }, '3xx', 'application/json') +serialize({ name: 'Jone', phone: 201090909090 }) // '{"name":"Jone", "phone":201090909090}' ``` Note that you should be careful when using this function, as it will cache @@ -461,14 +485,14 @@ const newSerialize = reply.compileSerializationSchema(newSchema) console.log(newSerialize === serialize) // false ``` -### .serializeInput(data, [schema | httpStatus], [httpStatus]) +### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType]) This function will serialize the input data based on the provided schema, or http status code. If both provided, the `httpStatus` will take presedence. If there is not a serialization function for a given `schema`, a new serialization -function will be compiled forwarding the `httpStatus` if provided. +function will be compiled forwarding the `httpStatus`, and the `contentType` if provided. ```js reply @@ -497,9 +521,14 @@ reply reply .serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}' + +// or + +reply + .serializeInput({ name: 'Jone', age: 18 }, '200', 'application/vnd.v1+json') // '{"name": "Jone", "age": 18}' ``` -See [.compileSerializationSchema(schema, [httpStatus])](#compileserializationschema) +See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema) for more information on how to compile serialization schemas. ### .serializer(func) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 03969d5091c..4dec6535d31 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -94,8 +94,8 @@ fastify.route(options) schemas for request validations. See the [Validation and Serialization](./Validation-and-Serialization.md#schema-validator) documentation. -* `serializerCompiler({ { schema, method, url, httpStatus } })`: function that - builds schemas for response serialization. See the [Validation and +* `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`: + function that builds schemas for response serialization. See the [Validation and Serialization](./Validation-and-Serialization.md#schema-serializer) documentation. * `schemaErrorFormatter(errors, dataVar)`: function that formats the errors from diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index c3150dfcddd..77e7805b41e 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1370,7 +1370,7 @@ const fastify = Fastify({ buildSerializer: function factory (externalSchemas, serializerOptsServerOption) { // This factory function must return a schema serializer compiler. // See [#schema-serializer](./Validation-and-Serialization.md#schema-serializer) for details. - return function serializerCompiler ({ schema, method, url, httpStatus }) { + return function serializerCompiler ({ schema, method, url, httpStatus, contentType }) { return data => JSON.stringify(data) } } diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 9e29caa1b81..7732fb863dc 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -611,6 +611,44 @@ const schema = { fastify.post('/the/url', { schema }, handler) ``` +You can even have a specific response schema for different content types. +For example: +```js +const schema = { + response: { + 200: { + description: 'Response schema that support different content types' + content: { + 'application/json': { + schema: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } + }, + 'application/vnd.v1+json': { + schema: { + type: 'array', + items: { $ref: 'test' } + } + } + } + }, + '3xx': { + content: { + 'application/vnd.v2+json': { + schema: { + fullName: { type: 'string' }, + phone: { type: 'string' } + } + } + } + } + } + } + +fastify.post('/url', { schema }, handler) +``` #### Serializer Compiler @@ -621,7 +659,7 @@ change the default serialization method by providing a function to serialize every route where you do. ```js -fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { +fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => { return data => JSON.stringify(data) }) diff --git a/lib/errors.js b/lib/errors.js index a992a202eaf..dbbad6d77c9 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -165,6 +165,10 @@ const codes = { 'FST_ERR_MISSING_SERIALIZATION_FN', 'Missing serialization function. Key "%s"' ), + FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN: createError( + 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN', + 'Missing serialization function. Key "%s:%s"' + ), FST_ERR_REQ_INVALID_VALIDATION_INVOCATION: createError( 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION', 'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.' @@ -181,6 +185,10 @@ const codes = { 'FST_ERR_SCH_ALREADY_PRESENT', "Schema with id '%s' already declared!" ), + FST_ERR_SCH_CONTENT_MISSING_SCHEMA: createError( + 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA', + "Schema is missing for the content type '%s'" + ), FST_ERR_SCH_DUPLICATE: createError( 'FST_ERR_SCH_DUPLICATE', "Schema with '%s' already present!" diff --git a/lib/reply.js b/lib/reply.js index 9420f78d78a..68f02de06dc 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -44,7 +44,8 @@ const { FST_ERR_BAD_STATUS_CODE, FST_ERR_BAD_TRAILER_NAME, FST_ERR_BAD_TRAILER_VALUE, - FST_ERR_MISSING_SERIALIZATION_FN + FST_ERR_MISSING_SERIALIZATION_FN, + FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') const warning = require('./warnings') @@ -312,11 +313,15 @@ Reply.prototype.code = function (code) { Reply.prototype.status = Reply.prototype.code -Reply.prototype.getSerializationFunction = function (schemaOrStatus) { +Reply.prototype.getSerializationFunction = function (schemaOrStatus, contentType) { let serialize if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') { - serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus] + if (typeof contentType === 'string') { + serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]?.[contentType] + } else { + serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus] + } } else if (typeof schemaOrStatus === 'object') { serialize = this[kRouteContext][kReplySerializeWeakMap]?.get(schemaOrStatus) } @@ -324,7 +329,7 @@ Reply.prototype.getSerializationFunction = function (schemaOrStatus) { return serialize } -Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null) { +Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null, contentType = null) { const { request } = this const { method, url } = request @@ -346,7 +351,8 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null schema, method, url, - httpStatus + httpStatus, + contentType }) // We create a WeakMap to compile the schema only once @@ -363,22 +369,34 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null return serializeFn } -Reply.prototype.serializeInput = function (input, schema, httpStatus) { +Reply.prototype.serializeInput = function (input, schema, httpStatus, contentType) { + const possibleContentType = httpStatus let serialize httpStatus = typeof schema === 'string' || typeof schema === 'number' ? schema : httpStatus + contentType = httpStatus && possibleContentType !== httpStatus + ? possibleContentType + : contentType + if (httpStatus != null) { - serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus] + if (contentType != null) { + serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]?.[contentType] + } else { + serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus] + } - if (serialize == null) throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus) + if (serialize == null) { + if (contentType) throw new FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN(httpStatus, contentType) + throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus) + } } else { // Check if serialize function already compiled if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) { serialize = this[kRouteContext][kReplySerializeWeakMap].get(schema) } else { - serialize = this.compileSerializationSchema(schema, httpStatus) + serialize = this.compileSerializationSchema(schema, httpStatus, contentType) } } @@ -483,7 +501,7 @@ function preserializeHookEnd (err, request, reply, payload) { } else if (reply[kRouteContext] && reply[kRouteContext][kReplySerializerDefault]) { payload = reply[kRouteContext][kReplySerializerDefault](payload, reply.raw.statusCode) } else { - payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode) + payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode, reply[kReplyHeaders]['content-type']) } } catch (e) { wrapSeralizationError(e, reply) @@ -797,10 +815,11 @@ function notFound (reply) { * @param {object} context the request context * @param {object} data the JSON payload to serialize * @param {number} statusCode the http status code + * @param {string} contentType the reply content type * @returns {string} the serialized payload */ -function serialize (context, data, statusCode) { - const fnSerialize = getSchemaSerializer(context, statusCode) +function serialize (context, data, statusCode, contentType) { + const fnSerialize = getSchemaSerializer(context, statusCode, contentType) if (fnSerialize) { return fnSerialize(data) } diff --git a/lib/schemas.js b/lib/schemas.js index 52c44647ef2..f9fc0488ebd 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -7,7 +7,8 @@ const kFluentSchema = Symbol.for('fluent-schema-object') const { FST_ERR_SCH_MISSING_ID, FST_ERR_SCH_ALREADY_PRESENT, - FST_ERR_SCH_DUPLICATE + FST_ERR_SCH_DUPLICATE, + FST_ERR_SCH_CONTENT_MISSING_SCHEMA } = require('./errors') const SCHEMAS_SOURCE = ['params', 'body', 'querystring', 'query', 'headers'] @@ -86,7 +87,27 @@ function normalizeSchema (routeSchemas, serverOptions) { if (routeSchemas.response) { const httpCodes = Object.keys(routeSchemas.response) for (const code of httpCodes) { - routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code], serverOptions.jsonShorthand) + const contentProperty = routeSchemas.response[code].content + + let hasContentMultipleContentTypes = false + if (contentProperty) { + const keys = Object.keys(contentProperty) + for (let i = 0; i < keys.length; i++) { + const mediaName = keys[i] + if (!contentProperty[mediaName].schema) { + if (keys.length === 1) { break } + throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(mediaName) + } + routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(contentProperty[mediaName].schema, serverOptions.jsonShorthand) + if (i === keys.length - 1) { + hasContentMultipleContentTypes = true + } + } + } + + if (!hasContentMultipleContentTypes) { + routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code], serverOptions.jsonShorthand) + } } } @@ -129,22 +150,49 @@ function getSchemaAnyway (schema, jsonShorthand) { * * @param {object} context the request context * @param {number} statusCode the http status code + * @param {string} contentType the reply content type * @returns {function|boolean} the right JSON Schema function to serialize * the reply or false if it is not set */ -function getSchemaSerializer (context, statusCode) { +function getSchemaSerializer (context, statusCode, contentType) { const responseSchemaDef = context[kSchemaResponse] if (!responseSchemaDef) { return false } if (responseSchemaDef[statusCode]) { + if (responseSchemaDef[statusCode].constructor === Object) { + const mediaName = contentType.split(';')[0] + if (responseSchemaDef[statusCode][mediaName]) { + return responseSchemaDef[statusCode][mediaName] + } + + return false + } return responseSchemaDef[statusCode] } const fallbackStatusCode = (statusCode + '')[0] + 'xx' if (responseSchemaDef[fallbackStatusCode]) { + if (responseSchemaDef[fallbackStatusCode].constructor === Object) { + const mediaName = contentType.split(';')[0] + if (responseSchemaDef[fallbackStatusCode][mediaName]) { + return responseSchemaDef[fallbackStatusCode][mediaName] + } + + return false + } + return responseSchemaDef[fallbackStatusCode] } if (responseSchemaDef.default) { + if (responseSchemaDef.default.constructor === Object) { + const mediaName = contentType.split(';')[0] + if (responseSchemaDef.default[mediaName]) { + return responseSchemaDef.default[mediaName] + } + + return false + } + return responseSchemaDef.default } return false diff --git a/lib/validation.js b/lib/validation.js index 939b5c53863..321b922c182 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -22,12 +22,28 @@ function compileSchemasForSerialization (context, compile) { throw new Error('response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') } - acc[statusCode] = compile({ - schema, - url, - method, - httpStatus: statusCode - }) + if (schema.content) { + const contentTypesSchemas = {} + for (const mediaName of Object.keys(schema.content)) { + const contentSchema = schema.content[mediaName].schema + contentTypesSchemas[mediaName] = compile({ + schema: contentSchema, + url, + method, + httpStatus: statusCode, + contentType: mediaName + }) + } + acc[statusCode] = contentTypesSchemas + } else { + acc[statusCode] = compile({ + schema, + url, + method, + httpStatus: statusCode + }) + } + return acc }, {}) } diff --git a/test/internals/reply-serialize.test.js b/test/internals/reply-serialize.test.js index 66ecd7aee9d..2166c7bb915 100644 --- a/test/internals/reply-serialize.test.js +++ b/test/internals/reply-serialize.test.js @@ -45,6 +45,16 @@ function getResponseSchema () { type: 'string' } } + }, + '3xx': { + content: { + 'application/json': { + schema: { + fullName: { type: 'string' }, + phone: { type: 'number' } + } + } + } } } } @@ -141,7 +151,20 @@ test('Reply#compileSerializationSchema', t => { } } - t.plan(10) + const custom2 = ({ schema, httpStatus, url, method, contentType }) => { + t.equal(schema, schemaObj) + t.equal(url, '/user') + t.equal(method, 'GET') + t.equal(httpStatus, '3xx') + t.equal(contentType, 'application/json') + + return input => { + t.same(input, { fullName: 'Jone', phone: 1090243795 }) + return JSON.stringify(input) + } + } + + t.plan(17) const schemaObj = getDefaultSchema() fastify.get('/', { serializerCompiler: custom }, (req, reply) => { @@ -157,10 +180,22 @@ test('Reply#compileSerializationSchema', t => { reply.send({ hello: 'world' }) }) + fastify.get('/user', { serializerCompiler: custom2 }, (req, reply) => { + const input = { fullName: 'Jone', phone: 1090243795 } + const first = reply.compileSerializationSchema(schemaObj, '3xx', 'application/json') + t.ok(first(input), JSON.stringify(input)) + reply.send(input) + }) + await fastify.inject({ path: '/', method: 'GET' }) + + await fastify.inject({ + path: '/user', + method: 'GET' + }) } ) @@ -209,10 +244,19 @@ test('Reply#getSerializationFunction', t => { status: 'error', code: 'something' } + const okInput3xx = { + fullName: 'Jone', + phone: 0 + } + const noOkInput3xx = { + fullName: 'Jone', + phone: 'phone' + } let cached4xx let cached201 + let cachedJson3xx - t.plan(9) + t.plan(13) const responseSchema = getResponseSchema() @@ -234,15 +278,19 @@ test('Reply#getSerializationFunction', t => { if (Number(id) === 1) { const serialize4xx = reply.getSerializationFunction('4xx') const serialize201 = reply.getSerializationFunction(201) + const serializeJson3xx = reply.getSerializationFunction('3xx', 'application/json') const serializeUndefined = reply.getSerializationFunction(undefined) cached4xx = serialize4xx cached201 = serialize201 + cachedJson3xx = serializeJson3xx t.type(serialize4xx, Function) t.type(serialize201, Function) + t.type(serializeJson3xx, Function) t.equal(serialize4xx(okInput4xx), JSON.stringify(okInput4xx)) t.equal(serialize201(okInput201), JSON.stringify(okInput201)) + t.equal(serializeJson3xx(okInput3xx), JSON.stringify(okInput3xx)) t.notOk(serializeUndefined) try { @@ -260,13 +308,21 @@ test('Reply#getSerializationFunction', t => { t.equal(err.message, '"status" is required!') } + try { + serializeJson3xx(noOkInput3xx) + } catch (err) { + t.equal(err.message, 'The value "phone" cannot be converted to a number.') + } + reply.status(201).send(okInput201) } else { const serialize201 = reply.getSerializationFunction(201) const serialize4xx = reply.getSerializationFunction('4xx') + const serializeJson3xx = reply.getSerializationFunction('3xx', 'application/json') t.equal(serialize4xx, cached4xx) t.equal(serialize201, cached201) + t.equal(serializeJson3xx, cachedJson3xx) reply.status(401).send(okInput4xx) } } @@ -367,7 +423,7 @@ test('Reply#getSerializationFunction', t => { }) test('Reply#serializeInput', t => { - t.plan(5) + t.plan(6) t.test( 'Should throw if missed serialization function from HTTP status', @@ -395,6 +451,47 @@ test('Reply#serializeInput', t => { } ) + t.test( + 'Should throw if missed serialization function from HTTP status with specific content type', + async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.get('/', { + schema: { + response: { + '3xx': { + content: { + 'application/json': { + schema: { + fullName: { type: 'string' }, + phone: { type: 'number' } + } + } + } + } + } + } + }, (req, reply) => { + reply.serializeInput({}, '3xx', 'application/vnd.v1+json') + }) + + const result = await fastify.inject({ + path: '/', + method: 'GET' + }) + + t.equal(result.statusCode, 500) + t.same(result.json(), { + statusCode: 500, + code: 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN', + error: 'Internal Server Error', + message: 'Missing serialization function. Key "3xx:application/vnd.v1+json"' + }) + } + ) + t.test('Should use a serializer fn from HTTP status', async t => { const fastify = Fastify() const okInput201 = { @@ -413,8 +510,16 @@ test('Reply#serializeInput', t => { status: 'error', code: 'something' } + const okInput3xx = { + fullName: 'Jone', + phone: 0 + } + const noOkInput3xx = { + fullName: 'Jone', + phone: 'phone' + } - t.plan(4) + t.plan(6) fastify.get( '/', @@ -438,6 +543,17 @@ test('Reply#serializeInput', t => { JSON.stringify(okInput201) ) + t.equal( + reply.serializeInput(okInput3xx, {}, '3xx', 'application/json'), + JSON.stringify(okInput3xx) + ) + + try { + reply.serializeInput(noOkInput3xx, '3xx', 'application/json') + } catch (err) { + t.equal(err.message, 'The value "phone" cannot be converted to a number.') + } + try { reply.serializeInput(notOkInput4xx, '4xx') } catch (err) { diff --git a/test/schema-serialization.test.js b/test/schema-serialization.test.js index 38c25f2dee0..e4e5a9c7bb5 100644 --- a/test/schema-serialization.test.js +++ b/test/schema-serialization.test.js @@ -60,6 +60,236 @@ test('custom serializer options', t => { }) }) +test('Different content types', t => { + t.plan(32) + + const fastify = Fastify() + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + verified: { type: 'boolean' } + } + }) + + fastify.get('/', { + schema: { + response: { + 200: { + content: { + 'application/json': { + schema: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } + }, + 'application/vnd.v1+json': { + schema: { + type: 'array', + items: { $ref: 'test' } + } + } + } + }, + 201: { + content: { type: 'string' } + }, + 202: { + content: { const: 'Processing exclusive content' } + }, + '3xx': { + content: { + 'application/vnd.v2+json': { + schema: { + fullName: { type: 'string' }, + phone: { type: 'string' } + } + } + } + }, + default: { + content: { + 'application/json': { + schema: { + details: { type: 'string' } + } + } + } + } + } + } + }, function (req, reply) { + switch (req.headers.accept) { + case 'application/json': + reply.header('Content-Type', 'application/json') + reply.send({ id: 1, name: 'Foo', image: 'profile picture', address: 'New Node' }) + break + case 'application/vnd.v1+json': + reply.header('Content-Type', 'application/vnd.v1+json') + reply.send([{ id: 2, name: 'Boo', age: 18, verified: false }, { id: 3, name: 'Woo', age: 30, verified: true }]) + break + case 'application/vnd.v2+json': + reply.header('Content-Type', 'application/vnd.v2+json') + reply.code(300) + reply.send({ fullName: 'Jhon Smith', phone: '01090000000', authMethod: 'google' }) + break + case 'application/vnd.v3+json': + reply.header('Content-Type', 'application/vnd.v3+json') + reply.code(300) + reply.send({ firstName: 'New', lastName: 'Hoo', country: 'eg', city: 'node' }) + break + case 'application/vnd.v4+json': + reply.header('Content-Type', 'application/vnd.v4+json') + reply.code(201) + reply.send({ boxId: 1, content: 'Games' }) + break + case 'application/vnd.v5+json': + reply.header('Content-Type', 'application/vnd.v5+json') + reply.code(202) + reply.send({ content: 'interesting content' }) + break + case 'application/vnd.v6+json': + reply.header('Content-Type', 'application/vnd.v6+json') + reply.code(400) + reply.send({ desc: 'age is missing', details: 'validation error' }) + break + case 'application/vnd.v7+json': + reply.code(400) + reply.send({ details: 'validation error' }) + break + default: + // to test if schema not found + reply.header('Content-Type', 'application/vnd.v3+json') + reply.code(200) + reply.send([{ type: 'student', grade: 6 }, { type: 'student', grade: 9 }]) + } + }) + + fastify.get('/test', { + serializerCompiler: ({ contentType }) => { + t.equal(contentType, 'application/json') + return data => JSON.stringify(data) + }, + schema: { + response: { + 200: { + content: { + 'application/json': { + schema: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } + } + } + } + } + } + }, function (req, reply) { + reply.header('Content-Type', 'application/json') + reply.send({ age: 18, city: 'AU' }) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ name: 'Foo', image: 'profile picture', address: 'New Node' })) + t.equal(res.statusCode, 200) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v1+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify([{ name: 'Boo', age: 18, verified: false }, { name: 'Woo', age: 30, verified: true }])) + t.equal(res.statusCode, 200) + }) + + fastify.inject({ method: 'GET', url: '/' }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify([{ type: 'student', grade: 6 }, { type: 'student', grade: 9 }])) + t.equal(res.statusCode, 200) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v2+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ fullName: 'Jhon Smith', phone: '01090000000' })) + t.equal(res.statusCode, 300) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v3+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ firstName: 'New', lastName: 'Hoo', country: 'eg', city: 'node' })) + t.equal(res.statusCode, 300) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v4+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ content: 'Games' })) + t.equal(res.statusCode, 201) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v5+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ content: 'Processing exclusive content' })) + t.equal(res.statusCode, 202) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v6+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ desc: 'age is missing', details: 'validation error' })) + t.equal(res.statusCode, 400) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v7+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ details: 'validation error' })) + t.equal(res.statusCode, 400) + }) + + fastify.inject({ method: 'GET', url: '/test' }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ age: 18, city: 'AU' })) + t.equal(res.statusCode, 200) + }) +}) + +test('Invalid multiple content schema, throw FST_ERR_SCH_CONTENT_MISSING_SCHEMA error', t => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/testInvalid', { + schema: { + response: { + 200: { + content: { + 'application/json': { + schema: { + fullName: { type: 'string' }, + phone: { type: 'string' } + }, + example: { + fullName: 'John Doe', + phone: '201090243795' + } + }, + type: 'string' + } + } + } + } + }, function (req, reply) { + reply.header('Content-Type', 'application/json') + reply.send({ fullName: 'Any name', phone: '0109001010' }) + }) + + fastify.ready((err) => { + t.equal(err.message, "Schema is missing for the content type 'type'") + t.equal(err.statusCode, 500) + t.equal(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + }) +}) + test('Use the same schema id in different places', t => { t.plan(2) const fastify = Fastify() diff --git a/types/reply.d.ts b/types/reply.d.ts index 5cc08f41d8e..11ea23d0b9a 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -55,10 +55,10 @@ export interface FastifyReply< serializer(fn: (payload: any) => string): FastifyReply; serialize(payload: any): string | ArrayBuffer | Buffer; // Serialization Methods - getSerializationFunction(httpStatus: string): (payload: {[key: string]: unknown}) => string; + getSerializationFunction(httpStatus: string, contentType?: string): (payload: {[key: string]: unknown}) => string; getSerializationFunction(schema: {[key: string]: unknown}): (payload: {[key: string]: unknown}) => string; - compileSerializationSchema(schema: {[key: string]: unknown}, httpStatus?: string): (payload: {[key: string]: unknown}) => string; - serializeInput(input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string): string; - serializeInput(input: {[key: string]: unknown}, httpStatus: string): unknown; + compileSerializationSchema(schema: {[key: string]: unknown}, httpStatus?: string, contentType?: string): (payload: {[key: string]: unknown}) => string; + serializeInput(input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string, contentType?: string): string; + serializeInput(input: {[key: string]: unknown}, httpStatus: string, contentType?: string): unknown; then(fulfilled: () => void, rejected: (err: Error) => void): void; } diff --git a/types/schema.d.ts b/types/schema.d.ts index bb0fd709168..e5f5eea0341 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -20,6 +20,7 @@ export interface FastifyRouteSchemaDef { url: string; httpPart?: string; httpStatus?: string; + contentType?: string; } export interface FastifySchemaValidationError { From c09367e2c8504b7f652e853b38b1e19650f9df16 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 15 Oct 2022 11:53:53 +0200 Subject: [PATCH 0117/1295] fix: should now call default schema compilers (#4340) --- lib/schema-controller.js | 14 +++++++---- test/schema-special-usage.test.js | 42 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/lib/schema-controller.js b/lib/schema-controller.js index 78c4437678f..8c746735ff4 100644 --- a/lib/schema-controller.js +++ b/lib/schema-controller.js @@ -15,12 +15,16 @@ function buildSchemaController (parentSchemaCtrl, opts) { return new SchemaController(parentSchemaCtrl, opts) } - let compilersFactory = { - buildValidator: ValidatorSelector(), - buildSerializer: SerializerSelector() + const compilersFactory = Object.assign({ + buildValidator: null, + buildSerializer: null + }, opts?.compilersFactory) + + if (!compilersFactory.buildValidator) { + compilersFactory.buildValidator = ValidatorSelector() } - if (opts && opts.compilersFactory) { - compilersFactory = Object.assign(compilersFactory, opts.compilersFactory) + if (!compilersFactory.buildSerializer) { + compilersFactory.buildSerializer = SerializerSelector() } const option = { diff --git a/test/schema-special-usage.test.js b/test/schema-special-usage.test.js index 8f438c2721d..1fc99dc6184 100644 --- a/test/schema-special-usage.test.js +++ b/test/schema-special-usage.test.js @@ -702,3 +702,45 @@ test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t => await fastify.ready() t.pass('fastify is ready') }) + +test('The default schema compilers should not be called when overwritte by the user', async t => { + const Fastify = t.mock('../', { + '@fastify/ajv-compiler': () => { + t.fail('The default validator compiler should not be called') + }, + '@fastify/fast-json-stringify-compiler': () => { + t.fail('The default serializer compiler should not be called') + } + }) + + const fastify = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: function factory () { + t.pass('The custom validator compiler should be called') + return function validatorCompiler () { + return () => { return true } + } + }, + buildSerializer: function factory () { + t.pass('The custom serializer compiler should be called') + return function serializerCompiler () { + return () => { return true } + } + } + } + } + }) + + fastify.get('/', + { + schema: { + query: { foo: { type: 'string' } }, + response: { + 200: { type: 'object' } + } + } + }, () => {}) + + await fastify.ready() +}) From 77b75820be399d8d28bbda676c130ee2b90c3f52 Mon Sep 17 00:00:00 2001 From: Emanuele Bertoldi Date: Sun, 16 Oct 2022 10:17:15 +0200 Subject: [PATCH 0118/1295] docs(ecosystem): replace heply -> beliven due to rebranding (#4334) * docs: replace heply -> beliven due to rebranding * chore: fix lint Co-authored-by: Manuel Spigolon --- docs/Guides/Ecosystem.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index a65a75a0f29..f97114c0b22 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -235,7 +235,7 @@ section. send HTTP requests via [axios](https://github.com/axios/axios). - [`fastify-babel`](https://github.com/cfware/fastify-babel) Fastify plugin for development servers that require Babel transformations of JavaScript sources. -- [`fastify-bcrypt`](https://github.com/heply/fastify-bcrypt) A Bcrypt hash +- [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash generator & checker. - [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your routes to the console, so you definitely know which endpoints are available. @@ -263,8 +263,8 @@ section. Sequelize ORM. - [`fastify-couchdb`](https://github.com/nigelhanlon/fastify-couchdb) Fastify plugin to add CouchDB support via [nano](https://github.com/apache/nano). -- [`fastify-crud-generator`](https://github.com/heply/fastify-crud-generator) A - plugin to rapidly generate CRUD routes for any entity. +- [`fastify-crud-generator`](https://github.com/beliven-it/fastify-crud-generator) + A plugin to rapidly generate CRUD routes for any entity. - [`fastify-custom-healthcheck`](https://github.com/gkampitakis/fastify-custom-healthcheck) Fastify plugin to add health route in your server that asserts custom functions. @@ -453,7 +453,7 @@ section. Fastify plugin for memoize responses by expressive settings. - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker thread pool plugin using [Piscina](https://github.com/piscinajs/piscina). -- [`fastify-polyglot`](https://github.com/heply/fastify-polyglot) A plugin to +- [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin to handle i18n using [node-polyglot](https://www.npmjs.com/package/node-polyglot). - [`fastify-postgraphile`](https://github.com/alemagio/fastify-postgraphile) @@ -547,7 +547,7 @@ section. [Tokenize](https://github.com/Bowser65/Tokenize) plugin for Fastify that removes the pain of managing authentication tokens, with built-in integration for `fastify-auth`. -- [`fastify-totp`](https://github.com/heply/fastify-totp) A plugin to handle +- [`fastify-totp`](https://github.com/beliven-it/fastify-totp) A plugin to handle TOTP (e.g. for 2FA). - [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools) Useful functions for Twitch Extension Backend Services (EBS). From a4dc6445c04980ba5fcc2be483019be625020b45 Mon Sep 17 00:00:00 2001 From: Black-Hole <158blackhole@gmail.com> Date: Mon, 17 Oct 2022 14:21:51 +0800 Subject: [PATCH 0119/1295] docs(ecosystem): add @fastify-userland plugins and tools (#4345) * docs(ecosystem): add @fastify-userland plugins and tools * fix(ci): actions failed * fix(ci): actions failed again --- docs/Guides/Ecosystem.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f97114c0b22..50f9dd06bf7 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -155,6 +155,10 @@ section. - [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server) A plugin to easily create git server and make one/many Git repositories available for clone/fetch/push through the standard `git` (over http) commands. +- [`@fastify-userland/request-id`](https://github.com/fastify-userland/request-id) + Fastify Request ID Plugin +- [`@fastify-userland/typeorm-query-runner`](https://github.com/fastify-userland/typeorm-query-runner) + Fastify typeorm QueryRunner plugin - [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server) Tiny (~5k), Fast, KISS, and dependency-free Node.JS library to make your Fastify API graceful. @@ -606,6 +610,8 @@ section. and updated Typeorm plugin for use with Fastify. #### [Community Tools](#community-tools) +- [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows) + Reusable workflows for use in the Fastify plugin - [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration generator by directory structure. - [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to From d6e05434ad77e9fc41c4815395130a1d759a06ae Mon Sep 17 00:00:00 2001 From: jhhom Date: Mon, 17 Oct 2022 14:40:42 +0800 Subject: [PATCH 0120/1295] fix: throws an error when registering invalid hook functions (#4332) * validate registered hook functions * move hooks validation to after onRoute hooks execution * improve hook validation * add test for hook registration by onRoute hook * use custom fastify error * reuse the existing hook error --- lib/errors.js | 2 +- lib/hooks.js | 2 +- lib/route.js | 17 +++++++++++++++- test/hooks.test.js | 38 ++++++++++++++++++++++++++++++++++++ test/internals/hooks.test.js | 6 +++--- 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index dbbad6d77c9..f34b21481c5 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -97,7 +97,7 @@ const codes = { ), FST_ERR_HOOK_INVALID_HANDLER: createError( 'FST_ERR_HOOK_INVALID_HANDLER', - 'The hook callback must be a function', + '%s hook should be a function, instead got %s', 500, TypeError ), diff --git a/lib/hooks.js b/lib/hooks.js index 5111f1ed432..d1bdadad12d 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -49,10 +49,10 @@ function Hooks () { Hooks.prototype.validate = function (hook, fn) { if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE() - if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER() if (supportedHooks.indexOf(hook) === -1) { throw new Error(`${hook} hook not supported!`) } + if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof fn) } Hooks.prototype.add = function (hook, fn) { diff --git a/lib/route.js b/lib/route.js index 5aeef399e5f..c5b99eb660d 100644 --- a/lib/route.js +++ b/lib/route.js @@ -20,7 +20,8 @@ const { FST_ERR_DEFAULT_ROUTE_INVALID_TYPE, FST_ERR_DUPLICATED_ROUTE, FST_ERR_INVALID_URL, - FST_ERR_SEND_UNDEFINED_ERR + FST_ERR_SEND_UNDEFINED_ERR, + FST_ERR_HOOK_INVALID_HANDLER } = require('./errors') const { @@ -243,6 +244,20 @@ function buildRouting (options) { } } + for (const hook of lifecycleHooks) { + if (opts && hook in opts) { + if (Array.isArray(opts[hook])) { + for (const func of opts[hook]) { + if (typeof func !== 'function') { + throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof func) + } + } + } else if (typeof opts[hook] !== 'function') { + throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof opts[hook]) + } + } + } + const constraints = opts.constraints || {} const config = { ...opts.config, diff --git a/test/hooks.test.js b/test/hooks.test.js index eef4173374e..20578f708c7 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3301,3 +3301,41 @@ test('onTimeout should be triggered and socket _meta is set', t => { }) }) }) + +test('registering invalid hooks should throw an error', async t => { + t.plan(3) + + const fastify = Fastify() + + t.throws(() => { + fastify.route({ + method: 'GET', + path: '/invalidHook', + onRequest: [undefined], + async handler () { + return 'hello world' + } + }) + }, new Error('onRequest hook should be a function, instead got undefined')) + + t.throws(() => { + fastify.route({ + method: 'GET', + path: '/invalidHook', + onRequest: undefined, + async handler () { + return 'hello world' + } + }) + }, new Error('onRequest hook should be a function, instead got undefined')) + + t.throws(() => { + fastify.addHook('onRoute', (routeOptions) => { + routeOptions.onSend = [undefined] + }) + + fastify.get('/', function (request, reply) { + reply.send('hello world') + }) + }, new Error('onSend hook should be a function, instead got undefined')) +}) diff --git a/test/internals/hooks.test.js b/test/internals/hooks.test.js index 38594aa82f4..f5aed5d6440 100644 --- a/test/internals/hooks.test.js +++ b/test/internals/hooks.test.js @@ -66,7 +66,7 @@ test('should throw on wrong parameters', t => { const hooks = new Hooks() t.plan(4) try { - hooks.add(null, noop) + hooks.add(null, () => {}) t.fail() } catch (e) { t.equal(e.code, 'FST_ERR_HOOK_INVALID_TYPE') @@ -74,10 +74,10 @@ test('should throw on wrong parameters', t => { } try { - hooks.add('', null) + hooks.add('onSend', null) t.fail() } catch (e) { t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') - t.equal(e.message, 'The hook callback must be a function') + t.equal(e.message, 'onSend hook should be a function, instead got object') } }) From f1480fc03b88343f6830239040e77dfac0e30682 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Oct 2022 09:43:12 +0200 Subject: [PATCH 0121/1295] Bumped v4.9.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index 29ba8d756bd..4e691582de5 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.8.1' +const VERSION = '4.9.0' const Avvio = require('avvio') const http = require('http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index 63c33edebcf..529977af9cf 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -217,7 +217,7 @@ class Serializer { } - + module.exports = main diff --git a/package.json b/package.json index cdcdfe7a6ff..8dc9e322821 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.8.1", + "version": "4.9.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 37c24267e24a5696fe74e4c140fccee629a6f8f2 Mon Sep 17 00:00:00 2001 From: D10f <48509601+D10f@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:11:22 +0200 Subject: [PATCH 0122/1295] docs(reply): specify streams content-type (#4337) --- docs/Reference/Reply.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index b80c515b659..8f489ef6a80 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -656,13 +656,13 @@ fastify.get('/json', options, function (request, reply) { #### Streams -*send* can also handle streams out of the box. If you are sending a stream and -you have not set a `'Content-Type'` header, *send* will set it at +*send* can also handle streams by setting the `'Content-Type'` header to `'application/octet-stream'`. ```js fastify.get('/streams', function (request, reply) { const fs = require('fs') const stream = fs.createReadStream('some-file', 'utf8') + reply.header('Content-Type', 'application/octet-stream') reply.send(stream) }) ``` From 846885f2b3622ca91c94ee3d357047340aeb2d64 Mon Sep 17 00:00:00 2001 From: Black-Hole <158blackhole@gmail.com> Date: Mon, 17 Oct 2022 18:11:47 +0800 Subject: [PATCH 0123/1295] fix(logger): lost setBindings type in FastifyBaseLogger (#4346) --- test/logger.test.js | 14 ++++++++++ test/types/logger.test-d.ts | 4 ++- test/types/request.test-d.ts | 3 ++- types/hooks.d.ts | 50 ++++++++++++++++++------------------ types/instance.d.ts | 42 +++++++++++++++--------------- types/logger.d.ts | 10 +++++--- types/plugin.d.ts | 6 ++--- types/reply.d.ts | 7 ++--- types/request.d.ts | 4 +-- types/route.d.ts | 18 ++++++------- 10 files changed, 90 insertions(+), 68 deletions(-) diff --git a/test/logger.test.js b/test/logger.test.js index 48ba39a2035..573c4a90220 100644 --- a/test/logger.test.js +++ b/test/logger.test.js @@ -1687,3 +1687,17 @@ test('should not throw error when serializing custom req', t => { t.same(lines[0].req, {}) }) + +test('set bindings', t => { + t.plan(1) + + const stream = split(JSON.parse) + stream.once('data', info => { + t.same(info.hello, 'world') + }) + + const fastify = Fastify({ logger: { level: 'info', stream } }) + + fastify.log.setBindings({ hello: 'world' }) + fastify.log.info('hello world') +}) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index d8e6b9cbfa8..d78cd2e54c6 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -10,6 +10,7 @@ import fastify, { import { Server, IncomingMessage, ServerResponse } from 'http' import * as fs from 'fs' import P from 'pino' +import { DefaultFastifyLogger } from '../../types/logger' expectType(fastify().log) @@ -24,7 +25,7 @@ class Foo {} expectType(fastify().log[logLevel as LogLevel](new Foo())) }) -interface CustomLogger extends FastifyBaseLogger { +interface CustomLogger extends DefaultFastifyLogger { customMethod(msg: string, ...args: unknown[]): void; } @@ -45,6 +46,7 @@ class CustomLoggerImpl implements CustomLogger { silent (...args: unknown[]) { } child (bindings: P.Bindings, options?: P.ChildLoggerOptions): CustomLoggerImpl { return new CustomLoggerImpl() } + setBindings (bindings: P.Bindings): void { } } const customLogger = new CustomLoggerImpl() diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 007be236eea..ce9636bea8c 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -142,7 +142,8 @@ const customLogger: CustomLoggerInterface = { trace: () => { }, debug: () => { }, foo: () => { }, // custom severity logger method - child: () => customLogger as pino.Logger + child: () => customLogger as pino.Logger, + setBindings: () => { } } const serverWithCustomLogger = fastify({ logger: customLogger }) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 03efbf8f368..a5da2e567ac 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -5,7 +5,7 @@ import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyD import { FastifyRequest } from './request' import { FastifyReply } from './reply' import { FastifyError } from '@fastify/error' -import { FastifyBaseLogger } from './logger' +import { DefaultFastifyLogger, FastifyBaseLogger } from './logger' import { FastifyTypeProvider, FastifyTypeProviderDefault @@ -34,7 +34,7 @@ export interface onRequestHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -52,7 +52,7 @@ export interface onRequestAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -73,7 +73,7 @@ export interface preParsingHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -92,7 +92,7 @@ export interface preParsingAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -113,7 +113,7 @@ export interface preValidationHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -131,7 +131,7 @@ export interface preValidationAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -151,7 +151,7 @@ export interface preHandlerHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -169,7 +169,7 @@ export interface preHandlerAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -198,7 +198,7 @@ export interface preSerializationHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -218,7 +218,7 @@ export interface preSerializationAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -241,7 +241,7 @@ export interface onSendHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -261,7 +261,7 @@ export interface onSendAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -283,7 +283,7 @@ export interface onResponseHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -301,7 +301,7 @@ export interface onResponseAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -322,7 +322,7 @@ export interface onTimeoutHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -340,7 +340,7 @@ export interface onTimeoutAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -364,7 +364,7 @@ export interface onErrorHookHandler< TError extends Error = FastifyError, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -384,7 +384,7 @@ export interface onErrorAsyncHookHandler< TError extends Error = FastifyError, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -407,7 +407,7 @@ export interface onRouteHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { ( this: FastifyInstance, @@ -424,7 +424,7 @@ export interface onRegisterHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyBaseLogger, + Logger extends FastifyBaseLogger = DefaultFastifyLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Options extends FastifyPluginOptions = FastifyPluginOptions > { @@ -442,7 +442,7 @@ export interface onReadyHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyBaseLogger, + Logger extends FastifyBaseLogger = DefaultFastifyLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -455,7 +455,7 @@ export interface onReadyAsyncHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyBaseLogger, + Logger extends FastifyBaseLogger = DefaultFastifyLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -469,7 +469,7 @@ export interface onCloseHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyBaseLogger, + Logger extends FastifyBaseLogger = DefaultFastifyLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -482,7 +482,7 @@ export interface onCloseAsyncHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyBaseLogger, + Logger extends FastifyBaseLogger = DefaultFastifyLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( diff --git a/types/instance.d.ts b/types/instance.d.ts index 0d562717cf6..8b2ad65fb4c 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -4,7 +4,7 @@ import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' -import { FastifyBaseLogger } from './logger' +import { DefaultFastifyLogger, FastifyBaseLogger } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' import { FastifyRequest } from './request' @@ -84,7 +84,7 @@ export interface FastifyInstance< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyBaseLogger, + Logger extends FastifyBaseLogger = DefaultFastifyLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { server: RawServer; @@ -204,7 +204,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onRequest', hook: onRequestHookHandler @@ -214,7 +214,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onRequest', hook: onRequestAsyncHookHandler @@ -228,7 +228,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'preParsing', hook: preParsingHookHandler @@ -238,7 +238,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'preParsing', hook: preParsingAsyncHookHandler @@ -251,7 +251,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'preValidation', hook: preValidationHookHandler @@ -261,7 +261,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'preValidation', hook: preValidationAsyncHookHandler @@ -274,7 +274,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'preHandler', hook: preHandlerHookHandler @@ -284,7 +284,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'preHandler', hook: preHandlerAsyncHookHandler @@ -299,7 +299,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'preSerialization', hook: preSerializationHookHandler @@ -310,7 +310,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'preSerialization', hook: preSerializationAsyncHookHandler @@ -325,7 +325,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onSend', hook: onSendHookHandler @@ -336,7 +336,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onSend', hook: onSendAsyncHookHandler @@ -350,7 +350,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onResponse', hook: onResponseHookHandler @@ -360,7 +360,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onResponse', hook: onResponseAsyncHookHandler @@ -374,7 +374,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onTimeout', hook: onTimeoutHookHandler @@ -384,7 +384,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onTimeout', hook: onTimeoutAsyncHookHandler @@ -400,7 +400,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onError', hook: onErrorHookHandler @@ -410,7 +410,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onError', hook: onErrorAsyncHookHandler @@ -425,7 +425,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger >( name: 'onRoute', hook: onRouteHookHandler diff --git a/types/logger.d.ts b/types/logger.d.ts index 5fccf5b9c19..d2e453d0ce3 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -19,15 +19,19 @@ export type Bindings = pino.Bindings export type ChildLoggerOptions = pino.ChildLoggerOptions -export type FastifyBaseLogger = pino.BaseLogger & { - child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger +export interface FastifyBaseLogger = FastifyBaseLogger> extends pino.BaseLogger { + child(bindings: Bindings, options?: ChildLoggerOptions): T +} + +export interface DefaultFastifyLogger extends FastifyBaseLogger, Pick { + } // TODO delete FastifyBaseLogger in the next major release. It seems that it is enough to have only FastifyBaseLogger. /** * @deprecated Use FastifyBaseLogger instead */ -export type FastifyLoggerInstance = FastifyBaseLogger +export type FastifyLoggerInstance = DefaultFastifyLogger export interface FastifyLoggerStreamDestination { write(msg: string): void; diff --git a/types/plugin.d.ts b/types/plugin.d.ts index 77bce4e1182..70b2234afb4 100644 --- a/types/plugin.d.ts +++ b/types/plugin.d.ts @@ -1,7 +1,7 @@ import { FastifyInstance } from './instance' import { RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault } from './utils' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' -import { FastifyBaseLogger } from './logger' +import { DefaultFastifyLogger } from './logger' export type FastifyPluginOptions = Record @@ -11,7 +11,7 @@ export type FastifyPluginOptions = Record * 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. */ export type FastifyPluginCallback, Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> = ( - instance: FastifyInstance, RawReplyDefaultExpression, FastifyBaseLogger, TypeProvider>, + instance: FastifyInstance, RawReplyDefaultExpression, DefaultFastifyLogger, TypeProvider>, opts: Options, done: (err?: Error) => void ) => void @@ -26,7 +26,7 @@ export type FastifyPluginAsync< Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > = ( - instance: FastifyInstance, RawReplyDefaultExpression, FastifyBaseLogger, TypeProvider>, + instance: FastifyInstance, RawReplyDefaultExpression, DefaultFastifyLogger, TypeProvider>, opts: Options ) => Promise; diff --git a/types/reply.d.ts b/types/reply.d.ts index 11ea23d0b9a..0e72527ac45 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -1,7 +1,7 @@ import { RawReplyDefaultExpression, RawServerBase, RawServerDefault, ContextConfigDefault, RawRequestDefaultExpression, ReplyDefault } from './utils' import { FastifyReplyType, ResolveFastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' import { FastifyContext } from './context' -import { FastifyBaseLogger } from './logger' +import { DefaultFastifyLogger, FastifyBaseLogger } from './logger' import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' import { FastifyInstance } from './instance' @@ -24,11 +24,12 @@ export interface FastifyReply< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - ReplyType extends FastifyReplyType = ResolveFastifyReplyType + ReplyType extends FastifyReplyType = ResolveFastifyReplyType, + Logger extends FastifyBaseLogger = DefaultFastifyLogger, > { raw: RawReply; context: FastifyContext; - log: FastifyBaseLogger; + log: Logger; request: FastifyRequest; server: FastifyInstance; code(statusCode: number): FastifyReply; diff --git a/types/request.d.ts b/types/request.d.ts index 4931e72e4b1..a7471786fc2 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,5 +1,5 @@ import { ErrorObject } from '@fastify/ajv-compiler' -import { FastifyBaseLogger } from './logger' +import { DefaultFastifyLogger, FastifyBaseLogger } from './logger' import { ContextConfigDefault, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './utils' import { RouteGenericInterface } from './route' import { FastifyInstance } from './instance' @@ -30,7 +30,7 @@ export interface FastifyRequest // ^ Temporary Note: RequestType has been re-ordered to be the last argument in // generic list. This generic argument is now considered optional as it can be diff --git a/types/route.d.ts b/types/route.d.ts index f8d2675cb43..ec5ea849054 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -11,7 +11,7 @@ import { FastifyTypeProviderDefault, ResolveFastifyReplyReturnType } from './type-provider' -import { FastifyBaseLogger, LogLevel } from './logger' +import { DefaultFastifyLogger, FastifyBaseLogger, LogLevel } from './logger' export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface {} @@ -26,7 +26,7 @@ export interface RouteShorthandOptions< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > { schema?: SchemaCompiler, // originally FastifySchema attachValidation?: boolean; @@ -67,7 +67,7 @@ export type RouteHandlerMethod< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > = ( this: FastifyInstance, request: FastifyRequest, @@ -86,7 +86,7 @@ export interface RouteShorthandOptionsWithHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > extends RouteShorthandOptions { handler: RouteHandlerMethod; } @@ -100,16 +100,16 @@ export interface RouteShorthandMethod< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { - ( + ( path: string, opts: RouteShorthandOptions, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, opts: RouteShorthandOptionsWithHandler ): FastifyInstance; @@ -126,7 +126,7 @@ export interface RouteOptions< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > extends RouteShorthandOptions { method: HTTPMethods | HTTPMethods[]; url: string; @@ -141,7 +141,7 @@ export type RouteHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = DefaultFastifyLogger > = ( this: FastifyInstance, request: FastifyRequest, From 7a566cb1ea37a5c270e7678545c5ee43800e83cf Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 17 Oct 2022 11:35:45 +0100 Subject: [PATCH 0124/1295] docs(reference): grammar and structure fixes (#4348) --- docs/Reference/Errors.md | 2 +- docs/Reference/Hooks.md | 6 +++--- docs/Reference/Routes.md | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 54b224795cd..695c3ed1718 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -332,7 +332,7 @@ The router received an invalid url. ### FST_ERR_ASYNC_CONSTRAINT -The router received error when using asynchronous constraints. +The router received an error when using asynchronous constraints. #### FST_ERR_DEFAULT_ROUTE_INVALID_TYPE diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index d46f6d0245c..2a8c6f61005 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -449,9 +449,9 @@ fastify.addHook('onRoute', (routeOptions) => { }) ``` -If you want to add more routes within an onRoute hook, you have to tag these -routes properly. If you don't, the hook will run into an infinite loop. The -recommended approach is shown below. +To add more routes within an onRoute hook, the routes must +be tagged correctly. The hook will run into an infinite loop if +not tagged. The recommended approach is shown below. ```js const kRouteAlreadyProcessed = Symbol('route-already-processed') diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 4dec6535d31..0e47e6e49ca 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -734,9 +734,9 @@ fastify.route({ #### Asynchronous Custom Constraints -You can provide your custom constraints and the `constraint` criteria can be -fetched from other source, for example `database`. Usage of asynchronous custom -constraint should place at the last resort since it impacts the router +Custom constraints can be provided and the `constraint` criteria can be +fetched from another source such as `database`. The use of asynchronous +custom constraints should be a last resort as it impacts router performance. ```js From e11a9ad706131098ea5c381b19485bec899e4dbd Mon Sep 17 00:00:00 2001 From: Leandro Andrade Date: Mon, 17 Oct 2022 10:15:38 -0300 Subject: [PATCH 0125/1295] docs: example how decorators dependencies works (#4343) * docs: example how decorators dependencies works * docs: remove unnecessary example definitions * docs: change fp to fastifyPlugin to improve readability --- docs/Reference/Decorators.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 9a051e6305b..b2bf0a9985d 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -114,10 +114,39 @@ fastify.get('/', async function (request, reply) { The `dependencies` parameter is an optional list of decorators that the decorator being defined relies upon. This list is simply a list of string names of other decorators. In the following example, the "utility" decorator depends -upon "greet" and "log" decorators: +upon "greet" and "hi" decorators: ```js -fastify.decorate('utility', fn, ['greet', 'log']) +async function greetDecorator (fastify, opts) { + fastify.decorate('greet', () => { + return 'greet message' + }) +} + +async function hiDecorator (fastify, opts) { + fastify.decorate('hi', () => { + return 'hi message' + }) +} + +async function utilityDecorator (fastify, opts) { + fastify.decorate('utility', () => { + return `${fastify.greet()} | ${fastify.hi()}` + }) +} + +fastify.register(fastifyPlugin(greetDecorator, { name: 'greet' })) +fastify.register(fastifyPlugin(hiDecorator, { name: 'hi' })) +fastify.register(fastifyPlugin(utilityDecorator, { dependencies: ['greet', 'hi'] })) + +fastify.get('/', function (req, reply) { + // Response: {"hello":"greet message | hi message"} + reply.send({ hello: fastify.utility() }) +}) + +fastify.listen({ port: 3000 }, (err, address) => { + if (err) throw err +}) ``` Note: using an arrow function will break the binding of `this` to the From 602dc087eeb4df808283aba9efa04e1c35bd4259 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Oct 2022 15:59:30 +0100 Subject: [PATCH 0126/1295] Undefined is a valid value for if set as a single hook (#4351) Signed-off-by: Matteo Collina Signed-off-by: Matteo Collina --- lib/route.js | 2 +- test/hooks.test.js | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/route.js b/lib/route.js index c5b99eb660d..286378604b6 100644 --- a/lib/route.js +++ b/lib/route.js @@ -252,7 +252,7 @@ function buildRouting (options) { throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof func) } } - } else if (typeof opts[hook] !== 'function') { + } else if (opts[hook] !== undefined && typeof opts[hook] !== 'function') { throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof opts[hook]) } } diff --git a/test/hooks.test.js b/test/hooks.test.js index 20578f708c7..75adf3be518 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3322,12 +3322,22 @@ test('registering invalid hooks should throw an error', async t => { fastify.route({ method: 'GET', path: '/invalidHook', - onRequest: undefined, + onRequest: null, async handler () { return 'hello world' } }) - }, new Error('onRequest hook should be a function, instead got undefined')) + }, new Error('onRequest hook should be a function, instead got object')) + + // undefined is ok + fastify.route({ + method: 'GET', + path: '/validhook', + onRequest: undefined, + async handler () { + return 'hello world' + } + }) t.throws(() => { fastify.addHook('onRoute', (routeOptions) => { From 79107281bf096ca6d56262b5920d9f2eda7a33a2 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Oct 2022 17:07:04 +0200 Subject: [PATCH 0127/1295] Bumped v4.9.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 4e691582de5..1e5b5e148a8 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.9.0' +const VERSION = '4.9.1' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 8dc9e322821..8335138e87c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.9.0", + "version": "4.9.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From fe3cf0e6985015a6498de34290aa905fbd245597 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Mon, 17 Oct 2022 22:17:43 +0300 Subject: [PATCH 0128/1295] Add missing context types (#4352) --- test/types/request.test-d.ts | 2 ++ types/context.d.ts | 3 +++ types/request.d.ts | 4 +++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index ce9636bea8c..e9c435a01b1 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -75,6 +75,8 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.params) expectType>(request.context) expectType(request.context.config) + expectType(request.routeConfig) + expectType(request.routeSchema) expectType(request.headers) request.headers = {} diff --git a/types/context.d.ts b/types/context.d.ts index e64a024228c..ad23ed752f2 100644 --- a/types/context.d.ts +++ b/types/context.d.ts @@ -8,5 +8,8 @@ export interface FastifyContextConfig { * Route context object. Properties defined here will be available in the route's handler */ export interface FastifyContext { + /** + * @deprecated Use Request#routeConfig or Request#routeSchema instead + */ config: FastifyContextConfig & ContextConfig; } diff --git a/types/request.d.ts b/types/request.d.ts index a7471786fc2..c0c68608a63 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -5,7 +5,7 @@ import { RouteGenericInterface } from './route' import { FastifyInstance } from './instance' import { FastifyTypeProvider, FastifyTypeProviderDefault, FastifyRequestType, ResolveFastifyRequestType } from './type-provider' import { FastifySchema } from './schema' -import { FastifyContext } from './context' +import { FastifyContext, FastifyContextConfig } from './context' type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' export interface RequestGenericInterface { @@ -48,6 +48,8 @@ export interface FastifyRequest; + routeConfig: FastifyContextConfig & ContextConfig; + routeSchema: FastifySchema /** in order for this to be used the user should ensure they have set the attachValidation option. */ validationError?: Error & { validation: any; validationContext: string }; From d0d36964373b1c54d30a4e8de661da6ad186cc76 Mon Sep 17 00:00:00 2001 From: KaKa Date: Tue, 18 Oct 2022 15:12:41 +0800 Subject: [PATCH 0129/1295] Revert "fix(logger): lost setBindings type in FastifyBaseLogger (#4346)" (#4355) --- test/logger.test.js | 14 ---------- test/types/logger.test-d.ts | 4 +-- test/types/request.test-d.ts | 3 +-- types/hooks.d.ts | 50 ++++++++++++++++++------------------ types/instance.d.ts | 42 +++++++++++++++--------------- types/logger.d.ts | 10 +++----- types/plugin.d.ts | 6 ++--- types/reply.d.ts | 7 +++-- types/request.d.ts | 4 +-- types/route.d.ts | 18 ++++++------- 10 files changed, 68 insertions(+), 90 deletions(-) diff --git a/test/logger.test.js b/test/logger.test.js index 573c4a90220..48ba39a2035 100644 --- a/test/logger.test.js +++ b/test/logger.test.js @@ -1687,17 +1687,3 @@ test('should not throw error when serializing custom req', t => { t.same(lines[0].req, {}) }) - -test('set bindings', t => { - t.plan(1) - - const stream = split(JSON.parse) - stream.once('data', info => { - t.same(info.hello, 'world') - }) - - const fastify = Fastify({ logger: { level: 'info', stream } }) - - fastify.log.setBindings({ hello: 'world' }) - fastify.log.info('hello world') -}) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index d78cd2e54c6..d8e6b9cbfa8 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -10,7 +10,6 @@ import fastify, { import { Server, IncomingMessage, ServerResponse } from 'http' import * as fs from 'fs' import P from 'pino' -import { DefaultFastifyLogger } from '../../types/logger' expectType(fastify().log) @@ -25,7 +24,7 @@ class Foo {} expectType(fastify().log[logLevel as LogLevel](new Foo())) }) -interface CustomLogger extends DefaultFastifyLogger { +interface CustomLogger extends FastifyBaseLogger { customMethod(msg: string, ...args: unknown[]): void; } @@ -46,7 +45,6 @@ class CustomLoggerImpl implements CustomLogger { silent (...args: unknown[]) { } child (bindings: P.Bindings, options?: P.ChildLoggerOptions): CustomLoggerImpl { return new CustomLoggerImpl() } - setBindings (bindings: P.Bindings): void { } } const customLogger = new CustomLoggerImpl() diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index e9c435a01b1..6b041dab309 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -144,8 +144,7 @@ const customLogger: CustomLoggerInterface = { trace: () => { }, debug: () => { }, foo: () => { }, // custom severity logger method - child: () => customLogger as pino.Logger, - setBindings: () => { } + child: () => customLogger as pino.Logger } const serverWithCustomLogger = fastify({ logger: customLogger }) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index a5da2e567ac..03efbf8f368 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -5,7 +5,7 @@ import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyD import { FastifyRequest } from './request' import { FastifyReply } from './reply' import { FastifyError } from '@fastify/error' -import { DefaultFastifyLogger, FastifyBaseLogger } from './logger' +import { FastifyBaseLogger } from './logger' import { FastifyTypeProvider, FastifyTypeProviderDefault @@ -34,7 +34,7 @@ export interface onRequestHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -52,7 +52,7 @@ export interface onRequestAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -73,7 +73,7 @@ export interface preParsingHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -92,7 +92,7 @@ export interface preParsingAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -113,7 +113,7 @@ export interface preValidationHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -131,7 +131,7 @@ export interface preValidationAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -151,7 +151,7 @@ export interface preHandlerHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -169,7 +169,7 @@ export interface preHandlerAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -198,7 +198,7 @@ export interface preSerializationHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -218,7 +218,7 @@ export interface preSerializationAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -241,7 +241,7 @@ export interface onSendHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -261,7 +261,7 @@ export interface onSendAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -283,7 +283,7 @@ export interface onResponseHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -301,7 +301,7 @@ export interface onResponseAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -322,7 +322,7 @@ export interface onTimeoutHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -340,7 +340,7 @@ export interface onTimeoutAsyncHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -364,7 +364,7 @@ export interface onErrorHookHandler< TError extends Error = FastifyError, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -384,7 +384,7 @@ export interface onErrorAsyncHookHandler< TError extends Error = FastifyError, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -407,7 +407,7 @@ export interface onRouteHookHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { ( this: FastifyInstance, @@ -424,7 +424,7 @@ export interface onRegisterHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = DefaultFastifyLogger, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Options extends FastifyPluginOptions = FastifyPluginOptions > { @@ -442,7 +442,7 @@ export interface onReadyHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = DefaultFastifyLogger, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -455,7 +455,7 @@ export interface onReadyAsyncHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = DefaultFastifyLogger, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -469,7 +469,7 @@ export interface onCloseHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = DefaultFastifyLogger, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { ( @@ -482,7 +482,7 @@ export interface onCloseAsyncHookHandler< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = DefaultFastifyLogger, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( diff --git a/types/instance.d.ts b/types/instance.d.ts index 8b2ad65fb4c..0d562717cf6 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -4,7 +4,7 @@ import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' -import { DefaultFastifyLogger, FastifyBaseLogger } from './logger' +import { FastifyBaseLogger } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' import { FastifyRequest } from './request' @@ -84,7 +84,7 @@ export interface FastifyInstance< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = DefaultFastifyLogger, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { server: RawServer; @@ -204,7 +204,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onRequest', hook: onRequestHookHandler @@ -214,7 +214,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onRequest', hook: onRequestAsyncHookHandler @@ -228,7 +228,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preParsing', hook: preParsingHookHandler @@ -238,7 +238,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preParsing', hook: preParsingAsyncHookHandler @@ -251,7 +251,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preValidation', hook: preValidationHookHandler @@ -261,7 +261,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preValidation', hook: preValidationAsyncHookHandler @@ -274,7 +274,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preHandler', hook: preHandlerHookHandler @@ -284,7 +284,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preHandler', hook: preHandlerAsyncHookHandler @@ -299,7 +299,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preSerialization', hook: preSerializationHookHandler @@ -310,7 +310,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'preSerialization', hook: preSerializationAsyncHookHandler @@ -325,7 +325,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onSend', hook: onSendHookHandler @@ -336,7 +336,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onSend', hook: onSendAsyncHookHandler @@ -350,7 +350,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onResponse', hook: onResponseHookHandler @@ -360,7 +360,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onResponse', hook: onResponseAsyncHookHandler @@ -374,7 +374,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onTimeout', hook: onTimeoutHookHandler @@ -384,7 +384,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onTimeout', hook: onTimeoutAsyncHookHandler @@ -400,7 +400,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onError', hook: onErrorHookHandler @@ -410,7 +410,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onError', hook: onErrorAsyncHookHandler @@ -425,7 +425,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger >( name: 'onRoute', hook: onRouteHookHandler diff --git a/types/logger.d.ts b/types/logger.d.ts index d2e453d0ce3..5fccf5b9c19 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -19,19 +19,15 @@ export type Bindings = pino.Bindings export type ChildLoggerOptions = pino.ChildLoggerOptions -export interface FastifyBaseLogger = FastifyBaseLogger> extends pino.BaseLogger { - child(bindings: Bindings, options?: ChildLoggerOptions): T -} - -export interface DefaultFastifyLogger extends FastifyBaseLogger, Pick { - +export type FastifyBaseLogger = pino.BaseLogger & { + child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger } // TODO delete FastifyBaseLogger in the next major release. It seems that it is enough to have only FastifyBaseLogger. /** * @deprecated Use FastifyBaseLogger instead */ -export type FastifyLoggerInstance = DefaultFastifyLogger +export type FastifyLoggerInstance = FastifyBaseLogger export interface FastifyLoggerStreamDestination { write(msg: string): void; diff --git a/types/plugin.d.ts b/types/plugin.d.ts index 70b2234afb4..77bce4e1182 100644 --- a/types/plugin.d.ts +++ b/types/plugin.d.ts @@ -1,7 +1,7 @@ import { FastifyInstance } from './instance' import { RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault } from './utils' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' -import { DefaultFastifyLogger } from './logger' +import { FastifyBaseLogger } from './logger' export type FastifyPluginOptions = Record @@ -11,7 +11,7 @@ export type FastifyPluginOptions = Record * 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. */ export type FastifyPluginCallback, Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> = ( - instance: FastifyInstance, RawReplyDefaultExpression, DefaultFastifyLogger, TypeProvider>, + instance: FastifyInstance, RawReplyDefaultExpression, FastifyBaseLogger, TypeProvider>, opts: Options, done: (err?: Error) => void ) => void @@ -26,7 +26,7 @@ export type FastifyPluginAsync< Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > = ( - instance: FastifyInstance, RawReplyDefaultExpression, DefaultFastifyLogger, TypeProvider>, + instance: FastifyInstance, RawReplyDefaultExpression, FastifyBaseLogger, TypeProvider>, opts: Options ) => Promise; diff --git a/types/reply.d.ts b/types/reply.d.ts index 0e72527ac45..11ea23d0b9a 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -1,7 +1,7 @@ import { RawReplyDefaultExpression, RawServerBase, RawServerDefault, ContextConfigDefault, RawRequestDefaultExpression, ReplyDefault } from './utils' import { FastifyReplyType, ResolveFastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' import { FastifyContext } from './context' -import { DefaultFastifyLogger, FastifyBaseLogger } from './logger' +import { FastifyBaseLogger } from './logger' import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' import { FastifyInstance } from './instance' @@ -24,12 +24,11 @@ export interface FastifyReply< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - ReplyType extends FastifyReplyType = ResolveFastifyReplyType, - Logger extends FastifyBaseLogger = DefaultFastifyLogger, + ReplyType extends FastifyReplyType = ResolveFastifyReplyType > { raw: RawReply; context: FastifyContext; - log: Logger; + log: FastifyBaseLogger; request: FastifyRequest; server: FastifyInstance; code(statusCode: number): FastifyReply; diff --git a/types/request.d.ts b/types/request.d.ts index c0c68608a63..2944fca6d47 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,5 +1,5 @@ import { ErrorObject } from '@fastify/ajv-compiler' -import { DefaultFastifyLogger, FastifyBaseLogger } from './logger' +import { FastifyBaseLogger } from './logger' import { ContextConfigDefault, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './utils' import { RouteGenericInterface } from './route' import { FastifyInstance } from './instance' @@ -30,7 +30,7 @@ export interface FastifyRequest // ^ Temporary Note: RequestType has been re-ordered to be the last argument in // generic list. This generic argument is now considered optional as it can be diff --git a/types/route.d.ts b/types/route.d.ts index ec5ea849054..f8d2675cb43 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -11,7 +11,7 @@ import { FastifyTypeProviderDefault, ResolveFastifyReplyReturnType } from './type-provider' -import { DefaultFastifyLogger, FastifyBaseLogger, LogLevel } from './logger' +import { FastifyBaseLogger, LogLevel } from './logger' export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface {} @@ -26,7 +26,7 @@ export interface RouteShorthandOptions< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > { schema?: SchemaCompiler, // originally FastifySchema attachValidation?: boolean; @@ -67,7 +67,7 @@ export type RouteHandlerMethod< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > = ( this: FastifyInstance, request: FastifyRequest, @@ -86,7 +86,7 @@ export interface RouteShorthandOptionsWithHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > extends RouteShorthandOptions { handler: RouteHandlerMethod; } @@ -100,16 +100,16 @@ export interface RouteShorthandMethod< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { - ( + ( path: string, opts: RouteShorthandOptions, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, opts: RouteShorthandOptionsWithHandler ): FastifyInstance; @@ -126,7 +126,7 @@ export interface RouteOptions< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > extends RouteShorthandOptions { method: HTTPMethods | HTTPMethods[]; url: string; @@ -141,7 +141,7 @@ export type RouteHandler< ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = DefaultFastifyLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger > = ( this: FastifyInstance, request: FastifyRequest, From 7c8ff1791495cc55236e06e62dc1780789366976 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 18 Oct 2022 09:44:46 +0200 Subject: [PATCH 0130/1295] Bumped v4.9.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 1e5b5e148a8..ac8671db327 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.9.1' +const VERSION = '4.9.2' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 8335138e87c..e14318b6ee0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.9.1", + "version": "4.9.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 68914519a3fd65ce65ba8db8962940eecf7f0fc8 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 18 Oct 2022 10:03:50 +0100 Subject: [PATCH 0131/1295] docs(reference/reply): spelling fixes (#4358) --- docs/Reference/Reply.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 8f489ef6a80..c6697909276 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -488,11 +488,11 @@ console.log(newSerialize === serialize) // false ### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType]) -This function will serialize the input data based on the provided schema, -or http status code. If both provided, the `httpStatus` will take presedence. +This function will serialize the input data based on the provided schema +or HTTP status code. If both are provided the `httpStatus` will take precedence. -If there is not a serialization function for a given `schema`, a new serialization -function will be compiled forwarding the `httpStatus`, and the `contentType` if provided. +If there is not a serialization function for a given `schema` a new serialization +function will be compiled, forwarding the `httpStatus` and `contentType` if provided. ```js reply From f03411361acce282351882147627bdebffdb0631 Mon Sep 17 00:00:00 2001 From: rain714 Date: Thu, 20 Oct 2022 18:44:35 +0900 Subject: [PATCH 0132/1295] Update reply type resolver (#4360) --- test/types/type-provider.test-d.ts | 311 +++++++++++++++++++++++++++++ types/type-provider.d.ts | 8 +- 2 files changed, 316 insertions(+), 3 deletions(-) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index e492c31cce1..d98ea6c0eaf 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -231,6 +231,40 @@ expectAssignable(server.withTypeProvider().get( } )) +// ------------------------------------------------------------------- +// TypeBox Reply Type (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: Type.String() + }, + 'application/json': { + schema: Type.Object({ + msg: Type.String() + }) + } + } + }, + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + res.send('hello') + res.send({ msg: 'hello' }) + res.send({ error: 'error' }) + } +)) + // ------------------------------------------------------------------- // TypeBox Reply Type: Non Assignable // ------------------------------------------------------------------- @@ -253,6 +287,38 @@ expectError(server.withTypeProvider().get( } )) +// ------------------------------------------------------------------- +// TypeBox Reply Type: Non Assignable (Different Content-types) +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: Type.String() + }, + 'application/json': { + schema: Type.Object({ + msg: Type.String() + }) + } + } + }, + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + res.send(false) + } +)) + // ------------------------------------------------------------------- // TypeBox Reply Return Type // ------------------------------------------------------------------- @@ -280,6 +346,43 @@ expectAssignable(server.withTypeProvider().get( } )) +// ------------------------------------------------------------------- +// TypeBox Reply Return Type (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: Type.String() + }, + 'application/json': { + schema: Type.Object({ + msg: Type.String() + }) + } + } + }, + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + const option = 1 as 1 | 2 | 3 + switch (option) { + case 1: return 'hello' + case 2: return { msg: 'hello' } + case 3: return { error: 'error' } + } + } +)) + // ------------------------------------------------------------------- // TypeBox Reply Return Type: Non Assignable // ------------------------------------------------------------------- @@ -302,6 +405,38 @@ expectError(server.withTypeProvider().get( } )) +// ------------------------------------------------------------------- +// TypeBox Reply Return Type: Non Assignable (Different Content-types) +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: Type.String() + }, + 'application/json': { + schema: Type.Object({ + msg: Type.String() + }) + } + } + }, + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + return false + } +)) + // ------------------------------------------------------------------- // JsonSchemaToTs Reply Type // ------------------------------------------------------------------- @@ -324,6 +459,36 @@ expectAssignable(server.withTypeProvider().get( } )) +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + (_, res) => { + res.send('hello') + res.send({ msg: 'hello' }) + res.send({ error: 'error' }) + } +)) + // ------------------------------------------------------------------- // JsonSchemaToTs Reply Type: Non Assignable // ------------------------------------------------------------------- @@ -344,6 +509,34 @@ expectError(server.withTypeProvider().get( } )) +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type: Non Assignable (Different Content-types) +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + res.send(false) + } +)) + // ------------------------------------------------------------------- // JsonSchemaToTs Reply Type Return // ------------------------------------------------------------------- @@ -368,6 +561,40 @@ expectAssignable(server.withTypeProvider().get( } } )) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type Return (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + const option = 1 as 1 | 2 | 3 + switch (option) { + case 1: return 'hello' + case 2: return { msg: 'hello' } + case 3: return { error: 'error' } + } + } +)) + // ------------------------------------------------------------------- // JsonSchemaToTs Reply Type Return: Non Assignable // ------------------------------------------------------------------- @@ -399,6 +626,34 @@ expectError(server.withTypeProvider().get('/', { return { foo: 555 } })) +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type Return: Non Assignable (Different Content-types) +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + return false + } +)) + // ------------------------------------------------------------------- // Reply Type Override // ------------------------------------------------------------------- @@ -419,6 +674,34 @@ expectAssignable(server.withTypeProvider().get<{Reply: b } )) +// ------------------------------------------------------------------- +// Reply Type Override (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get<{Reply: boolean}>( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + res.send(true) + } +)) + // ------------------------------------------------------------------- // Reply Type Return Override // ------------------------------------------------------------------- @@ -439,6 +722,34 @@ expectAssignable(server.withTypeProvider().get<{Reply: b } )) +// ------------------------------------------------------------------- +// Reply Type Return Override (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get<{Reply: boolean}>( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + return true + } +)) + // ------------------------------------------------------------------- // FastifyPlugin: Auxiliary // ------------------------------------------------------------------- diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 9f1c8da6dc5..0af41899d30 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -60,10 +60,12 @@ export interface ResolveFastifyRequestType = { - [K in keyof SchemaCompiler['response']]: CallTypeProvider -} extends infer Result ? Result[keyof Result] : unknown + [K1 in keyof SchemaCompiler['response']]: SchemaCompiler['response'][K1] extends { content: { [keyof: string]: { schema: unknown } } } ? ({ + [K2 in keyof SchemaCompiler['response'][K1]['content']]: CallTypeProvider + } extends infer Result ? Result[keyof Result] : unknown) : CallTypeProvider +} extends infer Result ? Result[keyof Result] : unknown; // The target reply type. This type is inferenced on fastify 'replies' via generic argument assignment export type FastifyReplyType = Reply From 3e47a77392939a7812f2cd0adb37419434e86cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Thu, 20 Oct 2022 18:17:50 +0200 Subject: [PATCH 0133/1295] chore: remove leading empty lines (#4364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This rule is on its way into the latest Standard ☺️ ref: https://github.com/standard/eslint-config-standard/pull/253 --- test/input-validation.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/input-validation.js b/test/input-validation.js index 62b837b1248..7eab5c927e1 100644 --- a/test/input-validation.js +++ b/test/input-validation.js @@ -1,4 +1,3 @@ - 'use strict' const sget = require('simple-get').concat From 97b7a4ade7d6e3c2b9c56172269b906f16c99f48 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 21 Oct 2022 10:25:38 +0100 Subject: [PATCH 0134/1295] fix types after pino 8.7.0 change (#4365) --- test/types/logger.test-d.ts | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index d8e6b9cbfa8..0607c632223 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -123,35 +123,6 @@ const serverAutoInferredFileOption = fastify({ expectType(serverAutoInferredFileOption.log) -const serverAutoInferredPinoPrettyBooleanOption = fastify({ - logger: { - prettyPrint: true - } -}) - -expectType(serverAutoInferredPinoPrettyBooleanOption.log) - -const serverAutoInferredPinoPrettyObjectOption = fastify({ - logger: { - prettyPrint: { - translateTime: true, - levelFirst: false, - messageKey: 'msg', - timestampKey: 'time', - messageFormat: false, - colorize: true, - crlf: false, - errorLikeObjectKeys: ['err', 'error'], - errorProps: '', - search: 'foo == `bar`', - ignore: 'pid,hostname', - suppressFlushSyncWarning: true - } - } -}) - -expectType(serverAutoInferredPinoPrettyObjectOption.log) - const serverAutoInferredSerializerObjectOption = fastify({ logger: { serializers: { @@ -200,9 +171,6 @@ const passPinoOption = fastify({ redact: ['custom'], messageKey: 'msg', nestedKey: 'nested', - prettyPrint: { - - }, enabled: true } }) From b94441ff71e10252b6b3806c8cd4730821dfe737 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 21 Oct 2022 11:30:25 +0100 Subject: [PATCH 0135/1295] Node.js V19 support (#4366) --- .github/workflows/ci.yml | 2 +- fastify.js | 3 +- lib/contentTypeParser.js | 4 ++ lib/route.js | 6 +- test/close-pipelining.test.js | 109 +++++++++++++++++++++------------- test/close.test.js | 43 +++++++++++++- 6 files changed, 121 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a6baf63bc2..24cc39ad708 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: strategy: matrix: - node-version: [14, 16, 18] + node-version: [14, 16, 18, 19] os: [macos-latest, ubuntu-latest, windows-latest] steps: diff --git a/fastify.js b/fastify.js index ac8671db327..c50ddf2c9ec 100644 --- a/fastify.js +++ b/fastify.js @@ -410,11 +410,12 @@ function fastify (options) { /* istanbul ignore next: Cannot test this without Node.js core support */ if (forceCloseConnections === 'idle') { + // Not needed in Node 19 instance.server.closeIdleConnections() /* istanbul ignore next: Cannot test this without Node.js core support */ } else if (serverHasCloseAllConnections && forceCloseConnections) { instance.server.closeAllConnections() - } else { + } else if (forceCloseConnections === true) { for (const conn of fastify[kKeepAliveConnections]) { // We must invoke the destroy method instead of merely unreffing // the sockets. If we only unref, then the callback passed to diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index a1eebeb3a02..8f46dd0b618 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -190,6 +190,9 @@ function rawBody (request, reply, options, parser, done) { : Number(request.headers['content-length']) if (contentLength > limit) { + // We must close the connection as the client is going + // to send this data anyway + reply.header('connection', 'close') reply.send(new FST_ERR_CTP_BODY_TOO_LARGE()) return } @@ -243,6 +246,7 @@ function rawBody (request, reply, options, parser, done) { } if (!Number.isNaN(contentLength) && (payload.receivedEncodedLength || receivedLength) !== contentLength) { + reply.header('connection', 'close') reply.send(new FST_ERR_CTP_INVALID_CONTENT_LENGTH()) return } diff --git a/lib/route.js b/lib/route.js index 286378604b6..fc4526de0fd 100644 --- a/lib/route.js +++ b/lib/route.js @@ -398,11 +398,15 @@ function buildRouting (options) { if (closing === true) { /* istanbul ignore next mac, windows */ if (req.httpVersionMajor !== 2) { - res.once('finish', () => req.destroy()) res.setHeader('Connection', 'close') } + // TODO remove return503OnClosing after Node v18 goes EOL + /* istanbul ignore else */ if (return503OnClosing) { + // On Node v19 we cannot test this behavior as it won't be necessary + // anymore. It will close all the idle connections before they reach this + // stage. const headers = { 'Content-Type': 'application/json', 'Content-Length': '80' diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index 1733674a6b1..3f4b273b83e 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -4,42 +4,72 @@ const t = require('tap') const test = t.test const Fastify = require('..') const { Client } = require('undici') +const semver = require('semver') -test('Should return 503 while closing - pipelining', t => { +test('Should return 503 while closing - pipelining', async t => { const fastify = Fastify({ return503OnClosing: true, forceCloseConnections: false }) + fastify.get('/', async (req, reply) => { + fastify.close() + return { hello: 'world' } + }) + + await fastify.listen({ port: 0 }) + + const instance = new Client('http://localhost:' + fastify.server.address().port, { + pipelining: 2 + }) + + const codes = [200, 200, 503] + const responses = await Promise.all([ + instance.request({ path: '/', method: 'GET' }), + instance.request({ path: '/', method: 'GET' }), + instance.request({ path: '/', method: 'GET' }) + ]) + const actual = responses.map(r => r.statusCode) + + t.same(actual, codes) + + await instance.close() +}) + +const isV19plus = semver.satisfies(process.version, '>= v19.0.0') +test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, async t => { + const fastify = Fastify({ + return503OnClosing: false, + forceCloseConnections: false + }) + fastify.get('/', (req, reply) => { fastify.close() reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, async err => { - t.error(err) - - const instance = new Client('http://localhost:' + fastify.server.address().port, { - pipelining: 1 - }) - - const codes = [200, 503] - for (const code of codes) { - instance.request( - { path: '/', method: 'GET' } - ).then(data => { - t.equal(data.statusCode, code) - }).catch((e) => { - t.fail(e) - }) - } - instance.close(() => { - t.end('Done') - }) + await fastify.listen({ port: 0 }) + + const instance = new Client('http://localhost:' + fastify.server.address().port, { + pipelining: 2 }) + + const codes = [200, 200, 200] + const responses = await Promise.all([ + instance.request({ path: '/', method: 'GET' }), + instance.request({ path: '/', method: 'GET' }), + instance.request({ path: '/', method: 'GET' }) + ]) + const actual = responses.map(r => r.statusCode) + + t.same(actual, codes) + + await instance.close() }) -test('Should not return 503 while closing - pipelining - return503OnClosing', t => { +test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, async t => { + // Since Node v19, we will always invoke server.closeIdleConnections() + // therefore our socket will be closed const fastify = Fastify({ return503OnClosing: false, forceCloseConnections: false @@ -50,25 +80,20 @@ test('Should not return 503 while closing - pipelining - return503OnClosing', t reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - - const instance = new Client('http://localhost:' + fastify.server.address().port, { - pipelining: 1 - }) - - const codes = [200, 200] - for (const code of codes) { - instance.request( - { path: '/', method: 'GET' } - ).then(data => { - t.equal(data.statusCode, code) - }).catch((e) => { - t.fail(e) - }) - } - instance.close(() => { - t.end('Done') - }) + await fastify.listen({ port: 0 }) + + const instance = new Client('http://localhost:' + fastify.server.address().port, { + pipelining: 2 }) + + const responses = await Promise.allSettled([ + instance.request({ path: '/', method: 'GET' }), + instance.request({ path: '/', method: 'GET' }), + instance.request({ path: '/', method: 'GET' }) + ]) + t.equal(responses[0].status, 'fulfilled') + t.equal(responses[1].status, 'rejected') + t.equal(responses[2].status, 'rejected') + + await instance.close() }) diff --git a/test/close.test.js b/test/close.test.js index 90dd53934b4..b0c78877ff8 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -6,6 +6,7 @@ const t = require('tap') const test = t.test const Fastify = require('..') const { Client } = require('undici') +const semver = require('semver') test('close callback', t => { t.plan(4) @@ -202,7 +203,8 @@ test('Should return error while closing (callback) - injection', t => { }) }) -t.test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false', t => { +const isV19plus = semver.satisfies(process.version, '>= v19.0.0') +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 => { const fastify = Fastify({ return503OnClosing: false, forceCloseConnections: false @@ -240,6 +242,45 @@ t.test('Current opened connection should continue to work after closing and retu }) }) +t.test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, t => { + t.plan(4) + const fastify = Fastify({ + return503OnClosing: false, + forceCloseConnections: false + }) + + fastify.get('/', (req, reply) => { + fastify.close() + reply.send({ hello: 'world' }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + const port = fastify.server.address().port + const client = net.createConnection({ port }, () => { + client.write('GET / HTTP/1.1\r\n\r\n') + + client.on('error', function () { + // Dependending on the Operating System + // the socket could error or not. + // However, it will always be closed. + }) + + client.on('close', function () { + t.pass('close') + }) + + client.once('data', data => { + t.match(data.toString(), /Connection:\s*keep-alive/i) + t.match(data.toString(), /200 OK/i) + + client.write('GET / HTTP/1.1\r\n\r\n') + }) + }) + }) +}) + t.test('Current opened connection should not accept new incoming connections', t => { t.plan(3) const fastify = Fastify({ forceCloseConnections: false }) From 3769cd070c3df0dd884e3b35c5c23fecad0a9796 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Sun, 23 Oct 2022 11:07:57 +0200 Subject: [PATCH 0136/1295] fix: no check on `null` or `undefined` values passed as fn (#4367) --- fastify.js | 10 +++++++--- lib/errors.js | 6 ++++++ test/hooks-async.test.js | 8 ++++++-- test/hooks.on-ready.test.js | 3 ++- test/hooks.test.js | 18 +++++++++++++++++- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/fastify.js b/fastify.js index c50ddf2c9ec..eb9ce97cda3 100644 --- a/fastify.js +++ b/fastify.js @@ -566,17 +566,21 @@ function fastify (options) { function addHook (name, fn) { throwIfAlreadyStarted('Cannot call "addHook" when fastify instance is already started!') + if (fn == null) { + throw new errorCodes.FST_ERR_HOOK_INVALID_HANDLER(name, fn) + } + if (name === 'onSend' || name === 'preSerialization' || name === 'onError' || name === 'preParsing') { if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) { - throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.') + throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() } } else if (name === 'onReady') { if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) { - throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.') + throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() } } else { if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) { - throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.') + throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() } } diff --git a/lib/errors.js b/lib/errors.js index f34b21481c5..3ef8e568a4f 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -101,6 +101,12 @@ const codes = { 500, TypeError ), + FST_ERR_HOOK_INVALID_ASYNC_HANDLER: createError( + 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER', + 'Async function has too many arguments. Async hooks should not use the \'done\' argument.', + 500, + TypeError + ), /** * Middlewares diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index 6240d8cd6be..8d801ba14f8 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -585,33 +585,37 @@ test('preHandler respond with a stream', t => { test('Should log a warning if is an async function with `done`', t => { t.test('3 arguments', t => { - t.plan(1) + t.plan(2) const fastify = Fastify() try { fastify.addHook('onRequest', async (req, reply, done) => {}) } catch (e) { + t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) t.test('4 arguments', t => { - t.plan(3) + t.plan(6) const fastify = Fastify() try { fastify.addHook('onSend', async (req, reply, payload, done) => {}) } catch (e) { + t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } try { fastify.addHook('preSerialization', async (req, reply, payload, done) => {}) } catch (e) { + t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } try { fastify.addHook('onError', async (req, reply, payload, done) => {}) } catch (e) { + t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) diff --git a/test/hooks.on-ready.test.js b/test/hooks.on-ready.test.js index 42c241742a0..034182b2648 100644 --- a/test/hooks.on-ready.test.js +++ b/test/hooks.on-ready.test.js @@ -291,12 +291,13 @@ t.test('onReady cannot add lifecycle hooks', t => { }) t.test('onReady throw loading error', t => { - t.plan(1) + t.plan(2) const fastify = Fastify() try { fastify.addHook('onReady', async function (done) {}) } catch (e) { + t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) diff --git a/test/hooks.test.js b/test/hooks.test.js index 75adf3be518..ceaf5ada4d8 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -23,7 +23,7 @@ function getUrl (app) { } test('hooks', t => { - t.plan(43) + t.plan(49) const fastify = Fastify({ exposeHeadRoutes: false }) try { @@ -41,6 +41,22 @@ test('hooks', t => { t.fail() } + try { + fastify.addHook('preHandler', null) + } catch (e) { + t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.equal(e.message, 'preHandler hook should be a function, instead got null') + t.pass() + } + + try { + fastify.addHook('preParsing') + } catch (e) { + t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.equal(e.message, 'preParsing hook should be a function, instead got undefined') + t.pass() + } + try { fastify.addHook('preParsing', function (request, reply, payload, done) { request.preParsing = true From 9cd2fea549071f511a2a3b6b9304eb90d7cf46ec Mon Sep 17 00:00:00 2001 From: "Cesar V. Sampaio" <37849741+cesarvspr@users.noreply.github.com> Date: Sun, 23 Oct 2022 05:58:29 -0700 Subject: [PATCH 0137/1295] docs(server): config is lost when reply.call not found() is called (#4368) --- docs/Reference/Server.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 77e7805b41e..1af2057c8ee 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1426,6 +1426,13 @@ plugins are registered. If you would like to augment the behavior of the default arguments `fastify.setNotFoundHandler()` within the context of these registered plugins. +> Note: Some config properties from the request object will be +> undefined inside the custom not found handler. E.g: +> `request.routerPath`, `routerMethod` and `context.config`. +> This method design goal is to allow calling the common not found route. +> To return a per-route customized 404 response, you can do it in +> the response itself. + #### setErrorHandler From 5c75f4993255a7e3fc5afbdc4b8171be52ecb618 Mon Sep 17 00:00:00 2001 From: utsav91 Date: Tue, 25 Oct 2022 12:25:19 +0530 Subject: [PATCH 0138/1295] Fix typo - 'sever' to 'server' (#4372) --- docs/Reference/TypeScript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 8793d6afa58..3d9c0c21f4c 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -843,7 +843,7 @@ const server = fastify() Check out the Learn By Example - [Getting Started](#getting-started) example for a more detailed http server walkthrough. -###### Example 2: HTTPS sever +###### Example 2: HTTPS server 1. Create the following imports from `@types/node` and `fastify` ```typescript From 5f16e9f7c2773d24db90127ab85595f07cda6fee Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Oct 2022 11:33:48 +0200 Subject: [PATCH 0139/1295] Add platformatic to the Acknowledgements (#4378) * Add platformatic to the Acknowledgements Signed-off-by: Matteo Collina * Apply suggestions from code review * Update README.md * Update README.md Co-authored-by: Uzlopak Signed-off-by: Matteo Collina Co-authored-by: Uzlopak --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d031e1c13f4..c94429d3f69 100644 --- a/README.md +++ b/README.md @@ -374,11 +374,15 @@ in the [OpenJS Foundation](https://openjsf.org/). ## Acknowledgements This project is kindly sponsored by: -- [nearForm](https://nearform.com) +- [NearForm](https://nearform.com) +- [Platformatic](https://platformatic.dev) Past Sponsors: - [LetzDoIt](https://www.letzdoitapp.com/) +This list includes all companies that support one or more of the team members +in the maintainance of this project. + ## License Licensed under [MIT](./LICENSE). From 1ae1ec2a64333a3f0b191f8ceb87e23dcd3352a5 Mon Sep 17 00:00:00 2001 From: Simone Busoli Date: Wed, 26 Oct 2022 11:38:13 +0200 Subject: [PATCH 0140/1295] docs: add Simone Busoli to plugin maintainers (#4379) * docs: add Simone Busoli to plugin maintainers * chore: fix twitter username --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c94429d3f69..d49324b2b3a 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,8 @@ listed in alphabetical order. , * [__Rafael Gonzaga__](https://github.com/rafaelgss), , +* [__Simone Busoli__](https://github.com/simoneb), + , ### Great Contributors Great contributors on a specific area in the Fastify ecosystem will be invited From 65d1a3268d48bb24236d8ad26946255a324658a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Burzy=C5=84ski?= <33811303+jakubburzynski@users.noreply.github.com> Date: Wed, 26 Oct 2022 12:46:18 +0200 Subject: [PATCH 0141/1295] add missing 'validationContext' field to FastifyError type (#4363) * fix(types): add missing 'validationContext' field to FastifyError type * test(types): add tests checking 'validation' and 'validationContext' fields of Fa stifyError type * refactor(types): clarify 'validationContext' type with union Co-authored-by: Manuel Spigolon * test(types): add additional assertions for 'validationContext' field Co-authored-by: Manuel Spigolon --- fastify.d.ts | 1 + test/types/fastify.test-d.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/fastify.d.ts b/fastify.d.ts index aa0cc012d22..759090cd9a3 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -183,6 +183,7 @@ type TrustProxyFunction = (address: string, hop: number) => boolean declare module '@fastify/error' { interface FastifyError { validation?: ValidationResult[]; + validationContext?: 'body' | 'headers' | 'parameters' | 'querystring'; } } diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 63f65360c6b..eac0a77e4d4 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -10,7 +10,8 @@ import fastify, { InjectOptions, FastifyBaseLogger, RouteGenericInterface, ValidationResult, - FastifyErrorCodes + FastifyErrorCodes, + FastifyError } from '../../fastify' import { ErrorObject as AjvErrorObject } from 'ajv' import * as http from 'http' @@ -235,6 +236,12 @@ const ajvErrorObject: AjvErrorObject = { } expectAssignable(ajvErrorObject) +expectAssignable([ajvErrorObject]) +expectAssignable('body') +expectAssignable('headers') +expectAssignable('parameters') +expectAssignable('querystring') + const routeGeneric: RouteGenericInterface = {} expectType(routeGeneric.Body) expectType(routeGeneric.Headers) From 31972aaf557b7dada511b9a306ae9d31101c7e34 Mon Sep 17 00:00:00 2001 From: Cristian Petre Date: Wed, 26 Oct 2022 19:15:27 +0100 Subject: [PATCH 0142/1295] fix(type-providers): assignability of instance with enabled type provider (#4371) --- docs/Guides/Write-Type-Provider.md | 32 ++++++++++++++++++++++++++++++ test/types/type-provider.test-d.ts | 8 ++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 docs/Guides/Write-Type-Provider.md diff --git a/docs/Guides/Write-Type-Provider.md b/docs/Guides/Write-Type-Provider.md new file mode 100644 index 00000000000..685a87dae82 --- /dev/null +++ b/docs/Guides/Write-Type-Provider.md @@ -0,0 +1,32 @@ +

Fastify

+ +## How to write your own type provider + +Things to keep in mind when implementing a custom [type provider](../Reference/Type-Providers.md): + +### Type Contravariance + +Whereas exhaustive type narrowing checks normally rely on `never` to represent +an unreachable state, reduction in type provider interfaces should only be done +up to `unknown`. + +The reasoning is that certain methods of `FastifyInstance` are +contravariant on `TypeProvider`, which can lead to TypeScript surfacing +assignability issues unless the custom type provider interface is +substitutible with `FastifyTypeProviderDefault`. + +For example, `FastifyTypeProviderDefault` will not be assignable to the following: +```ts +export interface NotSubstitutibleTypeProvider extends FastifyTypeProvider { + // bad, nothing is assignable to `never` (except for itself) + output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : never; +} +``` + +Unless changed to: +```ts +export interface SubstitutibleTypeProvider extends FastifyTypeProvider { + // good, anything can be assigned to `unknown` + output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown; +} +``` diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index d98ea6c0eaf..ac798e1863b 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -68,7 +68,7 @@ expectAssignable(server.withTypeProvider().get<{ Body: 'over // TypeBox // ------------------------------------------------------------------- -interface TypeBoxProvider extends FastifyTypeProvider { output: this['input'] extends TSchema ? Static : never } +interface TypeBoxProvider extends FastifyTypeProvider { output: this['input'] extends TSchema ? Static : unknown } expectAssignable(server.withTypeProvider().get( '/', @@ -88,11 +88,13 @@ expectAssignable(server.withTypeProvider().get( } )) +expectAssignable(server.withTypeProvider()) + // ------------------------------------------------------------------- // JsonSchemaToTs // ------------------------------------------------------------------- -interface JsonSchemaToTsProvider extends FastifyTypeProvider { output: this['input'] extends JSONSchema ? FromSchema : never } +interface JsonSchemaToTsProvider extends FastifyTypeProvider { output: this['input'] extends JSONSchema ? FromSchema : unknown } expectAssignable(server.withTypeProvider().get( '/', @@ -115,6 +117,8 @@ expectAssignable(server.withTypeProvider().get( } )) +expectAssignable(server.withTypeProvider()) + // ------------------------------------------------------------------- // Instance Type Remappable // ------------------------------------------------------------------- From 2065c110e208f845dcddce6404e451f6eadc0ca8 Mon Sep 17 00:00:00 2001 From: KaKa Date: Thu, 27 Oct 2022 19:05:51 +0800 Subject: [PATCH 0143/1295] feat: support async trailer (#4380) * feat: support async trailer * test: increase coverage * docs: add note about error * test: increase coverage * docs: wording * feat: deprecation --- docs/Reference/Reply.md | 14 ++++- lib/reply.js | 31 +++++++++- lib/warnings.js | 2 + test/reply-trailers.test.js | 115 ++++++++++++++++++++++++++++++------ 4 files changed, 139 insertions(+), 23 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index c6697909276..b5d690ae83c 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -234,8 +234,8 @@ as soon as possible. *Note: The header `Transfer-Encoding: chunked` will be added once you use the trailer. It is a hard requirement for using trailer in Node.js.* -*Note: Currently, the computation function only supports synchronous function. -That means `async-await` and `promise` are not supported.* +*Note: Any error passed to `done` callback will be ignored. If you interested +in the error, you can turn on `debug` level logging.* ```js reply.trailer('server-timing', function() { @@ -246,7 +246,15 @@ const { createHash } = require('crypto') // trailer function also recieve two argument // @param {object} reply fastify reply // @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent -reply.trailer('content-md5', function(reply, payload) { +// @param {function} done callback to set trailer value +reply.trailer('content-md5', function(reply, payload, done) { + const hash = createHash('md5') + hash.update(payload) + done(null, hash.disgest('hex')) +}) + +// when you prefer async-await +reply.trailer('content-md5', async function(reply, payload) { const hash = createHash('md5') hash.update(payload) return hash.disgest('hex') diff --git a/lib/reply.js b/lib/reply.js index 68f02de06dc..04fbd0fe8a9 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -673,11 +673,38 @@ function sendTrailer (payload, res, reply) { if (reply[kReplyTrailers] === null) return const trailerHeaders = Object.keys(reply[kReplyTrailers]) const trailers = {} + let handled = 0 for (const trailerName of trailerHeaders) { if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue - trailers[trailerName] = reply[kReplyTrailers][trailerName](reply, payload) + handled-- + + function cb (err, value) { + // TODO: we may protect multiple callback calls + // or mixing async-await with callback + handled++ + + // we can safely ignore error for trailer + // since it does affect the client + // we log in here only for debug usage + if (err) reply.log.debug(err) + else trailers[trailerName] = value + + // add trailers when all handler handled + /* istanbul ignore else */ + if (handled === 0) { + res.addTrailers(trailers) + } + } + + const result = reply[kReplyTrailers][trailerName](reply, payload, cb) + if (typeof result === 'object' && typeof result.then === 'function') { + result.then((v) => cb(null, v), cb) + } else if (result !== null && result !== undefined) { + // TODO: should be removed in fastify@5 + warning.emit('FSTDEP013') + cb(null, result) + } } - res.addTrailers(trailers) } function sendStreamTrailer (payload, res, reply) { diff --git a/lib/warnings.js b/lib/warnings.js index 76d15e1513c..25fe27be8a5 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -23,4 +23,6 @@ warning.create('FastifyDeprecation', 'FSTDEP011', 'Variadic listen method is dep warning.create('FastifyDeprecation', 'FSTDEP012', 'Request#context property access is deprecated. Please use "Request#routeConfig" or "Request#routeSchema" instead for accessing Route settings. The "Request#context" will be removed in `fastify@5`.') +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`.') + module.exports = warning diff --git a/test/reply-trailers.test.js b/test/reply-trailers.test.js index e9ae60cdf7b..ee59ed33535 100644 --- a/test/reply-trailers.test.js +++ b/test/reply-trailers.test.js @@ -12,8 +12,8 @@ test('send trailers when payload is empty string', t => { const fastify = Fastify() fastify.get('/', function (request, reply) { - reply.trailer('ETag', function (reply, payload) { - return 'custom-etag' + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') }) reply.send('') }) @@ -36,8 +36,8 @@ test('send trailers when payload is empty buffer', t => { const fastify = Fastify() fastify.get('/', function (request, reply) { - reply.trailer('ETag', function (reply, payload) { - return 'custom-etag' + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') }) reply.send(Buffer.alloc(0)) }) @@ -60,8 +60,8 @@ test('send trailers when payload is undefined', t => { const fastify = Fastify() fastify.get('/', function (request, reply) { - reply.trailer('ETag', function (reply, payload) { - return 'custom-etag' + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') }) reply.send(undefined) }) @@ -88,11 +88,11 @@ test('send trailers when payload is json', t => { const md5 = hash.digest('hex') fastify.get('/', function (request, reply) { - reply.trailer('Content-MD5', function (reply, payload) { + reply.trailer('Content-MD5', function (reply, payload, done) { t.equal(data, payload) const hash = createHash('md5') hash.update(payload) - return hash.digest('hex') + done(null, hash.digest('hex')) }) reply.send(data) }) @@ -116,9 +116,9 @@ test('send trailers when payload is stream', t => { const fastify = Fastify() fastify.get('/', function (request, reply) { - reply.trailer('ETag', function (reply, payload) { + reply.trailer('ETag', function (reply, payload, done) { t.same(payload, null) - return 'custom-etag' + done(null, 'custom-etag') }) const stream = Readable.from([JSON.stringify({ hello: 'world' })]) reply.send(stream) @@ -137,6 +137,85 @@ test('send trailers when payload is stream', t => { }) }) +test('send trailers when using async-await', t => { + t.plan(5) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', async function (reply, payload) { + return 'custom-etag' + }) + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers.trailer, 'etag') + t.equal(res.trailers.etag, 'custom-etag') + t.notHas(res.headers, 'content-length') + }) +}) + +test('error in trailers should be ignored', t => { + t.plan(5) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload, done) { + done('error') + }) + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers.trailer, 'etag') + t.notHas(res.trailers, 'etag') + t.notHas(res.headers, 'content-length') + }) +}) + +test('should emit deprecation warning when using direct return', t => { + t.plan(7) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload) { + return 'custom-etag' + }) + reply.send('') + }) + + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.code, 'FSTDEP013') + } + t.teardown(() => process.removeListener('warning', onWarning)) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers.trailer, 'etag') + t.equal(res.trailers.etag, 'custom-etag') + t.notHas(res.headers, 'content-length') + }) +}) + test('removeTrailer', t => { t.plan(6) @@ -144,12 +223,12 @@ test('removeTrailer', t => { fastify.get('/', function (request, reply) { reply.removeTrailer('ETag') // remove nothing - reply.trailer('ETag', function (reply, payload) { - return 'custom-etag' + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') }) - reply.trailer('Should-Not-Call', function (reply, payload) { + reply.trailer('Should-Not-Call', function (reply, payload, done) { t.fail('it should not called as this trailer is removed') - return 'should-not-call' + done(null, 'should-not-call') }) reply.removeTrailer('Should-Not-Call') reply.send(undefined) @@ -175,13 +254,13 @@ test('hasTrailer', t => { fastify.get('/', function (request, reply) { t.equal(reply.hasTrailer('ETag'), false) - reply.trailer('ETag', function (reply, payload) { - return 'custom-etag' + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') }) t.equal(reply.hasTrailer('ETag'), true) - reply.trailer('Should-Not-Call', function (reply, payload) { + reply.trailer('Should-Not-Call', function (reply, payload, done) { t.fail('it should not called as this trailer is removed') - return 'should-not-call' + done(null, 'should-not-call') }) t.equal(reply.hasTrailer('Should-Not-Call'), true) reply.removeTrailer('Should-Not-Call') From 183576d342e893e37ac1f0226408fe650cd7bed2 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 28 Oct 2022 14:46:21 +0800 Subject: [PATCH 0144/1295] fix: trailers async race condition (#4383) * fix: trailers async race condition * fix: import * refactor: typo and comment --- lib/reply.js | 36 ++++++++---- test/reply-trailers.test.js | 110 ++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 10 deletions(-) diff --git a/lib/reply.js b/lib/reply.js index 04fbd0fe8a9..9f1332146e5 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -570,8 +570,6 @@ function onSendEnd (reply, payload) { res.writeHead(statusCode, reply[kReplyHeaders]) sendTrailer(payload, res, reply) - // avoid ArgumentsAdaptorTrampoline from V8 - res.end(null, null, null) return } @@ -600,8 +598,6 @@ function onSendEnd (reply, payload) { res.write(payload) // then send trailers sendTrailer(payload, res, reply) - // avoid ArgumentsAdaptorTrampoline from V8 - res.end(null, null, null) } function logStreamError (logger, err, res) { @@ -670,12 +666,29 @@ function sendStream (payload, res, reply) { } function sendTrailer (payload, res, reply) { - if (reply[kReplyTrailers] === null) return + if (reply[kReplyTrailers] === null) { + // when no trailer, we close the stream + res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8 + return + } const trailerHeaders = Object.keys(reply[kReplyTrailers]) const trailers = {} let handled = 0 + let skipped = true + function send () { + // add trailers when all handler handled + /* istanbul ignore else */ + if (handled === 0) { + res.addTrailers(trailers) + // we need to properly close the stream + // after trailers sent + res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8 + } + } + for (const trailerName of trailerHeaders) { if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue + skipped = false handled-- function cb (err, value) { @@ -689,11 +702,10 @@ function sendTrailer (payload, res, reply) { if (err) reply.log.debug(err) else trailers[trailerName] = value - // add trailers when all handler handled - /* istanbul ignore else */ - if (handled === 0) { - res.addTrailers(trailers) - } + // we push the check to the end of event + // loop, so the registration continue to + // process. + process.nextTick(send) } const result = reply[kReplyTrailers][trailerName](reply, payload, cb) @@ -705,6 +717,10 @@ function sendTrailer (payload, res, reply) { cb(null, result) } } + + // when all trailers are skipped + // we need to close the stream + if (skipped) res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8 } function sendStreamTrailer (payload, res, reply) { diff --git a/test/reply-trailers.test.js b/test/reply-trailers.test.js index ee59ed33535..1e50fe90d0f 100644 --- a/test/reply-trailers.test.js +++ b/test/reply-trailers.test.js @@ -5,6 +5,8 @@ const test = t.test const Fastify = require('..') const { Readable } = require('stream') const { createHash } = require('crypto') +const { promisify } = require('util') +const sleep = promisify(setTimeout) test('send trailers when payload is empty string', t => { t.plan(5) @@ -216,6 +218,82 @@ test('should emit deprecation warning when using direct return', t => { }) }) +test('trailer handler counter', t => { + t.plan(2) + + const data = JSON.stringify({ hello: 'world' }) + const hash = createHash('md5') + hash.update(data) + const md5 = hash.digest('hex') + + t.test('callback with timeout', t => { + t.plan(9) + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('Return-Early', function (reply, payload, done) { + t.equal(data, payload) + done(null, 'return') + }) + reply.trailer('Content-MD5', function (reply, payload, done) { + t.equal(data, payload) + const hash = createHash('md5') + hash.update(payload) + setTimeout(() => { + done(null, hash.digest('hex')) + }, 500) + }) + reply.send(data) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['transfer-encoding'], 'chunked') + t.equal(res.headers.trailer, 'return-early content-md5') + t.equal(res.trailers['return-early'], 'return') + t.equal(res.trailers['content-md5'], md5) + t.notHas(res.headers, 'content-length') + }) + }) + + t.test('async-await', t => { + t.plan(9) + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('Return-Early', async function (reply, payload) { + t.equal(data, payload) + return 'return' + }) + reply.trailer('Content-MD5', async function (reply, payload) { + t.equal(data, payload) + const hash = createHash('md5') + hash.update(payload) + await sleep(500) + return hash.digest('hex') + }) + reply.send(data) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['transfer-encoding'], 'chunked') + t.equal(res.headers.trailer, 'return-early content-md5') + t.equal(res.trailers['return-early'], 'return') + t.equal(res.trailers['content-md5'], md5) + t.notHas(res.headers, 'content-length') + }) + }) +}) + test('removeTrailer', t => { t.plan(6) @@ -247,6 +325,38 @@ test('removeTrailer', t => { }) }) +test('remove all trailers', t => { + t.plan(6) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload, done) { + t.fail('it should not called as this trailer is removed') + done(null, 'custom-etag') + }) + reply.removeTrailer('ETag') + reply.trailer('Should-Not-Call', function (reply, payload, done) { + t.fail('it should not called as this trailer is removed') + done(null, 'should-not-call') + }) + reply.removeTrailer('Should-Not-Call') + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.notOk(res.headers.trailer) + t.notOk(res.trailers.etag) + t.notOk(res.trailers['should-not-call']) + t.notHas(res.headers, 'content-length') + }) +}) + test('hasTrailer', t => { t.plan(10) From c0f72da5994365f31948ae44e8cf9faa3c03e87a Mon Sep 17 00:00:00 2001 From: Chuong Tran Date: Fri, 28 Oct 2022 19:27:50 +0700 Subject: [PATCH 0145/1295] docs(ecosystem): Add fastify-list-routes (#4385) * docs(ecosystem): Add fastify-list-routes * update order alphabet * update format * update format --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 50f9dd06bf7..5ec7cdb10f5 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -378,6 +378,8 @@ section. Fastify plugin to parse request language. - [`fastify-lcache`](https://github.com/denbon05/fastify-lcache) Lightweight cache plugin +- [`fastify-list-routes`](https://github.com/chuongtrh/fastify-list-routes) + A simple plugin for Fastify list all available routes. - [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from a directory and inject the Fastify instance in each file. - [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua From e9604ce3af7a6a846bdbca1d20a93dc7e5a51401 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:21:30 +0000 Subject: [PATCH 0146/1295] build(deps-dev): bump @sinclair/typebox from 0.24.51 to 0.25.2 (#4388) Bumps [@sinclair/typebox](https://github.com/sinclairzx81/typebox) from 0.24.51 to 0.25.2. - [Release notes](https://github.com/sinclairzx81/typebox/releases) - [Changelog](https://github.com/sinclairzx81/typebox/blob/master/changelog.md) - [Commits](https://github.com/sinclairzx81/typebox/compare/0.24.51...0.25.2) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e14318b6ee0..6dd992d589f 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "homepage": "https://www.fastify.io/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.24.41", + "@sinclair/typebox": "^0.25.2", "@sinonjs/fake-timers": "^9.1.2", "@types/node": "^18.7.18", "@typescript-eslint/eslint-plugin": "^5.37.0", From a71dd83cc65c79c2a01dfbe91f8ee64fec3c0509 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Mon, 31 Oct 2022 23:04:07 +0530 Subject: [PATCH 0147/1295] fix: Improve error message for hooks check (#4387) * Improve error message for hooks check * log null value more precisely * final update and all testcase passed --- lib/hooks.js | 2 +- lib/route.js | 4 ++-- test/hooks.test.js | 6 +++--- test/internals/hooks.test.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/hooks.js b/lib/hooks.js index d1bdadad12d..31254781775 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -52,7 +52,7 @@ Hooks.prototype.validate = function (hook, fn) { if (supportedHooks.indexOf(hook) === -1) { throw new Error(`${hook} hook not supported!`) } - if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof fn) + if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(fn)) } Hooks.prototype.add = function (hook, fn) { diff --git a/lib/route.js b/lib/route.js index fc4526de0fd..621aa871a13 100644 --- a/lib/route.js +++ b/lib/route.js @@ -249,11 +249,11 @@ function buildRouting (options) { if (Array.isArray(opts[hook])) { for (const func of opts[hook]) { if (typeof func !== 'function') { - throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof func) + throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(func)) } } } else if (opts[hook] !== undefined && typeof opts[hook] !== 'function') { - throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof opts[hook]) + throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(opts[hook])) } } } diff --git a/test/hooks.test.js b/test/hooks.test.js index ceaf5ada4d8..70d589a7aad 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3332,7 +3332,7 @@ test('registering invalid hooks should throw an error', async t => { return 'hello world' } }) - }, new Error('onRequest hook should be a function, instead got undefined')) + }, new Error('onRequest hook should be a function, instead got [object Undefined]')) t.throws(() => { fastify.route({ @@ -3343,7 +3343,7 @@ test('registering invalid hooks should throw an error', async t => { return 'hello world' } }) - }, new Error('onRequest hook should be a function, instead got object')) + }, new Error('onRequest hook should be a function, instead got [object Null]')) // undefined is ok fastify.route({ @@ -3363,5 +3363,5 @@ test('registering invalid hooks should throw an error', async t => { fastify.get('/', function (request, reply) { reply.send('hello world') }) - }, new Error('onSend hook should be a function, instead got undefined')) + }, new Error('onSend hook should be a function, instead got [object Undefined]')) }) diff --git a/test/internals/hooks.test.js b/test/internals/hooks.test.js index f5aed5d6440..5db3854078b 100644 --- a/test/internals/hooks.test.js +++ b/test/internals/hooks.test.js @@ -78,6 +78,6 @@ test('should throw on wrong parameters', t => { t.fail() } catch (e) { t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') - t.equal(e.message, 'onSend hook should be a function, instead got object') + t.equal(e.message, 'onSend hook should be a function, instead got [object Null]') } }) From 3ba44ce311e41d384665ff382ccb969abbb6fa14 Mon Sep 17 00:00:00 2001 From: KaKa Date: Tue, 1 Nov 2022 15:04:26 +0800 Subject: [PATCH 0148/1295] fix: tiny-lru usage (#4391) chore: bump tiny-lru to 10.0.0 refactor: more explicit check --- lib/contentTypeParser.js | 5 ++--- package.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 8f46dd0b618..3326e192c3c 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -94,9 +94,8 @@ ContentTypeParser.prototype.getParser = function (contentType) { return this.customParsers.get(contentType) } - if (this.cache.has(contentType)) { - return this.cache.get(contentType) - } + const parser = this.cache.get(contentType) + if (parser !== undefined) return parser // eslint-disable-next-line no-var for (var i = 0; i !== this.parserList.length; ++i) { diff --git a/package.json b/package.json index 6dd992d589f..ed5c910b3f2 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "rfdc": "^1.3.0", "secure-json-parse": "^2.5.0", "semver": "^7.3.7", - "tiny-lru": "^9.0.2" + "tiny-lru": "^10.0.0" }, "standard": { "ignore": [ From a02650e40ca6f165b99a7ef72f69196d05092f94 Mon Sep 17 00:00:00 2001 From: Maksim Sinik Date: Tue, 1 Nov 2022 14:57:39 +0100 Subject: [PATCH 0149/1295] Removes old Note about named imports in ESM (#4392) As of today, you can import the named Fastify import in the ESM context with no issues. Namely, this works as expected: ``` import { fastify } from "fastify"; const app = fastify({ logger: true, }); await app.listen({ port: 3000 }); ``` --- docs/Guides/Plugins-Guide.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index e46fb1f9801..de131b92428 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -439,24 +439,6 @@ async function plugin (fastify, opts) { export default plugin ``` -__Note__: Fastify does not support named imports within an ESM context. Instead, -the `default` export is available. - -```js -// server.mjs -import Fastify from 'fastify' - -const fastify = Fastify() - -///... - -fastify.listen({ port: 3000 }, (err, address) => { - if (err) { - fastify.log.error(err) - process.exit(1) - } -}) -``` ## Handle errors From 041cf41d49eb202684fcb9307e6dc5200df9cb33 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Tue, 1 Nov 2022 20:32:01 +0200 Subject: [PATCH 0150/1295] docs: Add section about capacity planning (#4386) --- docs/Guides/Recommendations.md | 38 +++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index f0ee7b19859..ad40fb84588 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -8,7 +8,8 @@ This document contains a set of recommendations when using Fastify. - [HAProxy](#haproxy) - [Nginx](#nginx) - [Kubernetes](#kubernetes) - +- [Kubernetes](#kubernetes) +- [Capacity Planning For Production](#capacity) ## Use A Reverse Proxy @@ -298,3 +299,38 @@ readinessProbe: timeoutSeconds: 3 successThreshold: 1 failureThreshold: 5 +``` + +## Capacity Planning For Production + + +In order to rightsize the production environment for your Fastify application, +you are highly recommended to perform your own measurements against +different configurations of the environment, which may +use real CPU cores, virtual CPU cores (vCPU), or even fractional +vCPU cores. We will use the term vCPU throughout this +recommendation to represent any CPU type. + +You can use such tools as [k6](https://github.com/grafana/k6) +or [autocannon](https://github.com/mcollina/autocannon) for conducting +the necessary performance tests. + +That said, you may also consider the following as a rule of a thumb: + +* In order to have the lowest possible latency, 2 vCPU are recommended per app +instance (e.g., a k8s pod). The second vCPU will mostly be used by the +garbage collector (GC) and libuv threadpool. This will minimize the latency +for your users, as well as the memory usage, as the GC will be run more +frequently. Also, the main thread won't have to stop to let the GC run. + +* In order to optimize for throughput (handling the largest possible amount of +requests per second per vCPU available), consider using smaller amount of vCPUs +per app instance. It is totally fine to run Node.js application with 1 vCPU. + +* You may experiment with even smaller amount of vCPU, which may provide even +better throughput in certain use-cases. There are reports of e. g. API gateway +solutions working well with 100m-200m vCPU in Kubernetes. + +See [Node's Event Loop From the Inside Out ](https://www.youtube.com/watch?v=P9csgxBgaZ8) +in order to understand the workings of Node.js in greater detail and make a +better determination about what your specific application needs. From c6abdf77f6e0f4c4be66a1d2818c9d215c51bfa1 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 2 Nov 2022 16:06:32 +0000 Subject: [PATCH 0151/1295] docs(recommendations): grammar fixes (#4396) * docs(recommendations): grammar fixes * Update docs/Guides/Recommendations.md Co-authored-by: Uzlopak * Update docs/Guides/Recommendations.md Co-authored-by: Uzlopak Co-authored-by: Uzlopak --- docs/Guides/Recommendations.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index ad40fb84588..f131b7ea503 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -305,32 +305,32 @@ readinessProbe: In order to rightsize the production environment for your Fastify application, -you are highly recommended to perform your own measurements against +it is highly recommended that you perform your own measurements against different configurations of the environment, which may use real CPU cores, virtual CPU cores (vCPU), or even fractional vCPU cores. We will use the term vCPU throughout this recommendation to represent any CPU type. -You can use such tools as [k6](https://github.com/grafana/k6) -or [autocannon](https://github.com/mcollina/autocannon) for conducting -the necessary performance tests. +Tools such as [k6](https://github.com/grafana/k6) +or [autocannon](https://github.com/mcollina/autocannon) can be used for +conducting the necessary performance tests. -That said, you may also consider the following as a rule of a thumb: +That said, you may also consider the following as a rule of thumb: -* In order to have the lowest possible latency, 2 vCPU are recommended per app +* To have the lowest possible latency, 2 vCPU are recommended per app instance (e.g., a k8s pod). The second vCPU will mostly be used by the garbage collector (GC) and libuv threadpool. This will minimize the latency for your users, as well as the memory usage, as the GC will be run more frequently. Also, the main thread won't have to stop to let the GC run. -* In order to optimize for throughput (handling the largest possible amount of -requests per second per vCPU available), consider using smaller amount of vCPUs +* To optimize for throughput (handling the largest possible amount of +requests per second per vCPU available), consider using a smaller amount of vCPUs per app instance. It is totally fine to run Node.js application with 1 vCPU. -* You may experiment with even smaller amount of vCPU, which may provide even -better throughput in certain use-cases. There are reports of e. g. API gateway +* You may experiment with an even smaller amount of vCPU, which may provide +even better throughput in certain use-cases. There are reports of API gateway solutions working well with 100m-200m vCPU in Kubernetes. See [Node's Event Loop From the Inside Out ](https://www.youtube.com/watch?v=P9csgxBgaZ8) -in order to understand the workings of Node.js in greater detail and make a +to understand the workings of Node.js in greater detail and make a better determination about what your specific application needs. From de53fba42a664af05f1492564f5a71427135a55b Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 3 Nov 2022 14:31:51 +0100 Subject: [PATCH 0152/1295] chore(doc): duplicated menu item (#4398) --- docs/Guides/Recommendations.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index f131b7ea503..a02f8623ac9 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -8,7 +8,6 @@ This document contains a set of recommendations when using Fastify. - [HAProxy](#haproxy) - [Nginx](#nginx) - [Kubernetes](#kubernetes) -- [Kubernetes](#kubernetes) - [Capacity Planning For Production](#capacity) ## Use A Reverse Proxy From f1bd80e007b019a2708c5fc1ff8340b0082908e7 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Sun, 6 Nov 2022 02:32:04 +0530 Subject: [PATCH 0153/1295] feat: add request.routeOptions object (#4397) --- docs/Reference/Request.md | 21 +++++++- lib/context.js | 4 ++ lib/request.js | 19 +++++++ lib/route.js | 2 + test/bodyLimit.test.js | 79 +++++++++++++++++++++++++++++ test/request-error.test.js | 96 ++++++++++++++++++++++++++++++++++++ test/types/request.test-d.ts | 3 +- types/request.d.ts | 12 +++++ 8 files changed, 234 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 93af618f9cf..1472fd2d0d0 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -42,6 +42,17 @@ Request is a core Fastify object containing the following fields: handling the request - `routeConfig` - The route [`config`](./Routes.md#routes-config) object. +- `routeOptions` - The route [`option`](./Routes.md#routes-options) object + - `bodyLimit` - either server limit or route limit + - `method` - the http method for the route + - `url` - the path of the URL to match this route + - `attachValidation` - attach `validationError` to request + (if there is a schema defined) + - `logLevel` - log level defined for this route + - `version` - a semver compatible string that defines the version of the endpoint + - `exposeHeadRoute` - creates a sibling HEAD route for any GET routes + - `prefixTrailingSlash` - string used to determine how to handle passing / + as a route with a prefix. - [.getValidationFunction(schema | httpPart)](#getvalidationfunction) - Returns a validation function for the specified schema or http part, if any of either are set or cached. @@ -90,7 +101,15 @@ fastify.post('/:params', options, function (request, reply) { console.log(request.protocol) console.log(request.url) console.log(request.routerMethod) - console.log(request.routerPath) + console.log(request.routeOptions.bodyLimit) + console.log(request.routeOptions.method) + console.log(request.routeOptions.url) + console.log(request.routeOptions.attachValidation) + console.log(request.routeOptions.logLevel) + console.log(request.routeOptions.version) + console.log(request.routeOptions.exposeHeadRoute) + console.log(request.routeOptions.prefixTrailingSlash) + console.log(request.routerPath.logLevel) request.log.info('some info') }) ``` diff --git a/lib/context.js b/lib/context.js index 91953f947c1..226d09280de 100644 --- a/lib/context.js +++ b/lib/context.js @@ -31,6 +31,8 @@ function Context ({ serializerCompiler, replySerializer, schemaErrorFormatter, + exposeHeadRoute, + prefixTrailingSlash, server, isFastify }) { @@ -52,6 +54,8 @@ function Context ({ this._parserOptions = { limit: bodyLimit || server[kBodyLimit] } + this.exposeHeadRoute = exposeHeadRoute + this.prefixTrailingSlash = prefixTrailingSlash this.logLevel = logLevel || server[kLogLevel] this.logSerializers = logSerializers this[kFourOhFourContext] = null diff --git a/lib/request.js b/lib/request.js index ab47c6f0300..25d4c4ec0c5 100644 --- a/lib/request.js +++ b/lib/request.js @@ -165,6 +165,25 @@ Object.defineProperties(Request.prototype, { return this[kRouteContext].config.url } }, + routeOptions: { + get () { + const context = this[kRouteContext] + const routeLimit = context._parserOptions.limit + const serverLimit = context.server.initialConfig.bodyLimit + const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined + const options = { + method: context.config.method, + url: context.config.url, + bodyLimit: (routeLimit || serverLimit), + attachValidation: context.attachValidation, + logLevel: context.logLevel, + exposeHeadRoute: context.exposeHeadRoute, + prefixTrailingSlash: context.prefixTrailingSlash, + version + } + return Object.freeze(options) + } + }, routerMethod: { get () { return this[kRouteContext].config.method diff --git a/lib/route.js b/lib/route.js index 621aa871a13..f48467de881 100644 --- a/lib/route.js +++ b/lib/route.js @@ -278,6 +278,8 @@ function buildRouting (options) { replySerializer: this[kReplySerializerDefault], validatorCompiler: opts.validatorCompiler, serializerCompiler: opts.serializerCompiler, + exposeHeadRoute: shouldExposeHead, + prefixTrailingSlash: (opts.prefixTrailingSlash || 'both'), server: this, isFastify }) diff --git a/test/bodyLimit.test.js b/test/bodyLimit.test.js index f1c50b8a37f..980dd23b2a7 100644 --- a/test/bodyLimit.test.js +++ b/test/bodyLimit.test.js @@ -44,3 +44,82 @@ test('bodyLimit', t => { }) }) }) + +test('default request.routeOptions.bodyLimit should be 1048576', t => { + t.plan(4) + const fastify = Fastify() + fastify.post('/default-bodylimit', { + handler (request, reply) { + t.equal(1048576, request.routeOptions.bodyLimit) + reply.send({ }) + } + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/default-bodylimit', + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('request.routeOptions.bodyLimit should be equal to route limit', t => { + t.plan(4) + const fastify = Fastify({ bodyLimit: 1 }) + fastify.post('/route-limit', { + bodyLimit: 1000, + handler (request, reply) { + t.equal(1000, request.routeOptions.bodyLimit) + reply.send({}) + } + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/route-limit', + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('request.routeOptions.bodyLimit should be equal to server limit', t => { + t.plan(4) + const fastify = Fastify({ bodyLimit: 100 }) + fastify.post('/server-limit', { + handler (request, reply) { + t.equal(100, request.routeOptions.bodyLimit) + reply.send({}) + } + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/server-limit', + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) diff --git a/test/request-error.test.js b/test/request-error.test.js index a3be6e1870e..9108f9aa4ec 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -1,6 +1,7 @@ 'use strict' const { connect } = require('net') +const sget = require('simple-get').concat const t = require('tap') const test = t.test const Fastify = require('..') @@ -313,3 +314,98 @@ test('default clientError replies with bad request on reused keep-alive connecti client.write('\r\n\r\n') }) }) + +test('request.routeOptions should be immutable', t => { + t.plan(14) + const fastify = Fastify() + const handler = function (req, res) { + t.equal('POST', req.routeOptions.method) + t.equal('/', req.routeOptions.url) + t.throws(() => { req.routeOptions = null }, new TypeError('Cannot set property routeOptions of # which has only a getter')) + t.throws(() => { req.routeOptions.method = 'INVALID' }, new TypeError('Cannot assign to read only property \'method\' of object \'#\'')) + t.throws(() => { req.routeOptions.url = '//' }, new TypeError('Cannot assign to read only property \'url\' of object \'#\'')) + t.throws(() => { req.routeOptions.bodyLimit = 0xDEADBEEF }, new TypeError('Cannot assign to read only property \'bodyLimit\' of object \'#\'')) + t.throws(() => { req.routeOptions.attachValidation = true }, new TypeError('Cannot assign to read only property \'attachValidation\' of object \'#\'')) + t.throws(() => { req.routeOptions.logLevel = 'invalid' }, new TypeError('Cannot assign to read only property \'logLevel\' of object \'#\'')) + t.throws(() => { req.routeOptions.version = '95.0.1' }, new TypeError('Cannot assign to read only property \'version\' of object \'#\'')) + t.throws(() => { req.routeOptions.prefixTrailingSlash = true }, new TypeError('Cannot assign to read only property \'prefixTrailingSlash\' of object \'#\'')) + t.throws(() => { req.routeOptions.newAttribute = {} }, new TypeError('Cannot add property newAttribute, object is not extensible')) + + for (const key of Object.keys(req.routeOptions)) { + if (typeof req.routeOptions[key] === 'object' && req.routeOptions[key] !== null) { + t.fail('Object.freeze must run recursively on nested structures to ensure that routeOptions is immutable.') + } + } + + res.send({}) + } + fastify.post('/', { + bodyLimit: 1000, + handler + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port, + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('test request.routeOptions.version', t => { + t.plan(7) + const fastify = Fastify() + + fastify.route({ + method: 'POST', + url: '/version', + constraints: { version: '1.2.0' }, + handler: function (request, reply) { + t.equal('1.2.0', request.routeOptions.version) + reply.send({}) + } + }) + + fastify.route({ + method: 'POST', + url: '/version-undefined', + handler: function (request, reply) { + t.equal(undefined, request.routeOptions.version) + reply.send({}) + } + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/version', + headers: { 'Content-Type': 'application/json', 'Accept-Version': '1.2.0' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/version-undefined', + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 6b041dab309..5f5cd827e41 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -17,7 +17,7 @@ import fastify, { } from '../../fastify' import { RequestParamsDefault, RequestHeadersDefault, RequestQuerystringDefault } from '../../types/utils' import { FastifyLoggerInstance } from '../../types/logger' -import { FastifyRequest } from '../../types/request' +import { FastifyRequest, RequestRouteOptions } from '../../types/request' import { FastifyReply } from '../../types/reply' import { FastifyInstance } from '../../types/instance' import { RouteGenericInterface } from '../../types/route' @@ -66,6 +66,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.method) expectType(request.routerPath) expectType(request.routerMethod) + expectType>(request.routeOptions) expectType(request.is404) expectType(request.hostname) expectType(request.ip) diff --git a/types/request.d.ts b/types/request.d.ts index 2944fca6d47..a23ba41f398 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -20,6 +20,17 @@ export interface ValidationFunction { errors?: null | ErrorObject[]; } +export interface RequestRouteOptions { + method: string, + url: string, + bodyLimit:number, + attachValidation:boolean, + logLevel:string, + version: string | undefined, + exposeHeadRoute: boolean, + prefixTrailingSlash: string +} + /** * FastifyRequest is an instance of the standard http or http2 request objects. * It defaults to http.IncomingMessage, and it also extends the relative request object. @@ -66,6 +77,7 @@ export interface FastifyRequest readonly is404: boolean; readonly socket: RawRequest['socket']; From 675b00d6ca7a7eeaad073e6469c70b50fcd532eb Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Mon, 7 Nov 2022 13:38:44 +0200 Subject: [PATCH 0154/1295] docs: Document multiple app approach (#4393) Co-authored-by: James Sumners Co-authored-by: Frazer Smith --- docs/Guides/Recommendations.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index a02f8623ac9..9af219617d6 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -9,6 +9,8 @@ This document contains a set of recommendations when using Fastify. - [Nginx](#nginx) - [Kubernetes](#kubernetes) - [Capacity Planning For Production](#capacity) +- [Running Multiple Instances](#multiple) + ## Use A Reverse Proxy @@ -333,3 +335,17 @@ solutions working well with 100m-200m vCPU in Kubernetes. See [Node's Event Loop From the Inside Out ](https://www.youtube.com/watch?v=P9csgxBgaZ8) to understand the workings of Node.js in greater detail and make a better determination about what your specific application needs. + +## Running Multiple Instances + + +There are several use-cases where running multiple Fastify +apps on the same server might be considered. A common example +would be exposing metrics endpoints on a separate port, +to prevent public access, when using a reverse proxy or an ingress +firewall is not an option. + +It is perfectly fine to spin up several Fastify instances within the same +Node.js process and run them concurrently, even in high load systems. +Each Fastify instance only generates as much load as the traffic it receives, +plus the memory used for that Fastify instance. From ea585810695948fff9e0bf185b81ffa1b4b71ae7 Mon Sep 17 00:00:00 2001 From: Michael Marti <35479130+mmarti@users.noreply.github.com> Date: Mon, 7 Nov 2022 23:20:37 -0800 Subject: [PATCH 0155/1295] docs: fix example using db decorator on fastify instance (#4406) * fix example using db decorator on fastify instance I believe the current docs are a typo/broken example, unless that's an undocumented syntax. This change adds an example for both returning the value since we're using async/await, along with an example for still using `reply.send()` and then `await`ing `reply`. * remove semicolons --- docs/Reference/Decorators.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index b2bf0a9985d..fe413c139b1 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -107,7 +107,13 @@ route [route](./Routes.md) handlers: fastify.decorate('db', new DbConnection()) fastify.get('/', async function (request, reply) { - reply({hello: await this.db.query('world')}) + // using return + return { hello: await this.db.query('world') } + + // or + // using reply.send() + reply.send({ hello: await this.db.query('world') }) + await reply }) ``` From cd45d4ea170ea7bcbfdc05e816a8834a83e264f2 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Wed, 9 Nov 2022 14:34:44 +0100 Subject: [PATCH 0156/1295] add missing route shorthands (#4409) --- docs/Reference/Routes.md | 18 +++++++++++ fastify.js | 27 ++++++++++++++++ test/route-shorthand.test.js | 60 ++++++++++++++++++++++++++++++++++++ test/types/route.test-d.ts | 8 +++-- types/instance.d.ts | 13 ++++++-- 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 test/route-shorthand.test.js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 0e47e6e49ca..82332a6d468 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -176,6 +176,24 @@ The above route declaration is more *Hapi*-like, but if you prefer an `fastify.patch(path, [options], handler)` +`fastify.propfind(path, [options], handler)` + +`fastify.proppatch(path, [options], handler)` + +`fastify.mkcol(path, [options], handler)` + +`fastify.copy(path, [options], handler)` + +`fastify.move(path, [options], handler)` + +`fastify.lock(path, [options], handler)` + +`fastify.unlock(path, [options], handler)` + +`fastify.trace(path, [options], handler)` + +`fastify.search(path, [options], handler)` + Example: ```js const opts = { diff --git a/fastify.js b/fastify.js index 7882d2afb4b..5cf0e8f0d85 100644 --- a/fastify.js +++ b/fastify.js @@ -263,6 +263,33 @@ function fastify (options) { options: function _options (url, options, handler) { return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler }) }, + propfind: function _propfind (url, options, handler) { + return router.prepareRoute.call(this, { method: 'PROPFIND', url, options, handler }) + }, + proppatch: function _proppatch (url, options, handler) { + return router.prepareRoute.call(this, { method: 'PROPPATCH', url, options, handler }) + }, + mkcol: function _mkcol (url, options, handler) { + return router.prepareRoute.call(this, { method: 'MKCOL', url, options, handler }) + }, + copy: function _copy (url, options, handler) { + return router.prepareRoute.call(this, { method: 'COPY', url, options, handler }) + }, + move: function _move (url, options, handler) { + return router.prepareRoute.call(this, { method: 'MOVE', url, options, handler }) + }, + lock: function _lock (url, options, handler) { + return router.prepareRoute.call(this, { method: 'LOCK', url, options, handler }) + }, + unlock: function _unlock (url, options, handler) { + return router.prepareRoute.call(this, { method: 'UNLOCK', url, options, handler }) + }, + trace: function _trace (url, options, handler) { + return router.prepareRoute.call(this, { method: 'TRACE', url, options, handler }) + }, + search: function _search (url, options, handler) { + return router.prepareRoute.call(this, { method: 'SEARCH', url, options, handler }) + }, all: function _all (url, options, handler) { return router.prepareRoute.call(this, { method: supportedMethods, url, options, handler }) }, diff --git a/test/route-shorthand.test.js b/test/route-shorthand.test.js new file mode 100644 index 00000000000..d980f869ca1 --- /dev/null +++ b/test/route-shorthand.test.js @@ -0,0 +1,60 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../fastify') +const supportedMethods = require('../lib/httpMethods').supportedMethods + +test('route-shorthand', t => { + t.plan(supportedMethods.length + 1) + const test = t.test + + for (const method of supportedMethods) { + test(`route-shorthand - ${method.toLowerCase()}`, t => { + t.plan(3) + const fastify = new Fastify() + fastify[method.toLowerCase()]('/', function (req, reply) { + t.equal(req.method, method) + reply.send() + }) + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method, + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) + }) + } + + test('route-shorthand - all', t => { + t.plan(3 * supportedMethods.length) + const fastify = new Fastify() + let currentMethod = '' + fastify.all('/', function (req, reply) { + t.equal(req.method, currentMethod) + reply.send() + }) + fastify.listen({ port: 0 }, async function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + for (const method of supportedMethods) { + currentMethod = method + await new Promise(resolve => sget({ + method, + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + resolve() + }) + ) + } + }) + }) +}) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 697105dca8a..2d9a0a3fdb4 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -35,9 +35,13 @@ const routeHandlerWithReturnValue: RouteHandlerMethod = function (request, reply return reply.send() } -type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'options' +type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' | +'options' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' | +'unlock' | 'trace' | 'search' -;['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE', 'OPTIONS'].forEach(method => { +;['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'PROPFIND', + 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', 'TRACE', 'SEARCH' +].forEach(method => { // route method expectType(fastify().route({ method: method as HTTPMethods, diff --git a/types/instance.d.ts b/types/instance.d.ts index 0d562717cf6..d192b63c5e2 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -177,13 +177,22 @@ export interface FastifyInstance< SchemaCompiler extends FastifySchema = FastifySchema, >(opts: RouteOptions): FastifyInstance; + delete: RouteShorthandMethod; get: RouteShorthandMethod; head: RouteShorthandMethod; + patch: RouteShorthandMethod; post: RouteShorthandMethod; put: RouteShorthandMethod; - delete: RouteShorthandMethod; options: RouteShorthandMethod; - patch: RouteShorthandMethod; + propfind: RouteShorthandMethod; + proppatch: RouteShorthandMethod; + mkcol: RouteShorthandMethod; + copy: RouteShorthandMethod; + move: RouteShorthandMethod; + lock: RouteShorthandMethod; + unlock: RouteShorthandMethod; + trace: RouteShorthandMethod; + search: RouteShorthandMethod; all: RouteShorthandMethod; hasRoute< From 5c8039c40e11be4a9c3617c145cd2667e0c68f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=9C=E5=B2=B3?= <1277952981@qq.com> Date: Thu, 10 Nov 2022 15:44:59 +0800 Subject: [PATCH 0157/1295] docs: fix removeAdditional refer (#4410) --- docs/Reference/Server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 1af2057c8ee..2ce940020f7 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -674,7 +674,7 @@ The default configuration is explained in the const fastify = require('fastify')({ ajv: { customOptions: { - removeAdditional: 'all' // Refer to [ajv options](https://ajv.js.org/#options) + removeAdditional: 'all' // Refer to [ajv options](https://ajv.js.org/options.html#removeadditional) }, plugins: [ require('ajv-merge-patch'), From 3767bc262d17b2e9c39aaef7642d03571e7f358c Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 16 Nov 2022 13:45:12 +0000 Subject: [PATCH 0158/1295] Bumped v4.10.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index eb9ce97cda3..f3a0e6011f3 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.9.2' +const VERSION = '4.10.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index ed5c910b3f2..95d9107c4ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.9.2", + "version": "4.10.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 36d84bce78f2d4b2bc8255c1432160d49776e8fe Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Fri, 18 Nov 2022 10:25:58 +0100 Subject: [PATCH 0159/1295] fix node 19.1.0 port validation test (#4427) * fix node 19.1.0 port validation test * Apply suggestions from code review * check for ERR_SOCKET_BAD_PORT --- test/server.test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/server.test.js b/test/server.test.js index 59194914304..70dd5a915b1 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -36,19 +36,18 @@ test('listen should accept stringified number port', t => { test('listen should reject string port', async (t) => { t.plan(2) - const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) try { await fastify.listen({ port: 'hello-world' }) } catch (error) { - t.same(error.message, 'options.port should be >= 0 and < 65536. Received hello-world.') + t.equal(error.code, 'ERR_SOCKET_BAD_PORT') } try { await fastify.listen({ port: '1234hello' }) } catch (error) { - t.same(error.message, 'options.port should be >= 0 and < 65536. Received 1234hello.') + t.equal(error.code, 'ERR_SOCKET_BAD_PORT') } }) From 9c85bed1b511fd218ce1caf2fb59dc146ecb7aa4 Mon Sep 17 00:00:00 2001 From: Carlos Espa <43477095+Ceres6@users.noreply.github.com> Date: Fri, 18 Nov 2022 12:15:34 +0100 Subject: [PATCH 0160/1295] Add fastify-constraints to community plugins (#4428) * Add fastify-constraints to community plugins * fix md lint --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 5ec7cdb10f5..f80dcc82825 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -265,6 +265,8 @@ section. - [`fastify-cockroachdb`](https://github.com/alex-ppg/fastify-cockroachdb) Fastify plugin to connect to a CockroachDB PostgreSQL instance via the Sequelize ORM. +- [`fastify-constraints`](https://github.com/nearform/fastify-constraints) + Fastify plugin to add constraints to multiple routes - [`fastify-couchdb`](https://github.com/nigelhanlon/fastify-couchdb) Fastify plugin to add CouchDB support via [nano](https://github.com/apache/nano). - [`fastify-crud-generator`](https://github.com/beliven-it/fastify-crud-generator) From 9c1be2ab56cca0125b48666241dc4ff5338b69d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Nov 2022 12:06:37 +0000 Subject: [PATCH 0161/1295] build(deps-dev): bump @sinonjs/fake-timers from 9.1.2 to 10.0.0 (#4421) Bumps [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) from 9.1.2 to 10.0.0. - [Release notes](https://github.com/sinonjs/fake-timers/releases) - [Changelog](https://github.com/sinonjs/fake-timers/blob/main/CHANGELOG.md) - [Commits](https://github.com/sinonjs/fake-timers/compare/v9.1.2...v10.0.0) --- updated-dependencies: - dependency-name: "@sinonjs/fake-timers" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95d9107c4ae..2db6b921683 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "devDependencies": { "@fastify/pre-commit": "^2.0.2", "@sinclair/typebox": "^0.25.2", - "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/fake-timers": "^10.0.0", "@types/node": "^18.7.18", "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", From a8873ef924d51e84549facf63cf1153f8521362b Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 21 Nov 2022 15:33:51 +0100 Subject: [PATCH 0162/1295] add silent option to LogLevel (#4432) --- test/types/logger.test-d.ts | 1 + types/logger.d.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index 0607c632223..fc23d6a2cae 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -183,6 +183,7 @@ expectDeprecated({} as FastifyLoggerInstance) const childParent = fastify().log // we test different option variant here expectType(childParent.child({}, { level: 'info' })) +expectType(childParent.child({}, { level: 'silent' })) expectType(childParent.child({}, { redact: ['pass', 'pin'] })) expectType(childParent.child({}, { serializers: { key: () => {} } })) expectType(childParent.child({}, { level: 'info', redact: ['pass', 'pin'], serializers: { key: () => {} } })) diff --git a/types/logger.d.ts b/types/logger.d.ts index 5fccf5b9c19..fb2ce80efce 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -13,7 +13,7 @@ import pino from 'pino' */ export type FastifyLogFn = pino.LogFn -export type LogLevel = pino.Level +export type LogLevel = pino.LevelWithSilent export type Bindings = pino.Bindings From 6fc06c12c5021cf41ce632bcc902ad66637d15b3 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 21 Nov 2022 15:35:51 +0100 Subject: [PATCH 0163/1295] Bumped v4.10.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index f3a0e6011f3..7bb32986de4 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.10.0' +const VERSION = '4.10.1' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 2db6b921683..386b3fdb169 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.10.0", + "version": "4.10.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 62dde76f1f7aca76e38625fe8d983761f26e6fc9 Mon Sep 17 00:00:00 2001 From: KaKa Date: Mon, 21 Nov 2022 22:39:50 +0800 Subject: [PATCH 0164/1295] Merge pull request from GHSA-3fjj-p79j-c9hh * fix: content-type spoofing * refactor: improve performance --- lib/contentTypeParser.js | 81 +++++++++++++- package.json | 1 + test/content-parser.test.js | 214 ++++++++++++++++++++++++++++++++++++ test/custom-parser.test.js | 6 +- 4 files changed, 293 insertions(+), 9 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 3326e192c3c..b32cc098802 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -2,6 +2,8 @@ const { AsyncResource } = require('async_hooks') const lru = require('tiny-lru').lru +// TODO: find more perforamant solution +const { parse: parseContentType } = require('content-type') const secureJson = require('secure-json-parse') const { @@ -33,7 +35,7 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) this.customParsers = new Map() this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse])) this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser)) - this.parserList = ['application/json', 'text/plain'] + this.parserList = [new ParserListItem('application/json'), new ParserListItem('text/plain')] this.parserRegExpList = [] this.cache = lru(100) } @@ -66,7 +68,7 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { this.customParsers.set('', parser) } else { if (contentTypeIsString) { - this.parserList.unshift(contentType) + this.parserList.unshift(new ParserListItem(contentType)) } else { this.parserRegExpList.unshift(contentType) } @@ -97,11 +99,20 @@ ContentTypeParser.prototype.getParser = function (contentType) { const parser = this.cache.get(contentType) if (parser !== undefined) return parser + const parsed = safeParseContentType(contentType) + + // dummyContentType always the same object + // we can use === for the comparsion and return early + if (parsed === dummyContentType) { + return this.customParsers.get('') + } + // eslint-disable-next-line no-var for (var i = 0; i !== this.parserList.length; ++i) { - const parserName = this.parserList[i] - if (contentType.indexOf(parserName) !== -1) { - const parser = this.customParsers.get(parserName) + const parserListItem = this.parserList[i] + if (compareContentType(parsed, parserListItem)) { + const parser = this.customParsers.get(parserListItem.name) + // we set request content-type in cache to reduce parsing of MIME type this.cache.set(contentType, parser) return parser } @@ -110,8 +121,9 @@ ContentTypeParser.prototype.getParser = function (contentType) { // eslint-disable-next-line no-var for (var j = 0; j !== this.parserRegExpList.length; ++j) { const parserRegExp = this.parserRegExpList[j] - if (parserRegExp.test(contentType)) { + if (compareRegExpContentType(contentType, parsed.type, parserRegExp)) { const parser = this.customParsers.get(parserRegExp.toString()) + // we set request content-type in cache to reduce parsing of MIME type this.cache.set(contentType, parser) return parser } @@ -346,6 +358,63 @@ function removeAllContentTypeParsers () { this[kContentTypeParser].removeAll() } +// dummy here to prevent repeated object creation +const dummyContentType = { type: '', parameters: Object.create(null) } + +function safeParseContentType (contentType) { + try { + return parseContentType(contentType) + } catch (err) { + return dummyContentType + } +} + +function compareContentType (contentType, parserListItem) { + if (parserListItem.isEssence) { + // we do essence check + return contentType.type.indexOf(parserListItem) !== -1 + } else { + // when the content-type includes parameters + // we do a full-text search + // reject essence content-type before checking parameters + if (contentType.type.indexOf(parserListItem.type) === -1) return false + for (const key of parserListItem.parameterKeys) { + // reject when missing parameters + if (!(key in contentType.parameters)) return false + // reject when parameters do not match + if (contentType.parameters[key] !== parserListItem.parameters[key]) return false + } + return true + } +} + +function compareRegExpContentType (contentType, essenceMIMEType, regexp) { + if (regexp.source.indexOf(';') === -1) { + // we do essence check + return regexp.test(essenceMIMEType) + } else { + // when the content-type includes parameters + // we do a full-text match + return regexp.test(contentType) + } +} + +function ParserListItem (contentType) { + this.name = contentType + // we pre-calculate all the needed information + // before content-type comparsion + const parsed = safeParseContentType(contentType) + this.type = parsed.type + this.parameters = parsed.parameters + this.parameterKeys = Object.keys(parsed.parameters) + this.isEssence = contentType.indexOf(';') === -1 +} + +// used in ContentTypeParser.remove +ParserListItem.prototype.toString = function () { + return this.name +} + module.exports = ContentTypeParser module.exports.helpers = { buildContentTypeParser, diff --git a/package.json b/package.json index 386b3fdb169..d213dd995a9 100644 --- a/package.json +++ b/package.json @@ -176,6 +176,7 @@ "@fastify/fast-json-stringify-compiler": "^4.1.0", "abstract-logging": "^2.0.1", "avvio": "^8.2.0", + "content-type": "^1.0.4", "find-my-way": "^7.3.0", "light-my-request": "^5.6.1", "pino": "^8.5.0", diff --git a/test/content-parser.test.js b/test/content-parser.test.js index fd24d195f6f..edf7d18b128 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -395,3 +395,217 @@ test('Safeguard against malicious content-type / 3', async t => { t.same(response.statusCode, 415) }) + +test('Safeguard against content-type spoofing - string', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser('text/plain', function (request, body, done) { + t.pass('should be called') + done(null, body) + }) + fastify.addContentTypeParser('application/json', function (request, body, done) { + t.fail('shouldn\'t be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'text/plain; content-type="application/json"' + }, + body: '' + }) +}) + +test('Safeguard against content-type spoofing - regexp', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser(/text\/plain/, function (request, body, done) { + t.pass('should be called') + done(null, body) + }) + fastify.addContentTypeParser(/application\/json/, function (request, body, done) { + t.fail('shouldn\'t be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'text/plain; content-type="application/json"' + }, + body: '' + }) +}) + +test('content-type match parameters - string 1', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser('text/plain; charset=utf8', function (request, body, done) { + t.fail('shouldn\'t be called') + done(null, body) + }) + fastify.addContentTypeParser('application/json; charset=utf8', function (request, body, done) { + t.pass('should be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; charset=utf8' + }, + body: '' + }) +}) + +test('content-type match parameters - string 2', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { + t.pass('should be called') + done(null, body) + }) + fastify.addContentTypeParser('text/plain; charset=utf8; foo=bar', function (request, body, done) { + t.fail('shouldn\'t be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; foo=bar; charset=utf8' + }, + body: '' + }) +}) + +test('content-type match parameters - regexp', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser(/application\/json; charset=utf8/, function (request, body, done) { + t.pass('should be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; charset=utf8' + }, + body: '' + }) +}) + +test('content-type fail when parameters not match - string 1', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { + t.fail('shouldn\'t be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + const response = await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; charset=utf8' + }, + body: '' + }) + + t.same(response.statusCode, 415) +}) + +test('content-type fail when parameters not match - string 2', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { + t.fail('shouldn\'t be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + const response = await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; charset=utf8; foo=baz' + }, + body: '' + }) + + t.same(response.statusCode, 415) +}) + +test('content-type fail when parameters not match - regexp', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser(/application\/json; charset=utf8; foo=bar/, function (request, body, done) { + t.fail('shouldn\'t be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + const response = await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; charset=utf8' + }, + body: '' + }) + + t.same(response.statusCode, 415) +}) diff --git a/test/custom-parser.test.js b/test/custom-parser.test.js index 8986216afba..a669c32189e 100644 --- a/test/custom-parser.test.js +++ b/test/custom-parser.test.js @@ -1053,7 +1053,7 @@ test('The charset should not interfere with the content type handling', t => { url: getUrl(fastify), body: '{"hello":"world"}', headers: { - 'Content-Type': 'application/json charset=utf-8' + 'Content-Type': 'application/json; charset=utf-8' } }, (err, response, body) => { t.error(err) @@ -1236,7 +1236,7 @@ test('contentTypeParser should add a custom parser with RegExp value', t => { url: getUrl(fastify), body: '{"hello":"world"}', headers: { - 'Content-Type': 'weird-content-type+json' + 'Content-Type': 'weird/content-type+json' } }, (err, response, body) => { t.error(err) @@ -1266,7 +1266,7 @@ test('contentTypeParser should add multiple custom parsers with RegExp values', done(null, 'xml') }) - fastify.addContentTypeParser(/.*\+myExtension$/, function (req, payload, done) { + fastify.addContentTypeParser(/.*\+myExtension$/i, function (req, payload, done) { let data = '' payload.on('data', chunk => { data += chunk }) payload.on('end', () => { From 311590c147af90e32f7bcbcc10e0d176cbbafaca Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 21 Nov 2022 15:41:03 +0100 Subject: [PATCH 0165/1295] Bumped 4.10.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 7bb32986de4..ba1c406783e 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.10.1' +const VERSION = '4.10.2' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index d213dd995a9..6452874a3ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.10.1", + "version": "4.10.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From df5d94f541c50f690e8beebd143fdb3d861d991b Mon Sep 17 00:00:00 2001 From: Marco Reni Date: Sat, 26 Nov 2022 21:07:39 +0100 Subject: [PATCH 0166/1295] fix: use generic for Logger to register plugins when using a custom logger (#4435) (#4436) --- test/types/register.test-d.ts | 43 +++++++++++++++++++++++++++++++++++ types/plugin.d.ts | 12 +++++++--- types/register.d.ts | 16 ++++++------- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index 8cfd6b07117..3519d3c3b49 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -101,3 +101,46 @@ expectAssignable(serverWithTypeProvider.register(async ( expectAssignable(serverWithTypeProvider.register(async (instance: ServerWithTypeProvider) => { expectAssignable(instance) })) + +// With Type Provider and logger +const customLogger = { + level: 'info', + info: () => { }, + warn: () => { }, + error: () => { }, + fatal: () => { }, + trace: () => { }, + debug: () => { }, + child: () => customLogger, + silent: () => { } +} +const serverWithTypeProviderAndLogger = fastify({ + logger: customLogger +}).withTypeProvider() +type ServerWithTypeProviderAndLogger = FastifyInstance +const testPluginWithTypeProviderAndLogger: FastifyPluginCallback = function (instance, opts, done) { } +const testPluginWithTypeProviderAndLoggerAsync: FastifyPluginAsync = async function (instance, opts) { } +const testPluginWithTypeProviderAndLoggerWithType = (instance: ServerWithTypeProviderAndLogger, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { } +const testPluginWithTypeProviderAndLoggerWithTypeAsync = async (instance: ServerWithTypeProviderAndLogger, opts: FastifyPluginOptions) => { } +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginCallback)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginAsync)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOpts)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsAsync)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsWithType)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsWithTypeAsync)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithType)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithTypeAsync)) +expectAssignable(serverWithTypeProviderAndLogger.register((instance) => { + expectAssignable(instance) +})) +expectAssignable(serverWithTypeProviderAndLogger.register((instance: ServerWithTypeProviderAndLogger) => { + expectAssignable(instance) +})) +expectAssignable(serverWithTypeProviderAndLogger.register(async (instance) => { + expectAssignable(instance) +})) +expectAssignable(serverWithTypeProviderAndLogger.register(async (instance: ServerWithTypeProviderAndLogger) => { + expectAssignable(instance) +})) diff --git a/types/plugin.d.ts b/types/plugin.d.ts index 77bce4e1182..74c0a2b49f9 100644 --- a/types/plugin.d.ts +++ b/types/plugin.d.ts @@ -10,8 +10,13 @@ export type FastifyPluginOptions = Record * * 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. */ -export type FastifyPluginCallback, Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> = ( - instance: FastifyInstance, RawReplyDefaultExpression, FastifyBaseLogger, TypeProvider>, +export type FastifyPluginCallback< + Options extends FastifyPluginOptions = Record, + Server extends RawServerBase = RawServerDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, +> = ( + instance: FastifyInstance, RawReplyDefaultExpression, Logger, TypeProvider>, opts: Options, done: (err?: Error) => void ) => void @@ -25,8 +30,9 @@ export type FastifyPluginAsync< Options extends FastifyPluginOptions = Record, Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, > = ( - instance: FastifyInstance, RawReplyDefaultExpression, FastifyBaseLogger, TypeProvider>, + instance: FastifyInstance, RawReplyDefaultExpression, Logger, TypeProvider>, opts: Options ) => Promise; diff --git a/types/register.d.ts b/types/register.d.ts index 5cca479df16..de274836d66 100644 --- a/types/register.d.ts +++ b/types/register.d.ts @@ -2,7 +2,7 @@ import { FastifyPluginOptions, FastifyPluginCallback, FastifyPluginAsync } from import { LogLevel } from './logger' import { FastifyInstance } from './instance' import { RawServerBase } from './utils' -import { FastifyTypeProvider, RawServerDefault } from '../fastify' +import { FastifyBaseLogger, FastifyTypeProvider, RawServerDefault } from '../fastify' export interface RegisterOptions { prefix?: string; @@ -17,17 +17,17 @@ export type FastifyRegisterOptions = (RegisterOptions & Options) | ((in * * Function for adding a plugin to fastify. The options are inferred from the passed in FastifyPlugin parameter. */ -export interface FastifyRegister { - ( - plugin: FastifyPluginCallback, +export interface FastifyRegister { + ( + plugin: FastifyPluginCallback, opts?: FastifyRegisterOptions ): T; - ( - plugin: FastifyPluginAsync, + ( + plugin: FastifyPluginAsync, opts?: FastifyRegisterOptions ): T; - ( - plugin: FastifyPluginCallback | FastifyPluginAsync | Promise<{ default: FastifyPluginCallback }> | Promise<{ default: FastifyPluginAsync }>, + ( + plugin: FastifyPluginCallback | FastifyPluginAsync | Promise<{ default: FastifyPluginCallback }> | Promise<{ default: FastifyPluginAsync }>, opts?: FastifyRegisterOptions ): T; } From 204be5f85720ffdaf83e18071b819c56878d6c36 Mon Sep 17 00:00:00 2001 From: Saumya <76432998+SaumyaBhushan@users.noreply.github.com> Date: Tue, 29 Nov 2022 22:02:24 +0530 Subject: [PATCH 0167/1295] Incorrect example in default text parser docs (#4448) --- docs/Reference/Server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 2ce940020f7..de8854bc35f 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1670,7 +1670,7 @@ information. `fastify.defaultTextParser()` can be used to parse content as plain text. ```js -fastify.addContentTypeParser('text/json', { asString: true }, fastify.defaultTextParser()) +fastify.addContentTypeParser('text/json', { asString: true }, fastify.defaultTextParser) ``` #### errorHandler From c0686975c9cc72d0f1923428d7fed8afa5d2f1d5 Mon Sep 17 00:00:00 2001 From: nlf Date: Tue, 29 Nov 2022 11:54:45 -0800 Subject: [PATCH 0168/1295] chore: fix test skips for nodejs prereleases (#4449) --- test/close-pipelining.test.js | 2 +- test/close.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index 3f4b273b83e..7c952e83847 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -36,7 +36,7 @@ test('Should return 503 while closing - pipelining', async t => { await instance.close() }) -const isV19plus = semver.satisfies(process.version, '>= v19.0.0') +const isV19plus = semver.gte(process.version, '19.0.0') test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, async t => { const fastify = Fastify({ return503OnClosing: false, diff --git a/test/close.test.js b/test/close.test.js index b0c78877ff8..84a7a3d8404 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -203,7 +203,7 @@ test('Should return error while closing (callback) - injection', t => { }) }) -const isV19plus = semver.satisfies(process.version, '>= v19.0.0') +const isV19plus = semver.gte(process.version, '19.0.0') 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 => { const fastify = Fastify({ return503OnClosing: false, From f4843b4fd9a35f187c931e7efe61ad17c94fe67a Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Wed, 30 Nov 2022 12:45:17 -0700 Subject: [PATCH 0169/1295] Move @Ethan-Arrowood to Past Collaborator section (#4451) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d49324b2b3a..227269693b3 100644 --- a/README.md +++ b/README.md @@ -289,8 +289,6 @@ listed in alphabetical order. ### Fastify Core team * [__Tommaso Allevi__](https://github.com/allevo), , -* [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/), - , * [__Harry Brundage__](https://github.com/airhorns/), , * [__David Mark Clements__](https://github.com/davidmarkclements), @@ -362,6 +360,8 @@ to join this group by Lead Maintainers. , * [__Nathan Woltman__](https://github.com/nwoltman), , +* [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/), + , ## Hosted by From 089323ca868949607ac19c5081fe79b1861fe74a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Dec 2022 14:14:49 +0000 Subject: [PATCH 0170/1295] build(deps): bump lycheeverse/lychee-action from 1.5.1 to 1.5.4 (#4454) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.5.1 to 1.5.4. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/4a5af7cd2958a2282cefbd9c10f63bdb89982d76...4dcb8bee2a0a4531cba1a1f392c54e8375d6dd81) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 5d07926e8a4..5d30204111d 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@4a5af7cd2958a2282cefbd9c10f63bdb89982d76 + uses: lycheeverse/lychee-action@4dcb8bee2a0a4531cba1a1f392c54e8375d6dd81 with: fail: true # As external links behaviour is not predictable, we check only internal links From 0f6328250ef2c4c94fdeeccc914f725b3bfdf51d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Dec 2022 14:26:26 +0000 Subject: [PATCH 0171/1295] build(deps): bump actions/dependency-review-action from 2 to 3 (#4455) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 2 to 3. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24cc39ad708..ef8ec39567b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Dependency review - uses: actions/dependency-review-action@v2 + uses: actions/dependency-review-action@v3 linter: runs-on: ubuntu-latest From 660eea97b094de11d2619d711c1313fb3caba567 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 14:16:24 +0000 Subject: [PATCH 0172/1295] build(deps-dev): bump tsd from 0.24.1 to 0.25.0 (#4460) Bumps [tsd](https://github.com/SamVerschueren/tsd) from 0.24.1 to 0.25.0. - [Release notes](https://github.com/SamVerschueren/tsd/releases) - [Commits](https://github.com/SamVerschueren/tsd/compare/v0.24.1...v0.25.0) --- updated-dependencies: - dependency-name: tsd dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6452874a3ba..db5bcba1937 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,7 @@ "split2": "^4.1.0", "standard": "^17.0.0", "tap": "^16.3.0", - "tsd": "^0.24.1", + "tsd": "^0.25.0", "typescript": "^4.8.3", "undici": "^5.10.0", "vary": "^1.1.2", From cf5bbf8e820d3529dbc116e69a6f67fbde4e0758 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 13 Dec 2022 14:26:59 +0100 Subject: [PATCH 0173/1295] docs: add fastify-user-agent (#4466) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f80dcc82825..f769926dc33 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -565,6 +565,8 @@ section. for [zod](https://github.com/colinhacks/zod). - [`fastify-typeorm-plugin`](https://github.com/inthepocket/fastify-typeorm-plugin) Fastify plugin to work with TypeORM. +- [`fastify-user-agent`](https://github.com/Eomm/fastify-user-agent) parses your + request's `user-agent` header. - [`fastify-vhost`](https://github.com/patrickpissurno/fastify-vhost) Proxy subdomain HTTP requests to another server (useful if you want to point multiple subdomains to the same IP address, while running different servers on From 1651e0b8f59bd62213c9717d9184d125ed6d1514 Mon Sep 17 00:00:00 2001 From: Mateo Nunez Date: Sun, 18 Dec 2022 15:58:22 +0100 Subject: [PATCH 0174/1295] chore(ecosystem): rename fastify-lyra plugin (#4474) --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f769926dc33..53bcea2a191 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -387,6 +387,9 @@ section. - [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua scripts with [fastify-redis](https://github.com/fastify/fastify-redis) and [lured](https://github.com/enobufs/lured). +- [`fastify-lyra`](https://github.com/mateonunez/fastify-lyra) + A plugin to implement [Lyra](https://github.com/LyraSearch/lyra) search engine + on Fastify. - [`fastify-mailer`](https://github.com/coopflow/fastify-mailer) Plugin to initialize and encapsulate [Nodemailer](https://nodemailer.com)'s transporters instances in Fastify. From f0455e73f3707888b5eeaa4e831a32932863d150 Mon Sep 17 00:00:00 2001 From: Mateo Nunez Date: Sun, 18 Dec 2022 19:11:41 +0100 Subject: [PATCH 0175/1295] docs(ecosystem): add fastify-at-mysql plugin (#4473) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 53bcea2a191..746fe93bc3c 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -220,6 +220,8 @@ section. plugin to authenticate HTTP requests based on api key and signature - [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify Plugin for interacting with Appwrite server. +- [`fastify-at-mysql`](https://github.com/mateonunez/fastify-at-mysql) Fastify + MySQL plugin with auto SQL injection attack prevention. - [`fastify-auth0-verify`](https://github.com/nearform/fastify-auth0-verify): Auth0 verification plugin for Fastify, internally uses [fastify-jwt](https://npm.im/fastify-jwt) and From 35e7c1acfa654aa4b1b893d5fe1eafbaf49ca3c5 Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov <39828645+polRk@users.noreply.github.com> Date: Mon, 19 Dec 2022 17:08:39 +0300 Subject: [PATCH 0176/1295] fix: make res.statusCode optional (#4471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: make res.statusCode optional * test: add tests Co-authored-by: Поляков Владислав Михайлович --- test/types/logger.test-d.ts | 15 +++++++++++++++ types/logger.d.ts | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index fc23d6a2cae..10a570a1ba3 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -123,6 +123,21 @@ const serverAutoInferredFileOption = fastify({ expectType(serverAutoInferredFileOption.log) +const serverAutoInferredSerializerResponseObjectOption = fastify({ + logger: { + serializers: { + res (ServerResponse) { + expectType(ServerResponse) + return { + status: '200' + } + } + } + } +}) + +expectType(serverAutoInferredSerializerResponseObjectOption.log) + const serverAutoInferredSerializerObjectOption = fastify({ logger: { serializers: { diff --git a/types/logger.d.ts b/types/logger.d.ts index fb2ce80efce..5ae6393e36c 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -60,7 +60,7 @@ export interface FastifyLoggerOptions< [key: string]: unknown; }; res?: (res: RawReply) => { - statusCode: string | number; + statusCode?: string | number; [key: string]: unknown; }; }; From 4bde0e0b259b8df6495ae93c91e06615b61be9c6 Mon Sep 17 00:00:00 2001 From: Mateo Nunez Date: Tue, 20 Dec 2022 16:09:22 +0100 Subject: [PATCH 0177/1295] docs(ecosystem): add fastify-at-postgres plugin (#4475) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 746fe93bc3c..0423ce54075 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -222,6 +222,8 @@ section. Plugin for interacting with Appwrite server. - [`fastify-at-mysql`](https://github.com/mateonunez/fastify-at-mysql) Fastify MySQL plugin with auto SQL injection attack prevention. +- [`fastify-at-postgres`](https://github.com/mateonunez/fastify-at-postgres) Fastify + Postgres plugin with auto SQL injection attack prevention. - [`fastify-auth0-verify`](https://github.com/nearform/fastify-auth0-verify): Auth0 verification plugin for Fastify, internally uses [fastify-jwt](https://npm.im/fastify-jwt) and From 897360ad9a01340b45d94f001d8c46aeee22581f Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sat, 24 Dec 2022 09:42:19 +0100 Subject: [PATCH 0178/1295] perf: only check for isEssence once in RegExp for content-type-parser (#4481) --- lib/contentTypeParser.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index b32cc098802..d4eb906b481 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -70,6 +70,7 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { if (contentTypeIsString) { this.parserList.unshift(new ParserListItem(contentType)) } else { + contentType.isEssence = contentType.source.indexOf(';') === -1 this.parserRegExpList.unshift(contentType) } this.customParsers.set(contentType.toString(), parser) @@ -389,7 +390,7 @@ function compareContentType (contentType, parserListItem) { } function compareRegExpContentType (contentType, essenceMIMEType, regexp) { - if (regexp.source.indexOf(';') === -1) { + if (regexp.isEssence) { // we do essence check return regexp.test(essenceMIMEType) } else { From dbef64289e0812749ed44f89658609c49fa0d696 Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Fri, 23 Dec 2022 15:01:16 -0300 Subject: [PATCH 0179/1295] doc: improve setDefaultRoute documentation --- docs/Reference/Server.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index de8854bc35f..c0f3b80f422 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1024,13 +1024,20 @@ const defaultRoute = fastify.getDefaultRoute() **Note**: The default 404 handler, or one set using `setNotFoundHandler`, will -never trigger if the default route is overridden. This sets the handler for the +never trigger if the default route is overridden. This sets the handler for the Fastify application, not just the current instance context. Use [setNotFoundHandler](#setnotfoundhandler) if you want to customize 404 handling -instead. Method to set the `defaultRoute` for the server: +instead. + +This method set the `defaultRoute` for the server. Note that, its purpose is +to interact with the underlying raw requests. Unlike other Fastify handlers, the +arguments received are from type [RawRequest](./TypeScript.md#rawrequest) and +[RawReply](./TypeScript.md#rawreply) respectively. ```js const defaultRoute = function (req, res) { + // req = RawRequest + // res = RawReply res.end('hello world') } From fc81c707b819894bd84a903e5bbd23ec121e5a7b Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Mon, 26 Dec 2022 14:05:20 -0300 Subject: [PATCH 0180/1295] lib: deprecate fastify default route --- docs/Reference/Server.md | 13 ++++++++--- lib/route.js | 2 ++ lib/warnings.js | 2 ++ test/default-route.test.js | 45 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index c0f3b80f422..27fa1294c4e 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1011,6 +1011,9 @@ Note that the array contains the `fastify.server.address()` too. #### getDefaultRoute +**Notice**: this method is deprecated and should be removed in the next Fastify +major version. + The `defaultRoute` handler handles requests that do not match any URL specified by your Fastify application. This defaults to the 404 handler, but can be overridden with [setDefaultRoute](#setdefaultroute). Method to get the @@ -1023,15 +1026,19 @@ const defaultRoute = fastify.getDefaultRoute() #### setDefaultRoute -**Note**: The default 404 handler, or one set using `setNotFoundHandler`, will +**Notice**: this method is deprecated and should be removed in the next Fastify +major version. Please, consider to use `setNotFoundHandler` or a wildcard +matching route. + +The default 404 handler, or one set using `setNotFoundHandler`, will never trigger if the default route is overridden. This sets the handler for the Fastify application, not just the current instance context. Use [setNotFoundHandler](#setnotfoundhandler) if you want to customize 404 handling instead. -This method set the `defaultRoute` for the server. Note that, its purpose is +This method sets the `defaultRoute` for the server. Note that, its purpose is to interact with the underlying raw requests. Unlike other Fastify handlers, the -arguments received are from type [RawRequest](./TypeScript.md#rawrequest) and +arguments received are of type [RawRequest](./TypeScript.md#rawrequest) and [RawReply](./TypeScript.md#rawreply) respectively. ```js diff --git a/lib/route.js b/lib/route.js index f48467de881..ae659c5bd04 100644 --- a/lib/route.js +++ b/lib/route.js @@ -89,9 +89,11 @@ function buildRouting (options) { hasRoute, prepareRoute, getDefaultRoute: function () { + warning.emit('FSTDEP014') return router.defaultRoute }, setDefaultRoute: function (defaultRoute) { + warning.emit('FSTDEP014') if (typeof defaultRoute !== 'function') { throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE() } diff --git a/lib/warnings.js b/lib/warnings.js index 25fe27be8a5..3105f1e8558 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -25,4 +25,6 @@ warning.create('FastifyDeprecation', 'FSTDEP012', 'Request#context property acce 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`.') +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.') + module.exports = warning diff --git a/test/default-route.test.js b/test/default-route.test.js index 64de3c259cd..4a8679e0701 100644 --- a/test/default-route.test.js +++ b/test/default-route.test.js @@ -3,6 +3,51 @@ const t = require('tap') const test = t.test const Fastify = require('..') +const warning = require('../lib/warnings') + +// Silence the standard warning logs. We will test the messages explicitly. +process.removeAllListeners('warning') + +test('setDefaultRoute should emit a deprecation warning', t => { + t.plan(2) + + const fastify = Fastify() + const defaultRoute = (req, res) => { + res.end('hello from defaultRoute') + } + + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.code, 'FSTDEP014') + } + + t.teardown(() => { + process.removeListener('warning', onWarning) + warning.emitted.set('FSTDEP014', false) + }) + + fastify.setDefaultRoute(defaultRoute) +}) + +test('getDefaultRoute should emit a deprecation warning', t => { + t.plan(2) + + const fastify = Fastify() + + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.code, 'FSTDEP014') + } + + t.teardown(() => { + process.removeListener('warning', onWarning) + warning.emitted.set('FSTDEP014', false) + }) + + fastify.getDefaultRoute() +}) test('should fail if defaultRoute is not a function', t => { t.plan(1) From 003eae651861ffff9f8a8910039a4577175d6979 Mon Sep 17 00:00:00 2001 From: Tikkhun Date: Wed, 28 Dec 2022 05:36:28 +0800 Subject: [PATCH 0181/1295] docs(reference/reply): When using async-await, need return (#4429) * docs(reference/reply): When using async-await, please return the reply object to signal fastify wait for your further response. * docs(reference/reply): When using async-await, please return the reply object to signal fastify wait for your further response. * Update docs/Reference/Reply.md Co-authored-by: Frazer Smith * Update docs/Reference/Reply.md Co-authored-by: Frazer Smith * Apply suggestions from code review Co-authored-by: Andrey Chalkin * docs: return or await reply when async-await Co-authored-by: Frazer Smith Co-authored-by: Andrey Chalkin --- docs/Reference/Reply.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index b5d690ae83c..0f8e99db793 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -674,6 +674,15 @@ fastify.get('/streams', function (request, reply) { reply.send(stream) }) ``` +When using async-await you will need to return or await the reply object: +```js +fastify.get('/streams', async function (request, reply) { + const fs = require('fs') + const stream = fs.createReadStream('some-file', 'utf8') + reply.header('Content-Type', 'application/octet-stream') + return reply.send(stream) +}) +``` #### Buffers @@ -689,6 +698,16 @@ fastify.get('/streams', function (request, reply) { }) ``` +When using async-await you will need to return or await the reply object: +```js +const fs = require('fs') +fastify.get('/streams', async function (request, reply) { + fs.readFile('some-file', (err, fileBuffer) => { + reply.send(err || fileBuffer) + }) + return reply +}) +``` #### Errors From b8e201a077dfa27a9c472d1573cf81cdbd0e50f9 Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Tue, 27 Dec 2022 19:36:28 -0300 Subject: [PATCH 0182/1295] lib: drop setDefaultRoute and getDefaultRoute methods Signed-off-by: RafaelGSS --- docs/Reference/Errors.md | 7 +----- docs/Reference/Server.md | 31 ------------------------- fastify.js | 2 -- lib/errors.js | 6 ----- lib/route.js | 11 --------- test/default-route.test.js | 43 ----------------------------------- test/types/instance.test-d.ts | 2 -- types/errors.d.ts | 1 - types/instance.d.ts | 4 +--- 9 files changed, 2 insertions(+), 105 deletions(-) delete mode 100644 test/default-route.test.js diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 695c3ed1718..054b1eb5de0 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -334,11 +334,6 @@ The router received an invalid url. The router received an error when using asynchronous constraints. -#### FST_ERR_DEFAULT_ROUTE_INVALID_TYPE - - -The `defaultRoute` type should be a function. - #### FST_ERR_INVALID_URL @@ -382,4 +377,4 @@ Impossible to load plugin because the parent (mapped directly from `avvio`) #### FST_ERR_PLUGIN_TIMEOUT -Plugin did not start in time. Default timeout (in millis): `10000` \ No newline at end of file +Plugin did not start in time. Default timeout (in millis): `10000` diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index de8854bc35f..09e31896872 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -50,8 +50,6 @@ describes the properties available in that options object. - [ready](#ready) - [listen](#listen) - [addresses](#addresses) - - [getDefaultRoute](#getdefaultroute) - - [setDefaultRoute](#setdefaultroute) - [routing](#routing) - [route](#route) - [hasRoute](#hasRoute) @@ -1008,35 +1006,6 @@ const addresses = fastify.addresses() Note that the array contains the `fastify.server.address()` too. -#### getDefaultRoute - - -The `defaultRoute` handler handles requests that do not match any URL specified -by your Fastify application. This defaults to the 404 handler, but can be -overridden with [setDefaultRoute](#setdefaultroute). Method to get the -`defaultRoute` for the server: - -```js -const defaultRoute = fastify.getDefaultRoute() -``` - -#### setDefaultRoute - - -**Note**: The default 404 handler, or one set using `setNotFoundHandler`, will -never trigger if the default route is overridden. This sets the handler for the -Fastify application, not just the current instance context. Use -[setNotFoundHandler](#setnotfoundhandler) if you want to customize 404 handling -instead. Method to set the `defaultRoute` for the server: - -```js -const defaultRoute = function (req, res) { - res.end('hello world') -} - -fastify.setDefaultRoute(defaultRoute) -``` - #### routing diff --git a/fastify.js b/fastify.js index 5cf0e8f0d85..033329d7c2c 100644 --- a/fastify.js +++ b/fastify.js @@ -239,8 +239,6 @@ function fastify (options) { [kAvvioBoot]: null, // routing method routing: httpHandler, - getDefaultRoute: router.getDefaultRoute.bind(router), - setDefaultRoute: router.setDefaultRoute.bind(router), // routes shorthand methods delete: function _delete (url, options, handler) { return router.prepareRoute.call(this, { method: 'DELETE', url, options, handler }) diff --git a/lib/errors.js b/lib/errors.js index 3ef8e568a4f..e3621982072 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -245,12 +245,6 @@ const codes = { 'Unexpected error from async constraint', 500 ), - FST_ERR_DEFAULT_ROUTE_INVALID_TYPE: createError( - 'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE', - 'The defaultRoute type should be a function', - 500, - TypeError - ), FST_ERR_INVALID_URL: createError( 'FST_ERR_INVALID_URL', "URL must be a string. Received '%s'", diff --git a/lib/route.js b/lib/route.js index f48467de881..281565b10c7 100644 --- a/lib/route.js +++ b/lib/route.js @@ -17,7 +17,6 @@ const { const { FST_ERR_SCH_VALIDATION_BUILD, FST_ERR_SCH_SERIALIZATION_BUILD, - FST_ERR_DEFAULT_ROUTE_INVALID_TYPE, FST_ERR_DUPLICATED_ROUTE, FST_ERR_INVALID_URL, FST_ERR_SEND_UNDEFINED_ERR, @@ -88,16 +87,6 @@ function buildRouting (options) { route, // configure a route in the fastify instance hasRoute, prepareRoute, - getDefaultRoute: function () { - return router.defaultRoute - }, - setDefaultRoute: function (defaultRoute) { - if (typeof defaultRoute !== 'function') { - throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE() - } - - router.defaultRoute = defaultRoute - }, routeHandler, closeRoutes: () => { closing = true }, printRoutes: router.prettyPrint.bind(router), diff --git a/test/default-route.test.js b/test/default-route.test.js deleted file mode 100644 index 64de3c259cd..00000000000 --- a/test/default-route.test.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict' - -const t = require('tap') -const test = t.test -const Fastify = require('..') - -test('should fail if defaultRoute is not a function', t => { - t.plan(1) - - const fastify = Fastify() - const defaultRoute = {} - - fastify.get('/', () => {}) - - try { - fastify.setDefaultRoute(defaultRoute) - } catch (error) { - t.equal(error.code, 'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE') - } -}) - -test('correctly sets, returns, and calls defaultRoute', t => { - t.plan(3) - - const fastify = Fastify() - const defaultRoute = (req, res) => { - res.end('hello from defaultRoute') - } - - fastify.setDefaultRoute(defaultRoute) - const returnedDefaultRoute = fastify.getDefaultRoute() - t.equal(returnedDefaultRoute, defaultRoute) - - fastify.get('/', () => {}) - - fastify.inject({ - method: 'GET', - url: '/random' - }, (err, res) => { - t.error(err) - t.equal(res.body, 'hello from defaultRoute') - }) -}) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index eadd16443f1..ad870f1709b 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -324,7 +324,5 @@ const versionConstraintStrategy = { expectType(server.addConstraintStrategy(versionConstraintStrategy)) expectType(server.hasConstraintStrategy(versionConstraintStrategy.name)) -expectAssignable>(server.getDefaultRoute()) - expectType | undefined>(server.validatorCompiler) expectType | undefined>(server.serializerCompiler) diff --git a/types/errors.d.ts b/types/errors.d.ts index 170acfef33c..e193aa1de65 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -40,7 +40,6 @@ export type FastifyErrorCodes = Record< 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE' | 'FST_ERR_DUPLICATED_ROUTE' | 'FST_ERR_BAD_URL' | -'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE' | 'FST_ERR_INVALID_URL' | 'FST_ERR_REOPENED_CLOSE_SERVER' | 'FST_ERR_REOPENED_SERVER' | diff --git a/types/instance.d.ts b/types/instance.d.ts index d192b63c5e2..77056839b7e 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -8,7 +8,7 @@ import { FastifyBaseLogger } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' import { FastifyRequest } from './request' -import { DefaultRoute, RouteGenericInterface, RouteOptions, RouteShorthandMethod } from './route' +import { RouteGenericInterface, RouteOptions, RouteShorthandMethod } from './route' import { FastifySchema, FastifySchemaCompiler, @@ -168,8 +168,6 @@ export interface FastifyInstance< register: FastifyRegister & PromiseLike>; routing(req: RawRequest, res: RawReply): void; - getDefaultRoute(): DefaultRoute; - setDefaultRoute(defaultRoute: DefaultRoute): void; route< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, From 10fd294dc78bcc6f862bc91dea12ef24f7980f36 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 30 Dec 2022 00:52:21 +0800 Subject: [PATCH 0183/1295] fix: re-thrown error crash with async handler and sync custom error handler (#4488) --- lib/wrapThenable.js | 8 +++++++- test/reply-error.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/wrapThenable.js b/lib/wrapThenable.js index 7cda5d59942..b746a62e920 100644 --- a/lib/wrapThenable.js +++ b/lib/wrapThenable.js @@ -35,7 +35,13 @@ function wrapThenable (thenable, reply) { } reply[kReplyIsError] = true - reply.send(err) + + // try-catch allow to re-throw error in error handler for async handler + try { + reply.send(err) + } catch (err) { + reply.send(err) + } }) } diff --git a/test/reply-error.test.js b/test/reply-error.test.js index c9ab206eea4..c4ee4fb3de1 100644 --- a/test/reply-error.test.js +++ b/test/reply-error.test.js @@ -514,6 +514,34 @@ test('error thrown by custom error handler routes to default error handler', t = }) }) +// Refs: https://github.com/fastify/fastify/pull/4484#issuecomment-1367301750 +test('allow re-thrown error to default error handler when route handler is async and error handler is sync', t => { + t.plan(4) + const fastify = Fastify() + + fastify.setErrorHandler(function (error) { + t.equal(error.message, 'kaboom') + throw Error('kabong') + }) + + fastify.get('/', async function () { + throw Error('kaboom') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 500) + t.same(JSON.parse(res.payload), { + error: statusCodes['500'], + message: 'kabong', + statusCode: 500 + }) + }) +}) + // Issue 2078 https://github.com/fastify/fastify/issues/2078 // Supported error code list: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml const invalidErrorCodes = [ From 6b65f2001c860521b1884e336ca4406b2c2ac8b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Jan 2023 14:13:24 +0000 Subject: [PATCH 0184/1295] build(deps): bump thollander/actions-comment-pull-request from 1 to 2 (#4489) Bumps [thollander/actions-comment-pull-request](https://github.com/thollander/actions-comment-pull-request) from 1 to 2. - [Release notes](https://github.com/thollander/actions-comment-pull-request/releases) - [Commits](https://github.com/thollander/actions-comment-pull-request/compare/v1...v2) --- updated-dependencies: - dependency-name: thollander/actions-comment-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d80490baeb6..b1ff0c2563b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -66,7 +66,7 @@ jobs: pull-requests: write steps: - name: Comment PR - uses: thollander/actions-comment-pull-request@v1 + uses: thollander/actions-comment-pull-request@v2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | From 0d7264c854edef78a846e7a59ea203b9fc22347e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Jan 2023 14:16:58 +0000 Subject: [PATCH 0185/1295] build(deps): bump xt0rted/markdownlint-problem-matcher (#4490) Bumps [xt0rted/markdownlint-problem-matcher](https://github.com/xt0rted/markdownlint-problem-matcher) from 1.1.0 to 2.0.0. - [Release notes](https://github.com/xt0rted/markdownlint-problem-matcher/releases) - [Changelog](https://github.com/xt0rted/markdownlint-problem-matcher/blob/main/CHANGELOG.md) - [Commits](https://github.com/xt0rted/markdownlint-problem-matcher/compare/b643b0751c371f357690337d4549221347c0e1bc...98d94724052d20ca2e06c091f202e4c66c3c59fb) --- updated-dependencies: - dependency-name: xt0rted/markdownlint-problem-matcher dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/md-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index b981705bec8..7e6c861ba86 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -37,7 +37,7 @@ jobs: run: npm install --ignore-scripts - name: Add Matcher - uses: xt0rted/markdownlint-problem-matcher@b643b0751c371f357690337d4549221347c0e1bc + uses: xt0rted/markdownlint-problem-matcher@98d94724052d20ca2e06c091f202e4c66c3c59fb - name: Run Linter run: ./node_modules/.bin/markdownlint-cli2 From 0557c0af871332f10389af32a21807756f642a86 Mon Sep 17 00:00:00 2001 From: Trim21 Date: Mon, 2 Jan 2023 06:33:21 +0800 Subject: [PATCH 0186/1295] improve `setErrorHandler` example (#4484) * improve setErrorHandler example * Update Errors.md * review suggestion * typo --- docs/Reference/Errors.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 695c3ed1718..f925490ec52 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -112,6 +112,9 @@ fastify.setErrorHandler(function (error, request, reply) { this.log.error(error) // Send error response reply.status(500).send({ ok: false }) + } else { + // fastify will use parent error handler to handle this + reply.send(error) } }) @@ -382,4 +385,4 @@ Impossible to load plugin because the parent (mapped directly from `avvio`) #### FST_ERR_PLUGIN_TIMEOUT -Plugin did not start in time. Default timeout (in millis): `10000` \ No newline at end of file +Plugin did not start in time. Default timeout (in millis): `10000` From 1b2b383e82a5c81e8188ded6c8a7e9ad2f3cbeaf Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 1 Jan 2023 22:35:33 +0000 Subject: [PATCH 0187/1295] Bumped v4.11.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index ba1c406783e..a8ecaa9bffd 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.10.2' +const VERSION = '4.11.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index db5bcba1937..82ea3e65b72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.10.2", + "version": "4.11.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 45a1df539d1c109549392e5433885b9d9962cbca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jan 2023 14:24:20 +0000 Subject: [PATCH 0188/1295] build(deps-dev): bump markdownlint-cli2 from 0.5.1 to 0.6.0 (#4492) Bumps [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) from 0.5.1 to 0.6.0. - [Release notes](https://github.com/DavidAnson/markdownlint-cli2/releases) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.5.1...v0.6.0) --- updated-dependencies: - dependency-name: markdownlint-cli2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82ea3e65b72..0d19baf3c46 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "json-schema-to-ts": "^2.5.5", "JSONStream": "^1.3.5", "license-checker": "^25.0.1", - "markdownlint-cli2": "^0.5.1", + "markdownlint-cli2": "^0.6.0", "proxyquire": "^2.1.3", "pump": "^3.0.0", "self-cert": "^2.0.0", From d89b4f4e39a1df21a320f22d11ac62a24c0aef15 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 3 Jan 2023 16:57:32 +0100 Subject: [PATCH 0189/1295] nodenext compatibility (#4438) * restructue imports and export explicitly * move @fastify/error module declaration to top of fastify.d.ts * use FastifyBaseLogger instead of FastifyLoggerInstance * nodenext structure * move import of types/reply above imports of types/request --- fastify.d.ts | 367 +++++++++++++++++++++++++++------------------------ 1 file changed, 191 insertions(+), 176 deletions(-) diff --git a/fastify.d.ts b/fastify.d.ts index 759090cd9a3..6749ba8d1b7 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -1,26 +1,190 @@ import * as http from 'http' import * as http2 from 'http2' import * as https from 'https' -import { ConstraintStrategy, HTTPVersion } from 'find-my-way' +import { Socket } from 'net' -import { FastifyRequest, RequestGenericInterface } from './types/request' -import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression } from './types/utils' -import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions } from './types/logger' -import { FastifyInstance } from './types/instance' -import { FastifyServerFactory } from './types/serverFactory' -import { Options as AjvOptions } from '@fastify/ajv-compiler' -import { Options as FJSOptions } from '@fastify/fast-json-stringify-compiler' +import { Options as AjvOptions, ValidatorCompiler } from '@fastify/ajv-compiler' import { FastifyError } from '@fastify/error' +import { Options as FJSOptions, SerializerCompiler } from '@fastify/fast-json-stringify-compiler' +import { ConstraintStrategy, HTTPVersion } from 'find-my-way' +import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request' + +import { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser' +import { FastifyContext, FastifyContextConfig } from './types/context' +import { FastifyErrorCodes } from './types/errors' +import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './types/hooks' +import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' +import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel } from './types/logger' +import { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' +import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './types/register' import { FastifyReply } from './types/reply' -import { FastifySchemaValidationError } from './types/schema' -import { ConstructorAction, ProtoAction } from "./types/content-type-parser"; -import { Socket } from 'net' -import { ValidatorCompiler } from '@fastify/ajv-compiler' -import { SerializerCompiler } from '@fastify/fast-json-stringify-compiler' -import { FastifySchema } from './types/schema' -import { FastifyContextConfig } from './types/context' +import { FastifyRequest, RequestGenericInterface } from './types/request' +import { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route' +import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError } from './types/schema' +import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider' -import { FastifyErrorCodes } from './types/errors' +import { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils' + +declare module '@fastify/error' { + interface FastifyError { + validation?: fastify.ValidationResult[]; + validationContext?: 'body' | 'headers' | 'parameters' | 'querystring'; + } +} + +type Fastify = typeof fastify + +declare namespace fastify { + export const errorCodes: FastifyErrorCodes; + + export type FastifyHttp2SecureOptions< + Server extends http2.Http2SecureServer, + Logger extends FastifyBaseLogger = FastifyBaseLogger + > = FastifyServerOptions & { + http2: true, + https: http2.SecureServerOptions, + http2SessionTimeout?: number + } + + export type FastifyHttp2Options< + Server extends http2.Http2Server, + Logger extends FastifyBaseLogger = FastifyBaseLogger + > = FastifyServerOptions & { + http2: true, + http2SessionTimeout?: number + } + + export type FastifyHttpsOptions< + Server extends https.Server, + Logger extends FastifyBaseLogger = FastifyBaseLogger + > = FastifyServerOptions & { + https: https.ServerOptions | null + } + + type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2 + + export interface ConnectionError extends Error { + code: string, + bytesParsed: number, + rawPacket: { + type: string, + data: number[] + } + } + + type TrustProxyFunction = (address: string, hop: number) => boolean + + /** + * Options for a fastify server instance. Utilizes conditional logic on the generic server parameter to enforce certain https and http2 + */ + export type FastifyServerOptions< + RawServer extends RawServerBase = RawServerDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger + > = { + ignoreTrailingSlash?: boolean, + ignoreDuplicateSlashes?: boolean, + connectionTimeout?: number, + keepAliveTimeout?: number, + maxRequestsPerSocket?: number, + forceCloseConnections?: boolean | 'idle', + requestTimeout?: number, + pluginTimeout?: number, + bodyLimit?: number, + maxParamLength?: number, + disableRequestLogging?: boolean, + exposeHeadRoutes?: boolean, + onProtoPoisoning?: ProtoAction, + onConstructorPoisoning?: ConstructorAction, + logger?: boolean | FastifyLoggerOptions & PinoLoggerOptions | Logger, + serializerOpts?: FJSOptions | Record, + serverFactory?: FastifyServerFactory, + caseSensitive?: boolean, + requestIdHeader?: string | false, + requestIdLogLabel?: string; + jsonShorthand?: boolean; + genReqId?: (req: FastifyRequest, FastifySchema, TypeProvider>) => string, + trustProxy?: boolean | string | string[] | number | TrustProxyFunction, + querystringParser?: (str: string) => { [key: string]: unknown }, + /** + * @deprecated Prefer using the `constraints.version` property + */ + versioning?: { + storage(): { + get(version: string): string | null, + set(version: string, store: Function): void + del(version: string): void, + empty(): void + }, + deriveVersion(req: Object, ctx?: Context): string // not a fan of using Object here. Also what is Context? Can either of these be better defined? + }, + constraints?: { + [name: string]: ConstraintStrategy, unknown>, + }, + schemaController?: { + bucket?: (parentSchemas?: unknown) => { + add(schema: unknown): FastifyInstance; + getSchema(schemaId: string): unknown; + getSchemas(): Record; + }; + compilersFactory?: { + buildValidator?: ValidatorCompiler; + buildSerializer?: SerializerCompiler; + }; + }; + return503OnClosing?: boolean, + ajv?: { + customOptions?: AjvOptions, + plugins?: (Function | [Function, unknown])[] + }, + frameworkErrors?: ( + error: FastifyError, + req: FastifyRequest, FastifySchema, TypeProvider>, + res: FastifyReply, RawReplyDefaultExpression, RequestGeneric, FastifyContextConfig, SchemaCompiler, TypeProvider> + ) => void, + rewriteUrl?: (req: RawRequestDefaultExpression) => string, + schemaErrorFormatter?: (errors: FastifySchemaValidationError[], dataVar: string) => Error, + /** + * listener to error events emitted by client connections + */ + clientErrorHandler?: (error: ConnectionError, socket: Socket) => void + } + + export interface ValidationResult { + keyword: string; + instancePath: string; + schemaPath: string; + params: Record; + message?: string; + } + + /* Export additional types */ + export type { + LightMyRequestChain, InjectOptions, LightMyRequestResponse, LightMyRequestCallback, // 'light-my-request' + FastifyRequest, RequestGenericInterface, // './types/request' + FastifyReply, // './types/reply' + FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin, // './types/plugin' + FastifyListenOptions, FastifyInstance, PrintRoutesOptions, // './types/instance' + FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel, // './types/logger' + FastifyContext, FastifyContextConfig, // './types/context' + RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface, // './types/route' + FastifyRegister, FastifyRegisterOptions, RegisterOptions, // './types/register' + FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction, // './types/content-type-parser' + FastifyError, // '@fastify/error' + FastifySchema, FastifySchemaCompiler, // './types/schema' + HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault, // './types/utils' + DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, // './types/hooks' + FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory' + FastifyTypeProvider, FastifyTypeProviderDefault, // './types/type-provider' + FastifyErrorCodes, // './types/errors' + } + // named export + // import { plugin } from 'plugin' + // const { plugin } = require('plugin') + export const fastify: Fastify + // default export + // import plugin from 'plugin' + export { fastify as default } +} /** * Fastify factory function for the standard fastify http, https, or http2 server instance. @@ -34,183 +198,34 @@ declare function fastify< Server extends http2.Http2SecureServer, Request extends RawRequestDefaultExpression = RawRequestDefaultExpression, Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts: FastifyHttp2SecureOptions): FastifyInstance & PromiseLike> +>(opts: fastify.FastifyHttp2SecureOptions): FastifyInstance & PromiseLike> declare function fastify< Server extends http2.Http2Server, Request extends RawRequestDefaultExpression = RawRequestDefaultExpression, Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts: FastifyHttp2Options): FastifyInstance & PromiseLike> +>(opts: fastify.FastifyHttp2Options): FastifyInstance & PromiseLike> declare function fastify< Server extends https.Server, Request extends RawRequestDefaultExpression = RawRequestDefaultExpression, Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts: FastifyHttpsOptions): FastifyInstance & PromiseLike> +>(opts: fastify.FastifyHttpsOptions): FastifyInstance & PromiseLike> declare function fastify< Server extends http.Server, Request extends RawRequestDefaultExpression = RawRequestDefaultExpression, Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - Logger extends FastifyBaseLogger = FastifyLoggerInstance, + Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts?: FastifyServerOptions): FastifyInstance & PromiseLike> - -declare namespace fastify { - export const errorCodes: FastifyErrorCodes; -} - -export default fastify - -export type FastifyHttp2SecureOptions< - Server extends http2.Http2SecureServer, - Logger extends FastifyBaseLogger = FastifyLoggerInstance -> = FastifyServerOptions & { - http2: true, - https: http2.SecureServerOptions, - http2SessionTimeout?: number -} - -export type FastifyHttp2Options< - Server extends http2.Http2Server, - Logger extends FastifyBaseLogger = FastifyLoggerInstance -> = FastifyServerOptions & { - http2: true, - http2SessionTimeout?: number -} - -export type FastifyHttpsOptions< - Server extends https.Server, - Logger extends FastifyBaseLogger = FastifyLoggerInstance -> = FastifyServerOptions & { - https: https.ServerOptions | null -} - -type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2 - -export interface ConnectionError extends Error { - code: string, - bytesParsed: number, - rawPacket: { - type: string, - data: number[] - } -} - -/** - * Options for a fastify server instance. Utilizes conditional logic on the generic server parameter to enforce certain https and http2 - */ -export type FastifyServerOptions< - RawServer extends RawServerBase = RawServerDefault, - Logger extends FastifyBaseLogger = FastifyLoggerInstance -> = { - ignoreTrailingSlash?: boolean, - ignoreDuplicateSlashes?: boolean, - connectionTimeout?: number, - keepAliveTimeout?: number, - maxRequestsPerSocket?: number, - forceCloseConnections?: boolean | 'idle', - requestTimeout?: number, - pluginTimeout?: number, - bodyLimit?: number, - maxParamLength?: number, - disableRequestLogging?: boolean, - exposeHeadRoutes?: boolean, - onProtoPoisoning?: ProtoAction, - onConstructorPoisoning?: ConstructorAction, - logger?: boolean | FastifyLoggerOptions & PinoLoggerOptions | Logger, - serializerOpts?: FJSOptions | Record, - serverFactory?: FastifyServerFactory, - caseSensitive?: boolean, - requestIdHeader?: string | false, - requestIdLogLabel?: string; - jsonShorthand?: boolean; - genReqId?: (req: FastifyRequest, FastifySchema, TypeProvider>) => string, - trustProxy?: boolean | string | string[] | number | TrustProxyFunction, - querystringParser?: (str: string) => { [key: string]: unknown }, - /** - * @deprecated Prefer using the `constraints.version` property - */ - versioning?: { - storage(): { - get(version: string): string | null, - set(version: string, store: Function): void - del(version: string): void, - empty(): void - }, - deriveVersion(req: Object, ctx?: Context): string // not a fan of using Object here. Also what is Context? Can either of these be better defined? - }, - constraints?: { - [name: string]: ConstraintStrategy, unknown>, - }, - schemaController?: { - bucket?: (parentSchemas?: unknown) => { - add(schema: unknown): FastifyInstance; - getSchema(schemaId: string): unknown; - getSchemas(): Record; - }; - compilersFactory?: { - buildValidator?: ValidatorCompiler; - buildSerializer?: SerializerCompiler; - }; - }; - return503OnClosing?: boolean, - ajv?: { - customOptions?: AjvOptions, - plugins?: (Function | [Function, unknown])[] - }, - frameworkErrors?: ( - error: FastifyError, - req: FastifyRequest, FastifySchema, TypeProvider>, - res: FastifyReply, RawReplyDefaultExpression, RequestGeneric, FastifyContextConfig, SchemaCompiler, TypeProvider> - ) => void, - rewriteUrl?: (req: RawRequestDefaultExpression) => string, - schemaErrorFormatter?: (errors: FastifySchemaValidationError[], dataVar: string) => Error, - /** - * listener to error events emitted by client connections - */ - clientErrorHandler?: (error: ConnectionError, socket: Socket) => void -} - -type TrustProxyFunction = (address: string, hop: number) => boolean - -declare module '@fastify/error' { - interface FastifyError { - validation?: ValidationResult[]; - validationContext?: 'body' | 'headers' | 'parameters' | 'querystring'; - } -} - -export interface ValidationResult { - keyword: string; - instancePath: string; - schemaPath: string; - params: Record; - message?: string; -} +>(opts?: fastify.FastifyServerOptions): FastifyInstance & PromiseLike> -/* Export all additional types */ -export type { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request' -export { FastifyRequest, RequestGenericInterface } from './types/request' -export { FastifyReply } from './types/reply' -export { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' -export { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' -export { FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel } from './types/logger' -export { FastifyContext, FastifyContextConfig } from './types/context' -export { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route' -export * from './types/register' -export { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser' -export { FastifyError } from '@fastify/error' -export { FastifySchema, FastifySchemaCompiler } from './types/schema' -export { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils' -export * from './types/hooks' -export { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' -export { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider' -export { FastifyErrorCodes } from './types/errors' -export { fastify } +// CJS export +// const fastify = require('fastify') +export = fastify From 687459a0664619b843630b2cf9726b42b798c3ca Mon Sep 17 00:00:00 2001 From: KaKa Date: Thu, 5 Jan 2023 19:23:20 +0800 Subject: [PATCH 0190/1295] fix: content-type parserRegExpList when plugin override (#4496) --- lib/contentTypeParser.js | 1 + test/content-parser.test.js | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index d4eb906b481..e12a17e6fcd 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -308,6 +308,7 @@ function buildContentTypeParser (c) { contentTypeParser[kDefaultJsonParse] = c[kDefaultJsonParse] contentTypeParser.customParsers = new Map(c.customParsers.entries()) contentTypeParser.parserList = c.parserList.slice() + contentTypeParser.parserRegExpList = c.parserRegExpList.slice() return contentTypeParser } diff --git a/test/content-parser.test.js b/test/content-parser.test.js index edf7d18b128..3e71d85a625 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -609,3 +609,46 @@ test('content-type fail when parameters not match - regexp', async t => { t.same(response.statusCode, 415) }) + +// Refs: https://github.com/fastify/fastify/issues/4495 +test('content-type regexp list should be cloned when plugin override', async t => { + t.plan(6) + + const fastify = Fastify() + + fastify.addContentTypeParser(/^image\/.*/, { parseAs: 'buffer' }, (req, payload, done) => { + done(null, payload) + }) + + fastify.register(function plugin (fastify, options, done) { + fastify.post('/', function (request, reply) { + reply.type(request.headers['content-type']).send(request.body) + }) + + done() + }) + + { + const { payload, headers, statusCode } = await fastify.inject({ + method: 'POST', + path: '/', + payload: 'jpeg', + headers: { 'content-type': 'image/jpeg' } + }) + t.same(statusCode, 200) + t.same(headers['content-type'], 'image/jpeg') + t.same(payload, 'jpeg') + } + + { + const { payload, headers, statusCode } = await fastify.inject({ + method: 'POST', + path: '/', + payload: 'png', + headers: { 'content-type': 'image/png' } + }) + t.same(statusCode, 200) + t.same(headers['content-type'], 'image/png') + t.same(payload, 'png') + } +}) From da047e62d6e4cd419f44996f415d87a8a2a14661 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Fri, 6 Jan 2023 22:15:17 +0100 Subject: [PATCH 0191/1295] docs: upgrade migration guide for v4 (#4503) * docs: upgrade migration guide for v4 * fix: linter --- docs/Guides/Migration-Guide-V4.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 4efc3ce923b..2496e3ac72a 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -39,6 +39,11 @@ const res = await fastify.inject('/encapsulated') console.log(res.json().message) // 'wrapped' ``` +>The root error handler is Fastify’s generic error handler. +>This error handler will use the headers and status code in the Error object, +>if they exist. **The headers and status code will not be automatically set if +>a custom error handler is provided**. + ### Removed `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506)) With v4 of Fastify, `app.use()` has been removed and the use of middleware is From 8ebe376185ebed16627b24c21472e91c44509bb5 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 10 Jan 2023 09:23:47 +0100 Subject: [PATCH 0192/1295] replace content-type with fast-content-type-parse (#4505) --- lib/contentTypeParser.js | 17 ++--------------- package.json | 2 +- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index e12a17e6fcd..418ac563cb5 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -2,9 +2,7 @@ const { AsyncResource } = require('async_hooks') const lru = require('tiny-lru').lru -// TODO: find more perforamant solution -const { parse: parseContentType } = require('content-type') - +const { safeParse: safeParseContentType, defaultContentType } = require('fast-content-type-parse') const secureJson = require('secure-json-parse') const { kDefaultJsonParse, @@ -104,7 +102,7 @@ ContentTypeParser.prototype.getParser = function (contentType) { // dummyContentType always the same object // we can use === for the comparsion and return early - if (parsed === dummyContentType) { + if (parsed === defaultContentType) { return this.customParsers.get('') } @@ -360,17 +358,6 @@ function removeAllContentTypeParsers () { this[kContentTypeParser].removeAll() } -// dummy here to prevent repeated object creation -const dummyContentType = { type: '', parameters: Object.create(null) } - -function safeParseContentType (contentType) { - try { - return parseContentType(contentType) - } catch (err) { - return dummyContentType - } -} - function compareContentType (contentType, parserListItem) { if (parserListItem.isEssence) { // we do essence check diff --git a/package.json b/package.json index 0d19baf3c46..4cf11babb52 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "@fastify/fast-json-stringify-compiler": "^4.1.0", "abstract-logging": "^2.0.1", "avvio": "^8.2.0", - "content-type": "^1.0.4", + "fast-content-type-parse": "^1.0.0", "find-my-way": "^7.3.0", "light-my-request": "^5.6.1", "pino": "^8.5.0", From baf0f068908a9bf4ba9975940f5223dd021d9492 Mon Sep 17 00:00:00 2001 From: Kyle Rush Date: Sun, 15 Jan 2023 13:46:50 -0500 Subject: [PATCH 0193/1295] Add fastify-postgres-dot-js plugin to ecosystem docs. (#4514) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 0423ce54075..db9ea620cc1 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -474,6 +474,8 @@ section. - [`fastify-postgraphile`](https://github.com/alemagio/fastify-postgraphile) Plugin to integrate [PostGraphile](https://www.graphile.org/postgraphile/) in a Fastify project. +- [`fastify-postgres-dot-js`](https://github.com/kylerush/fastify-postgresjs) Fastify + PostgreSQL connection plugin that uses [Postgres.js](https://github.com/porsager/postgres). - [`fastify-prettier`](https://github.com/hsynlms/fastify-prettier) A Fastify plugin that uses [prettier](https://github.com/prettier/prettier) under the hood to beautify outgoing responses and/or other things in the Fastify server. From a6e48435c790b98c0d1555a4d8e5281762729bb0 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 16 Jan 2023 11:48:25 +0000 Subject: [PATCH 0194/1295] chore(license): update licensing year (#4516) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index a5b4e2a259f..bd8d19c6904 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2022 The Fastify Team +Copyright (c) 2016-2023 The Fastify Team The Fastify team members are listed at https://github.com/fastify/fastify#team and in the README file. From 5bdf76c17903df18fd3687e093677cebd0d3e8cb Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Wed, 18 Jan 2023 06:25:33 +0100 Subject: [PATCH 0195/1295] docs(Ecosystem): add metcoder95/fastify-ip (#4517) --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index db9ea620cc1..deb94d99869 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -366,6 +366,9 @@ section. - [`fastify-influxdb`](https://github.com/alex-ppg/fastify-influxdb) Fastify InfluxDB plugin connecting to an InfluxDB instance via the Influx default package. +- [`fastify-ip`](https://github.com/metcoder95/fastify-ip) A plugin + for Fastify that allows you to infer a request ID by a + given set of custom Request headers. - [`fastify-jwt-authz`](https://github.com/Ethan-Arrowood/fastify-jwt-authz) JWT user scope verifier. - [`fastify-jwt-webapp`](https://github.com/charlesread/fastify-jwt-webapp) JWT From 2cf0c32b20e84071cd089ae56dabb9c7427a8468 Mon Sep 17 00:00:00 2001 From: Baptiste Garcin Date: Sat, 21 Jan 2023 00:12:22 +0100 Subject: [PATCH 0196/1295] #4523 Add types for fastifyInstance.addresses() (#4524) * #4523 Add types for fastifyInstance.addresses() * #4523 Add type definition test in instance.test-d.ts --- test/types/instance.test-d.ts | 2 ++ types/instance.d.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index eadd16443f1..fa5d4c0a334 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -12,6 +12,7 @@ import { FastifyReply } from '../../types/reply' import { FastifyRequest } from '../../types/request' import { DefaultRoute } from '../../types/route' import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema' +import { AddressInfo } from 'net' const server = fastify() @@ -26,6 +27,7 @@ expectAssignable(server.addSchema({ })) expectType>(server.getSchemas()) +expectType(server.addresses()) expectType(server.getSchema('SchemaId')) expectType(server.printRoutes()) expectType(server.printPlugins()) diff --git a/types/instance.d.ts b/types/instance.d.ts index 0d562717cf6..363b2d56fda 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -21,6 +21,7 @@ import { FastifyTypeProviderDefault } from './type-provider' import { ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' +import { AddressInfo } from 'net' export interface PrintRoutesOptions { includeMeta?: boolean | (string | symbol)[] @@ -92,6 +93,7 @@ export interface FastifyInstance< version: string; log: Logger; + addresses(): AddressInfo[] withTypeProvider(): FastifyInstance; addSchema(schema: unknown): FastifyInstance; From d34f81bfa01842432a4eb06da25fd83c7b30742d Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 21 Jan 2023 15:25:47 +0100 Subject: [PATCH 0197/1295] Bumped v4.12.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 8 ++++++++ package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index a8ecaa9bffd..4f30beaeb8e 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.11.0' +const VERSION = '4.12.0' const Avvio = require('avvio') const http = require('http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index 529977af9cf..afb17021fe0 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -5,6 +5,9 @@ +// eslint-disable-next-line +const STR_ESCAPE = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]|[\ud800-\udbff](?![\udc00-\udfff])|(?:[^\ud800-\udbff]|^)[\udc00-\udfff]/ + class Serializer { constructor (options = {}) { switch (options.rounding) { @@ -99,6 +102,11 @@ class Serializer { str = str.toString() } + // Fast escape chars check + if (!STR_ESCAPE.test(str)) { + return quotes + str + quotes + } + if (str.length < 42) { return this.asStringSmall(str) } else { diff --git a/package.json b/package.json index 4cf11babb52..05409641dac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.11.0", + "version": "4.12.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 6f4842df031aaf770fecb3860a9b587965e6acb2 Mon Sep 17 00:00:00 2001 From: Eugenio Oddone <121885756+eugenio-oddone@users.noreply.github.com> Date: Tue, 24 Jan 2023 00:56:15 -0800 Subject: [PATCH 0198/1295] Docs: Add guide about detecting client abort (#4518) * docs: add guide about client abort * fix lint errors * fix line break * A guide to table of contents * grammar updates Co-authored-by: Frazer Smith * grammar updates Co-authored-by: Frazer Smith * grammar updates Co-authored-by: Frazer Smith * grammar updates Co-authored-by: Frazer Smith * Use fetch for testing example Co-authored-by: Frazer Smith --- docs/Guides/Detecting-When-Clients-Abort.md | 180 ++++++++++++++++++++ docs/Guides/Index.md | 2 + 2 files changed, 182 insertions(+) create mode 100644 docs/Guides/Detecting-When-Clients-Abort.md diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md new file mode 100644 index 00000000000..19102dc92a7 --- /dev/null +++ b/docs/Guides/Detecting-When-Clients-Abort.md @@ -0,0 +1,180 @@ +

Fastify

+ +# Detecting When Clients Abort + +## Introduction + +Fastify provides request events to trigger at certain points in a request's +lifecycle. However, there isn't a mechanism built-in to +detect unintentional client disconnection scenarios such as when the client's +internet connection is interrupted. This guide covers methods to detect if +and when a client intentionally aborts a request. + +Keep in mind, Fastify's clientErrorHandler is not designed to detect when a +client aborts a request. This works in the same way as the standard Node HTTP +module, which triggers the clientError event when there is a bad request or +exceedingly large header data. When a client aborts a request, there is no +error on the socket and the clientErrorHandler will not be triggered. + +## Solution + +### Overview + +The proposed solution is a possible way of detecting when a client +intentionally aborts a request, such as when a browser is closed or the HTTP +request is aborted from your client application. If there is an error in your +application code that results in the server crashing, you may require +additional logic to avoid a false abort detection. + +The goal here is to detect when a client intentionally aborts a connection +so your application logic can proceed accordingly. This can be useful for +logging purposes or halting business logic. + +### Hands-on + +For this sample solution, we'll be using the following: + +- `node.js v18.12.1` +- `npm 8.19.2` +- `fastify 4.11.0` + +Say we have the following base server set up: + +```js +import Fastify from 'fastify'; + +const sleep = async (time) => { + return await new Promise(resolve => setTimeout(resolve, time || 1000)); +} + +const app = Fastify({ + logger: { + transport: { + target: 'pino-pretty', + options: { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname', + }, + }, + }, +}) + +app.addHook('onRequest', async (request, reply) => { + request.raw.on('close', () => { + if (request.raw.aborted) { + app.log.info('request closed') + } + }) +}) + +app.get('/', async (request, reply) => { + await sleep(3000) + reply.code(200).send({ ok: true }) +}) + +const start = async () => { + try { + await app.listen({ port: 3000 }) + } catch (err) { + app.log.error(err) + process.exit(1) + } +} + +start() +``` + +Our code is setting up a Fastify server which includes the following +functionality: + +- Accepting requests at http://localhost:3000, with a 3 second delayed response +of { ok: true }. +- An onRequest hook that triggers when every request is received. +- Logic that triggers in the hook when the request is closed. +- Logging that occurs when the closed request attribute 'aborted' is true. + +In the request close event, you should examine the diff between a successful +request and one aborted by the client to determine the best attribute for your +use case. There are many other attributes on a request that will differ between +a successfully closed request and one that has been aborted by the client. +Examples of such attributes include: + +- destroyed +- errors + +You can also perform this logic outside of a hook, directly in a specific route. + +```js +app.get('/', async (request, reply) => { + request.raw.on('close', () => { + if (request.raw.aborted) { + app.log.info('request closed') + } + }) + await sleep(3000) + reply.code(200).send({ ok: true }) +}) +``` + +At any point in your business logic, you can check if the request has been +aborted and perform alternative actions. + +```js +app.get('/', async (request, reply) => { + await sleep(3000) + if (request.raw.aborted) { + // do something here + } + await sleep(3000) + reply.code(200).send({ ok: true }) +}) +``` + +A benefit to adding this in your application code is that you can log Fastify +details such as the reqId, which may be unavailable in lower-level code that +only has access to the raw request information. + +### Testing + +To test this functionality you can use an app like Postman and cancel your +request within 3 seconds. Alternatively, you can use Node to send an HTTP +request with logic to abort the request before 3 seconds. Example: + +```js +const controller = new AbortController(); +const signal = controller.signal; + +(async () => { + try { + const response = await fetch('http://localhost:3000', { signal }); + const body = await response.text(); + console.log(body); + } catch (error) { + console.error(error); + } +})(); + +setTimeout(() => { + controller.abort() +}, 1000); +``` + +With either approach, you should see the Fastify log appear at the moment the +request is aborted. + +## Conclusion + +Specifics of the implementation will vary from one problem to another, but the +main goal of this guide was to show a very specific use case of an issue that +could be solved within Fastify's ecosystem. + +You can listen to the request close event and determine if the request was +aborted or if it was successfully delivered. You can implement this solution +in an onRequest hook or directly in an individual route. + +This approach will not trigger in the event of internet disruption, and such +detection would require additional business logic. If you have flawed backend +application logic that results in a server crash, then you could trigger a +false detection. The clientErrorHandler, either by default or with custom +logic, is not intended to handle this scenario and will not trigger when the +client aborts a request. diff --git a/docs/Guides/Index.md b/docs/Guides/Index.md index fa8b4635984..9e80b166055 100644 --- a/docs/Guides/Index.md +++ b/docs/Guides/Index.md @@ -15,6 +15,8 @@ This table of contents is in alphabetical order. met in your application. This guide focuses on solving the problem using [`Hooks`](../Reference/Hooks.md), [`Decorators`](../Reference/Decorators.md), and [`Plugins`](../Reference/Plugins.md). ++ [Detecting When Clients Abort](./Detecting-When-Clients-Abort.md): A + practical guide on detecting if and when a client aborts a request. + [Ecosystem](./Ecosystem.md): Lists all core plugins and many known community plugins. + [Fluent Schema](./Fluent-Schema.md): Shows how writing JSON Schema can be From 842dc1379d4f664c0c151fc0e27faeff03ed7760 Mon Sep 17 00:00:00 2001 From: audothomas Date: Mon, 23 Jan 2023 10:49:09 +0100 Subject: [PATCH 0199/1295] fix: add type support for instance has plugin --- test/types/instance.test-d.ts | 2 ++ types/instance.d.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index fa5d4c0a334..f0d65daf2f5 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -326,6 +326,8 @@ const versionConstraintStrategy = { expectType(server.addConstraintStrategy(versionConstraintStrategy)) expectType(server.hasConstraintStrategy(versionConstraintStrategy.name)) +expectType(server.hasPlugin('')) + expectAssignable>(server.getDefaultRoute()) expectType | undefined>(server.validatorCompiler) diff --git a/types/instance.d.ts b/types/instance.d.ts index 363b2d56fda..f2e39770a44 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -131,6 +131,7 @@ export interface FastifyInstance< hasDecorator(decorator: string | symbol): boolean; hasRequestDecorator(decorator: string | symbol): boolean; hasReplyDecorator(decorator: string | symbol): boolean; + hasPlugin(name: string): boolean; addConstraintStrategy(strategy: ConstraintStrategy, unknown>): void; hasConstraintStrategy(strategyName: string): boolean; From a4c5f5ffee0b57983515d87a769e8de0723dbd62 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 27 Jan 2023 03:18:59 +0800 Subject: [PATCH 0200/1295] test: add host header for net connection (#4536) --- test/close.test.js | 8 ++++---- test/maxRequestsPerSocket.test.js | 12 ++++++------ test/skip-reply-send.test.js | 2 +- test/unsupported-httpversion.test.js | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/close.test.js b/test/close.test.js index 84a7a3d8404..c2abf38a3c6 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -220,13 +220,13 @@ t.test('Current opened connection should continue to work after closing and retu const port = fastify.server.address().port const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /Connection:\s*keep-alive/i) t.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /Connection:\s*close/i) @@ -259,7 +259,7 @@ t.test('Current opened connection should NOT continue to work after closing and const port = fastify.server.address().port const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.on('error', function () { // Dependending on the Operating System @@ -275,7 +275,7 @@ t.test('Current opened connection should NOT continue to work after closing and t.match(data.toString(), /Connection:\s*keep-alive/i) t.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') }) }) }) diff --git a/test/maxRequestsPerSocket.test.js b/test/maxRequestsPerSocket.test.js index 1b9887a8d86..9de7676c7c1 100644 --- a/test/maxRequestsPerSocket.test.js +++ b/test/maxRequestsPerSocket.test.js @@ -22,20 +22,20 @@ test('maxRequestsPerSocket on node version >= 16.10.0', { skip }, t => { const port = fastify.server.address().port const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /Connection:\s*keep-alive/i) t.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) t.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /Connection:\s*close/i) t.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /Connection:\s*close/i) @@ -63,21 +63,21 @@ test('maxRequestsPerSocket zero should behave same as null', { skip }, t => { const port = fastify.server.address().port const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /Connection:\s*keep-alive/i) t.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) t.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /Connection:\s*keep-alive/i) t.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) t.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /Connection:\s*keep-alive/i) diff --git a/test/skip-reply-send.test.js b/test/skip-reply-send.test.js index 3ab06088251..4ce58c471e3 100644 --- a/test/skip-reply-send.test.js +++ b/test/skip-reply-send.test.js @@ -204,7 +204,7 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { app.listen({ port: 0 }, err => { t.error(err) const client = net.createConnection({ port: (app.server.address()).port }, () => { - client.write('GET / HTTP/1.1\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') let chunks = '' client.setEncoding('utf8') diff --git a/test/unsupported-httpversion.test.js b/test/unsupported-httpversion.test.js index ef93fef7d4f..9d6b9b17919 100644 --- a/test/unsupported-httpversion.test.js +++ b/test/unsupported-httpversion.test.js @@ -18,7 +18,7 @@ t.test('Will return 505 HTTP error if HTTP version (2.0 when server is 1.1) is n const port = fastify.server.address().port const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/2.0\r\n\r\n') + client.write('GET / HTTP/2.0\r\nHost: example.com\r\n\r\n') client.once('data', data => { t.match(data.toString(), /505 HTTP Version Not Supported/i) From 46a6d1fe6d3f99378353de4e7aa54c58c17822e9 Mon Sep 17 00:00:00 2001 From: Sasa Cvetkovic Date: Fri, 27 Jan 2023 00:21:32 +0100 Subject: [PATCH 0201/1295] fix: getSchemaSerializer contentType check (#4531) --- lib/error-handler.js | 2 +- lib/schemas.js | 6 +++--- test/hooks.test.js | 41 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/error-handler.js b/lib/error-handler.js index 68c89097339..801c42bce41 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -96,7 +96,7 @@ function fallbackErrorHandler (error, reply, cb) { const statusCode = reply.statusCode let payload try { - const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode) + const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type']) payload = (serializerFn === false) ? serializeError({ error: statusCodes[statusCode + ''], diff --git a/lib/schemas.js b/lib/schemas.js index f9fc0488ebd..154248cf0b9 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -160,7 +160,7 @@ function getSchemaSerializer (context, statusCode, contentType) { return false } if (responseSchemaDef[statusCode]) { - if (responseSchemaDef[statusCode].constructor === Object) { + if (responseSchemaDef[statusCode].constructor === Object && contentType) { const mediaName = contentType.split(';')[0] if (responseSchemaDef[statusCode][mediaName]) { return responseSchemaDef[statusCode][mediaName] @@ -172,7 +172,7 @@ function getSchemaSerializer (context, statusCode, contentType) { } const fallbackStatusCode = (statusCode + '')[0] + 'xx' if (responseSchemaDef[fallbackStatusCode]) { - if (responseSchemaDef[fallbackStatusCode].constructor === Object) { + if (responseSchemaDef[fallbackStatusCode].constructor === Object && contentType) { const mediaName = contentType.split(';')[0] if (responseSchemaDef[fallbackStatusCode][mediaName]) { return responseSchemaDef[fallbackStatusCode][mediaName] @@ -184,7 +184,7 @@ function getSchemaSerializer (context, statusCode, contentType) { return responseSchemaDef[fallbackStatusCode] } if (responseSchemaDef.default) { - if (responseSchemaDef.default.constructor === Object) { + if (responseSchemaDef.default.constructor === Object && contentType) { const mediaName = contentType.split(';')[0] if (responseSchemaDef.default[mediaName]) { return responseSchemaDef.default[mediaName] diff --git a/test/hooks.test.js b/test/hooks.test.js index 70d589a7aad..742bf86849b 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -10,6 +10,7 @@ const fs = require('fs') const split = require('split2') const symbols = require('../lib/symbols.js') const payload = { hello: 'world' } +const proxyquire = require('proxyquire') process.removeAllListeners('warning') @@ -1269,7 +1270,14 @@ test('clear payload', t => { }) test('onSend hook throws', t => { - t.plan(9) + t.plan(11) + const Fastify = proxyquire('..', { + './lib/schemas.js': { + getSchemaSerializer: (param1, param2, param3) => { + t.equal(param3, 'application/json; charset=utf-8', 'param3 should be "application/json; charset=utf-8"') + } + } + }) const fastify = Fastify() fastify.addHook('onSend', function (request, reply, payload, done) { if (request.raw.method === 'DELETE') { @@ -1281,6 +1289,10 @@ test('onSend hook throws', t => { throw new Error('some error') } + if (request.raw.method === 'POST') { + throw new Error('some error') + } + done() }) @@ -1288,6 +1300,26 @@ test('onSend hook throws', t => { reply.send({ hello: 'world' }) }) + fastify.post('/', { + schema: { + response: { + 200: { + content: { + 'application/json': { + schema: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } + } + } + } + } + } + }, (req, reply) => { + reply.send({ hello: 'world' }) + }) + fastify.delete('/', (req, reply) => { reply.send({ hello: 'world' }) }) @@ -1309,6 +1341,13 @@ test('onSend hook throws', t => { t.equal(response.headers['content-length'], '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) }) + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 500) + }) sget({ method: 'DELETE', url: 'http://localhost:' + fastify.server.address().port From ad79204594b9fbb39f6edebc0f00e82b685997b5 Mon Sep 17 00:00:00 2001 From: Erfan Safari Date: Fri, 27 Jan 2023 17:53:58 +0330 Subject: [PATCH 0202/1295] docs(ecosystem): add fastify-web-response (#4537) * docs(ecosystem): add fastify-web-response * Update docs/Guides/Ecosystem.md Co-authored-by: James Sumners Co-authored-by: James Sumners --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index deb94d99869..0f282922e16 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -590,6 +590,8 @@ section. should use. - [`fastify-wamp-router`](https://github.com/lependu/fastify-wamp-router) Web Application Messaging Protocol router for Fastify. +- [`fastify-web-response`](https://github.com/erfanium/fastify-web-response) + Enables returning web streams objects `Response` and `ReadableStream` in routes. - [`fastify-webpack-hmr`](https://github.com/lependu/fastify-webpack-hmr) Webpack hot module reloading plugin for Fastify. - [`fastify-webpack-hot`](https://github.com/gajus/fastify-webpack-hot) Webpack From 7829f48236f5e292edc1e80c8de4170678b96cac Mon Sep 17 00:00:00 2001 From: Eugenio Oddone <121885756+eugenio-oddone@users.noreply.github.com> Date: Fri, 27 Jan 2023 11:26:16 -0800 Subject: [PATCH 0203/1295] Docs: Update guide about detecting client abort (#4530) * suggested updates from: https://github.com/fastify/fastify/pull/4518 * update text to code format * remove details about different properties --- docs/Guides/Detecting-When-Clients-Abort.md | 30 +++++++-------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md index 19102dc92a7..1bdedec3a99 100644 --- a/docs/Guides/Detecting-When-Clients-Abort.md +++ b/docs/Guides/Detecting-When-Clients-Abort.md @@ -5,16 +5,16 @@ ## Introduction Fastify provides request events to trigger at certain points in a request's -lifecycle. However, there isn't a mechanism built-in to +lifecycle. However, there isn't a built-in mechanism to detect unintentional client disconnection scenarios such as when the client's internet connection is interrupted. This guide covers methods to detect if and when a client intentionally aborts a request. -Keep in mind, Fastify's clientErrorHandler is not designed to detect when a +Keep in mind, Fastify's `clientErrorHandler` is not designed to detect when a client aborts a request. This works in the same way as the standard Node HTTP -module, which triggers the clientError event when there is a bad request or +module, which triggers the `clientError` event when there is a bad request or exceedingly large header data. When a client aborts a request, there is no -error on the socket and the clientErrorHandler will not be triggered. +error on the socket and the `clientErrorHandler` will not be triggered. ## Solution @@ -32,12 +32,6 @@ logging purposes or halting business logic. ### Hands-on -For this sample solution, we'll be using the following: - -- `node.js v18.12.1` -- `npm 8.19.2` -- `fastify 4.11.0` - Say we have the following base server set up: ```js @@ -88,19 +82,15 @@ Our code is setting up a Fastify server which includes the following functionality: - Accepting requests at http://localhost:3000, with a 3 second delayed response -of { ok: true }. +of `{ ok: true }`. - An onRequest hook that triggers when every request is received. - Logic that triggers in the hook when the request is closed. -- Logging that occurs when the closed request attribute 'aborted' is true. +- Logging that occurs when the closed request property `aborted` is true. In the request close event, you should examine the diff between a successful -request and one aborted by the client to determine the best attribute for your -use case. There are many other attributes on a request that will differ between -a successfully closed request and one that has been aborted by the client. -Examples of such attributes include: - -- destroyed -- errors +request and one aborted by the client to determine the best property for your +use case. You can find details about request properties in the +[NodeJS documentation](https://nodejs.org/api/http.html). You can also perform this logic outside of a hook, directly in a specific route. @@ -175,6 +165,6 @@ in an onRequest hook or directly in an individual route. This approach will not trigger in the event of internet disruption, and such detection would require additional business logic. If you have flawed backend application logic that results in a server crash, then you could trigger a -false detection. The clientErrorHandler, either by default or with custom +false detection. The `clientErrorHandler`, either by default or with custom logic, is not intended to handle this scenario and will not trigger when the client aborts a request. From dcfd99246602d70a8a23f40147c5e738cc0bf84b Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 2 Feb 2023 10:57:24 +0100 Subject: [PATCH 0204/1295] Bring docs in line with ajv-serializer defaults (#4544) Signed-off-by: Matteo Collina --- docs/Reference/Validation-and-Serialization.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 7732fb863dc..ab1f5c28803 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -394,9 +394,11 @@ configuration](https://github.com/fastify/ajv-compiler#ajv-configuration) is: ```js { - coerceTypes: true, // change data type of data to match type keyword + coerceTypes: 'array', // change data type of data to match type keyword useDefaults: true, // replace missing properties and items with the values from corresponding default keyword removeAdditional: true, // remove additional properties + uriResolver: require('fast-uri'), + addUsedSchema: false, // Explicitly set allErrors to `false`. // When set to `true`, a DoS attack is possible. allErrors: false From 19d05b542a46bb1f580ef95c9cf258db0be8aa35 Mon Sep 17 00:00:00 2001 From: Adibla <32988879+Adibla@users.noreply.github.com> Date: Fri, 3 Feb 2023 10:53:43 +0100 Subject: [PATCH 0205/1295] docs: update await fastify.register docs (#4546) --- docs/Guides/Migration-Guide-V4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 2496e3ac72a..71c228d7f56 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -121,7 +121,7 @@ As a result, if you specify an `onRoute` hook in a plugin you should now either: Into this: ```js - await fastify.register((instance, opts) => { + await fastify.register((instance, opts, done) => { instance.addHook('onRoute', (routeOptions) => { const { path, method } = routeOptions; console.log({ path, method }); From 2179f835706fb804a3e78edf6aa2e2ff4f7ba91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Sun, 5 Feb 2023 07:31:03 +0100 Subject: [PATCH 0206/1295] ref(ctp): Return boolean indicating content type parser removal result (#4550) --- lib/contentTypeParser.js | 4 +++- test/content-parser.test.js | 19 ++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 418ac563cb5..80401b66b25 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -141,7 +141,7 @@ ContentTypeParser.prototype.removeAll = function () { ContentTypeParser.prototype.remove = function (contentType) { if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() - this.customParsers.delete(contentType.toString()) + const removed = this.customParsers.delete(contentType.toString()) const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList @@ -150,6 +150,8 @@ ContentTypeParser.prototype.remove = function (contentType) { if (idx > -1) { parsers.splice(idx, 1) } + + return removed || idx > -1 } ContentTypeParser.prototype.run = function (contentType, handler, request, reply) { diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 3e71d85a625..d677ecfedb8 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -256,27 +256,25 @@ test('Error thrown 415 from content type is null and make post request to server test('remove', t => { test('should remove default parser', t => { - t.plan(2) + t.plan(3) const fastify = Fastify() const contentTypeParser = fastify[keys.kContentTypeParser] - contentTypeParser.remove('application/json') - + t.ok(contentTypeParser.remove('application/json')) t.notOk(contentTypeParser.customParsers['application/json']) t.notOk(contentTypeParser.parserList.find(parser => parser === 'application/json')) }) test('should remove RegExp parser', t => { - t.plan(2) + t.plan(3) const fastify = Fastify() fastify.addContentTypeParser(/^text\/*/, first) const contentTypeParser = fastify[keys.kContentTypeParser] - contentTypeParser.remove(/^text\/*/) - + t.ok(contentTypeParser.remove(/^text\/*/)) t.notOk(contentTypeParser.customParsers[/^text\/*/]) t.notOk(contentTypeParser.parserRegExpList.find(parser => parser.toString() === /^text\/*/.toString())) }) @@ -289,23 +287,22 @@ test('remove', t => { t.throws(() => fastify[keys.kContentTypeParser].remove(12), FST_ERR_CTP_INVALID_TYPE) }) - test('should not throw error if content type does not exist', t => { + test('should return false if content type does not exist', t => { t.plan(1) const fastify = Fastify() - t.doesNotThrow(() => fastify[keys.kContentTypeParser].remove('image/png')) + t.notOk(fastify[keys.kContentTypeParser].remove('image/png')) }) test('should not remove any content type parser if content type does not exist', t => { - t.plan(1) + t.plan(2) const fastify = Fastify() const contentTypeParser = fastify[keys.kContentTypeParser] - contentTypeParser.remove('image/png') - + t.notOk(contentTypeParser.remove('image/png')) t.same(contentTypeParser.customParsers.size, 2) }) From ccec18a28c0bb807fecb588a2ba0ab80267ae510 Mon Sep 17 00:00:00 2001 From: Adibla <32988879+Adibla@users.noreply.github.com> Date: Mon, 6 Feb 2023 00:29:44 +0100 Subject: [PATCH 0207/1295] docs: update Ecosystem.md (#4551) * docs: update Ecosystem.md * docs: new line Ecosystem.md --- docs/Guides/Ecosystem.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 0f282922e16..01850c1931a 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -537,6 +537,8 @@ section. - [`fastify-server-session`](https://github.com/jsumners/fastify-server-session) A session plugin with support for arbitrary backing caches via `fastify-caching`. +- [`fastify-shared-schema`](https://github.com/Adibla/fastify-shared-schema) Plugin + for sharing schemas between different routes. - [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik plugin, with this you can use slonik in every part of your server. - [`fastify-slow-down`](https://github.com/nearform/fastify-slow-down) A plugin @@ -628,7 +630,6 @@ section. and lightweight Sequelize plugin for Fastify. - [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple and updated Typeorm plugin for use with Fastify. - #### [Community Tools](#community-tools) - [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows) Reusable workflows for use in the Fastify plugin From e2102915ee5c3856c8d1a81c56a0b328b6036749 Mon Sep 17 00:00:00 2001 From: Gilles Marchand Date: Mon, 6 Feb 2023 14:59:36 +0100 Subject: [PATCH 0208/1295] docs(ecosystem): add fastify-204 (#4504) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 01850c1931a..45e5c38b2e7 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -201,6 +201,8 @@ section. using Fastify without the need of consuming a port on Electron apps. - [`fast-water`](https://github.com/tswayne/fast-water) A Fastify plugin for waterline. Decorates Fastify with waterline models. +- [`fastify-204`](https://github.com/Shiva127/fastify-204) Fastify plugin that + return 204 status on empty response. - [`fastify-405`](https://github.com/Eomm/fastify-405) Fastify plugin that adds 405 HTTP status to your routes - [`fastify-allow`](https://github.com/mattbishop/fastify-allow) Fastify plugin From fe9b1029bc9435486e2833547fb531801fcac50f Mon Sep 17 00:00:00 2001 From: KaKa Date: Tue, 7 Feb 2023 18:25:49 +0800 Subject: [PATCH 0209/1295] docs: add fastify-delay-request (#4569) * docs: add fastify-delay-request The plugin that implement https://github.com/fastify/fastify/issues/2500 using `p-queue`. The test is flaky, I have no idea why `macOS` failed. But the implementation should be good enough for production use. * chore: update plugin description Co-authored-by: Frazer Smith --------- Co-authored-by: Frazer Smith --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 45e5c38b2e7..e9ca25d0886 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -282,6 +282,9 @@ section. functions. - [`fastify-decorators`](https://github.com/L2jLiga/fastify-decorators) Fastify plugin that provides the set of TypeScript decorators. +- [`fastify-delay-request`](https://github.com/climba03003/fastify-delay-request) + Fastify plugin that allows requests to be delayed whilst a task the response is + dependent on is run, such as a resource intensive process. - [`fastify-disablecache`](https://github.com/Fdawgs/fastify-disablecache) Fastify plugin to disable client-side caching, inspired by [nocache](https://github.com/helmetjs/nocache). From 084bd6034e284a5edad786eea93a8a46d3fb73b5 Mon Sep 17 00:00:00 2001 From: Dimitris Klouvas Date: Tue, 7 Feb 2023 19:07:34 +0200 Subject: [PATCH 0210/1295] Update Ecosystem.md with @clerk/fastify plugin (#4571) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index e9ca25d0886..7bc371e4b90 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -140,6 +140,8 @@ section. - [`@applicazza/fastify-nextjs`](https://github.com/applicazza/fastify-nextjs) Alternate Fastify and Next.js integration. +- [`@clerk/fastify`](https://github.com/clerkinc/javascript/tree/main/packages/fastify) + Add authentication and user management to your Fastify application with Clerk. - [`@coobaha/typed-fastify`](https://github.com/Coobaha/typed-fastify) Strongly typed routes with a runtime validation using JSON schema generated from types. - [`@dnlup/fastify-doc`](https://github.com/dnlup/fastify-doc) A plugin for From c7d8d2024ba519208fdbf63ea5573e9a42b95063 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 7 Feb 2023 17:07:49 +0000 Subject: [PATCH 0211/1295] docs(ecosystem): add fastify-json-to-xml plugin (#4572) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 7bc371e4b90..7be8801a9c5 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -376,6 +376,8 @@ section. - [`fastify-ip`](https://github.com/metcoder95/fastify-ip) A plugin for Fastify that allows you to infer a request ID by a given set of custom Request headers. +- [`fastify-json-to-xml`](https://github.com/Fdawgs/fastify-json-to-xml) Fastify + plugin to serialize JSON responses into XML. - [`fastify-jwt-authz`](https://github.com/Ethan-Arrowood/fastify-jwt-authz) JWT user scope verifier. - [`fastify-jwt-webapp`](https://github.com/charlesread/fastify-jwt-webapp) JWT From d14a10fed5f30cf4a14576b9b5994b7c34d9f9b8 Mon Sep 17 00:00:00 2001 From: Alain Date: Thu, 9 Feb 2023 12:47:16 +0000 Subject: [PATCH 0212/1295] #4521 Replace native errors with @fastify/errors (#4554) --- docs/Reference/Errors.md | 113 ++++++++++++++++++++++++- fastify.js | 53 +++++++----- lib/contentTypeParser.js | 9 +- lib/errors.js | 100 ++++++++++++++++++++++ lib/fourOhFour.js | 5 +- lib/hooks.js | 3 +- lib/pluginUtils.js | 7 +- lib/route.js | 29 ++++--- lib/server.js | 6 +- lib/validation.js | 6 +- test/close.test.js | 4 +- test/http2/unknown-http-method.test.js | 1 + test/internals/reply.test.js | 2 +- test/validation-error-handling.test.js | 6 +- types/errors.d.ts | 22 ++++- 15 files changed, 314 insertions(+), 52 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index f925490ec52..f919e222be2 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -133,7 +133,43 @@ fastify.listen({ port: 3000 }, function (err, address) { 404 Not Found. +#### FST_ERR_OPTIONS_NOT_OBJ + + +Fastify options must be an object. + +#### FST_ERR_QSP_NOT_FN + + +QueryStringParser option should be a function. + +#### FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN + + +SchemaController.bucket option should be a function. + +#### FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN + + +SchemaErrorFormatter option should be a non async function. + +#### FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ + + +ajv.customOptions option should be an object. + +#### FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR + + +ajv.plugins option should be an array. + +#### FST_ERR_VERSION_CONSTRAINT_NOT_STR + + +Version constraint should be a string. + + #### FST_ERR_CTP_ALREADY_PRESENT @@ -184,6 +220,16 @@ Request body size did not match `Content-Length`. Body cannot be empty when content-type is set to `application/json`. +#### FST_ERR_CTP_INSTANCE_ALREADY_STARTED + + +Fastify is already started. + +#### FST_ERR_INSTANCE_ALREADY_LISTENING + + +Fastify instance is already listening. + #### FST_ERR_DEC_ALREADY_PRESENT @@ -214,10 +260,15 @@ The hook name must be a string. The hook callback must be a function. +#### FST_ERR_HOOK_NOT_SUPPORTED + + +The hook is not supported. + #### FST_ERR_MISSING_MIDDLEWARE -You must register a plugin for handling middlewares, +You must register a plugin for handling middlewares, visit [`Middleware`](./Middleware.md) for more info. @@ -278,7 +329,7 @@ Missing serialization function. #### FST_ERR_REQ_INVALID_VALIDATION_INVOCATION -Invalid validation invocation. Missing validation function for +Invalid validation invocation. Missing validation function for HTTP part nor schema provided. #### FST_ERR_SCH_MISSING_ID @@ -306,6 +357,11 @@ The JSON schema provided for validation to a route is not valid. The JSON schema provided for serialization of a route response is not valid. +#### FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX + + +Response schemas should be nested under a valid status code (2XX). + #### FST_ERR_HTTP2_INVALID_VERSION @@ -319,7 +375,7 @@ Invalid initialization options. #### FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE -Cannot set forceCloseConnections to `idle` as your HTTP server +Cannot set forceCloseConnections to `idle` as your HTTP server does not support `closeIdleConnections` method. @@ -347,6 +403,46 @@ The `defaultRoute` type should be a function. URL must be a string. +#### FST_ERR_ROUTE_OPTIONS_NOT_OBJ + + +Options for the route must be an object. + +#### FST_ERR_ROUTE_DUPLICATED_HANDLER + + +Duplicate handler for the route is not allowed. + +#### FST_ERR_ROUTE_HANDLER_NOT_FN + + +Handler for the route must be a function. + +#### FST_ERR_ROUTE_MISSING_HANDLER + + +Missing handler function for the route. + +#### FST_ERR_ROUTE_METHOD_NOT_SUPPORTED + + +Method is not supported for the route. + +#### FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED + + +Body validation schema route is not supported. + +#### FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT + + +BodyLimit option must be an integer. + +#### FST_ERR_ROUTE_REWRITE_NOT_STR + + +Rewrite url needs to be of type "string". + #### FST_ERR_REOPENED_CLOSE_SERVER @@ -363,26 +459,37 @@ Fastify is already listening. Installed Fastify plugin mismatched expected version. + #### FST_ERR_PLUGIN_CALLBACK_NOT_FN Callback for a hook is not a function (mapped directly from `avvio`) + #### FST_ERR_PLUGIN_NOT_VALID Plugin must be a function or a promise. + #### FST_ERR_ROOT_PLG_BOOTED Root plugin has already booted (mapped directly from `avvio`) + #### FST_ERR_PARENT_PLUGIN_BOOTED Impossible to load plugin because the parent (mapped directly from `avvio`) + #### FST_ERR_PLUGIN_TIMEOUT Plugin did not start in time. Default timeout (in millis): `10000` + + + +#### FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE + +The decorator is not present in the instance. diff --git a/fastify.js b/fastify.js index 4f30beaeb8e..b55d6a4dee7 100644 --- a/fastify.js +++ b/fastify.js @@ -59,7 +59,17 @@ const { defaultInitOptions } = getSecuredInitialConfig const { FST_ERR_ASYNC_CONSTRAINT, FST_ERR_BAD_URL, - FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE + FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE, + FST_ERR_OPTIONS_NOT_OBJ, + FST_ERR_QSP_NOT_FN, + FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN, + FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ, + FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR, + FST_ERR_VERSION_CONSTRAINT_NOT_STR, + FST_ERR_INSTANCE_ALREADY_LISTENING, + FST_ERR_REOPENED_CLOSE_SERVER, + FST_ERR_ROUTE_REWRITE_NOT_STR, + FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN } = errorCodes const { buildErrorHandler } = require('./lib/error-handler.js') @@ -90,15 +100,15 @@ function fastify (options) { options = options || {} if (typeof options !== 'object') { - throw new TypeError('Options must be an object') + throw new FST_ERR_OPTIONS_NOT_OBJ() } if (options.querystringParser && typeof options.querystringParser !== 'function') { - throw new Error(`querystringParser option should be a function, instead got '${typeof options.querystringParser}'`) + throw new FST_ERR_QSP_NOT_FN(typeof options.querystringParser) } if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') { - throw new Error(`schemaController.bucket option should be a function, instead got '${typeof options.schemaController.bucket}'`) + throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket) } validateBodyLimitOption(options.bodyLimit) @@ -117,10 +127,10 @@ function fastify (options) { // Ajv options if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') { - throw new Error(`ajv.customOptions option should be an object, instead got '${typeof ajvOptions.customOptions}'`) + throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions) } if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) { - throw new Error(`ajv.plugins option should be an array, instead got '${typeof ajvOptions.plugins}'`) + throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins) } // Instance Fastify components @@ -156,7 +166,7 @@ function fastify (options) { deriveConstraint: options.versioning.deriveVersion, validate (value) { if (typeof value !== 'string') { - throw new Error('Version constraint should be a string.') + throw new FST_ERR_VERSION_CONSTRAINT_NOT_STR() } } } @@ -463,7 +473,7 @@ function fastify (options) { return fastify function throwIfAlreadyStarted (msg) { - if (fastify[kState].started) throw new Error(msg) + if (fastify[kState].started) throw new FST_ERR_INSTANCE_ALREADY_LISTENING(msg) } // HTTP injection handling @@ -479,7 +489,7 @@ function fastify (options) { if (fastify[kState].started) { if (fastify[kState].closing) { // Force to return an error - const error = new Error('Server is closed') + const error = new FST_ERR_REOPENED_CLOSE_SERVER() if (cb) { cb(error) return @@ -564,7 +574,7 @@ function fastify (options) { // wrapper that we expose to the user for hooks handling function addHook (name, fn) { - throwIfAlreadyStarted('Cannot call "addHook" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "addHook"!') if (fn == null) { throw new errorCodes.FST_ERR_HOOK_INVALID_HANDLER(name, fn) @@ -607,7 +617,7 @@ function fastify (options) { // wrapper that we expose to the user for schemas handling function addSchema (schema) { - throwIfAlreadyStarted('Cannot call "addSchema" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "addSchema"!') this[kSchemaController].add(schema) this[kChildren].forEach(child => child.addSchema(schema)) return this @@ -697,33 +707,33 @@ function fastify (options) { } function setNotFoundHandler (opts, handler) { - throwIfAlreadyStarted('Cannot call "setNotFoundHandler" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "setNotFoundHandler"!') fourOhFour.setNotFoundHandler.call(this, opts, handler, avvio, router.routeHandler) return this } function setValidatorCompiler (validatorCompiler) { - throwIfAlreadyStarted('Cannot call "setValidatorCompiler" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "setValidatorCompiler"!') this[kSchemaController].setValidatorCompiler(validatorCompiler) return this } function setSchemaErrorFormatter (errorFormatter) { - throwIfAlreadyStarted('Cannot call "setSchemaErrorFormatter" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "setSchemaErrorFormatter"!') validateSchemaErrorFormatter(errorFormatter) this[kSchemaErrorFormatter] = errorFormatter.bind(this) return this } function setSerializerCompiler (serializerCompiler) { - throwIfAlreadyStarted('Cannot call "setSerializerCompiler" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "setSerializerCompiler"!') this[kSchemaController].setSerializerCompiler(serializerCompiler) return this } function setSchemaController (schemaControllerOpts) { - throwIfAlreadyStarted('Cannot call "setSchemaController" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "setSchemaController"!') const old = this[kSchemaController] const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts)) this[kSchemaController] = schemaController @@ -733,7 +743,7 @@ function fastify (options) { } function setReplySerializer (replySerializer) { - throwIfAlreadyStarted('Cannot call "setReplySerializer" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "setReplySerializer"!') this[kReplySerializerDefault] = replySerializer return this @@ -741,7 +751,7 @@ function fastify (options) { // wrapper that we expose to the user for configure the custom error handler function setErrorHandler (func) { - throwIfAlreadyStarted('Cannot call "setErrorHandler" when fastify instance is already started!') + throwIfAlreadyStarted('Cannot call "setErrorHandler"!') this[kErrorHandler] = buildErrorHandler(this[kErrorHandler], func.bind(this)) return this @@ -766,7 +776,8 @@ function fastify (options) { if (typeof url === 'string') { req.url = url } else { - req.destroy(new Error(`Rewrite url for "${req.url}" needs to be of type "string" but received "${typeof url}"`)) + const err = new FST_ERR_ROUTE_REWRITE_NOT_STR(req.url, typeof url) + req.destroy(err) } } } @@ -779,9 +790,9 @@ fastify.errorCodes = errorCodes function validateSchemaErrorFormatter (schemaErrorFormatter) { if (typeof schemaErrorFormatter !== 'function') { - throw new Error(`schemaErrorFormatter option should be a function, instead got ${typeof schemaErrorFormatter}`) + throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN(typeof schemaErrorFormatter) } else if (schemaErrorFormatter.constructor.name === 'AsyncFunction') { - throw new Error('schemaErrorFormatter option should not be an async function') + throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN('AsyncFunction') } } diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 80401b66b25..10f4bff5f81 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -24,7 +24,8 @@ const { FST_ERR_CTP_BODY_TOO_LARGE, FST_ERR_CTP_INVALID_MEDIA_TYPE, FST_ERR_CTP_INVALID_CONTENT_LENGTH, - FST_ERR_CTP_EMPTY_JSON_BODY + FST_ERR_CTP_EMPTY_JSON_BODY, + FST_ERR_CTP_INSTANCE_ALREADY_STARTED } = require('./errors') function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) { @@ -314,7 +315,7 @@ function buildContentTypeParser (c) { function addContentTypeParser (contentType, opts, parser) { if (this[kState].started) { - throw new Error('Cannot call "addContentTypeParser" when fastify instance is already started!') + throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('addContentTypeParser') } if (typeof opts === 'function') { @@ -340,7 +341,7 @@ function hasContentTypeParser (contentType) { function removeContentTypeParser (contentType) { if (this[kState].started) { - throw new Error('Cannot call "removeContentTypeParser" when fastify instance is already started!') + throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('removeContentTypeParser') } if (Array.isArray(contentType)) { @@ -354,7 +355,7 @@ function removeContentTypeParser (contentType) { function removeAllContentTypeParsers () { if (this[kState].started) { - throw new Error('Cannot call "removeAllContentTypeParsers" when fastify instance is already started!') + throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('removeAllContentTypeParsers') } this[kContentTypeParser].removeAll() diff --git a/lib/errors.js b/lib/errors.js index 3ef8e568a4f..8d51dd8094e 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -11,6 +11,42 @@ const codes = { 'Not Found', 404 ), + FST_ERR_OPTIONS_NOT_OBJ: createError( + 'FST_ERR_OPTIONS_NOT_OBJ', + 'Options must be an object', + 500, + TypeError + ), + FST_ERR_QSP_NOT_FN: createError( + 'FST_ERR_QSP_NOT_FN', + "querystringParser option should be a function, instead got '%s'", + 500 + ), + FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN: createError( + 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN', + "schemaController.bucket option should be a function, instead got '%s'", + 500 + ), + FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN: createError( + 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN', + "schemaErrorFormatter option should be a non async function. Instead got '%s'.", + 500 + ), + FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ: createError( + 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ', + "sajv.customOptions option should be an object, instead got '%s'", + 500 + ), + FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR: createError( + 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR', + "sajv.plugins option should be an array, instead got '%s'", + 500 + ), + FST_ERR_VERSION_CONSTRAINT_NOT_STR: createError( + 'FST_ERR_VERSION_CONSTRAINT_NOT_STR', + 'Version constraint should be a string.', + 500 + ), /** * ContentTypeParser @@ -65,6 +101,11 @@ const codes = { "Body cannot be empty when content-type is set to 'application/json'", 400 ), + FST_ERR_CTP_INSTANCE_ALREADY_STARTED: createError( + 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED', + 'Cannot call "%s" when fastify instance is already started!', + 400 + ), /** * decorate @@ -107,6 +148,12 @@ const codes = { 500, TypeError ), + FST_ERR_HOOK_NOT_SUPPORTED: createError( + 'FST_ERR_HOOK_NOT_SUPPORTED', + '%s hook not supported!', + 500, + TypeError + ), /** * Middlewares @@ -207,6 +254,10 @@ const codes = { 'FST_ERR_SCH_SERIALIZATION_BUILD', 'Failed building the serialization schema for %s: %s, due to error %s' ), + FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX: createError( + 'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX', + 'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }' + ), /** * http2 @@ -256,6 +307,47 @@ const codes = { "URL must be a string. Received '%s'", 400 ), + FST_ERR_ROUTE_OPTIONS_NOT_OBJ: createError( + 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ', + 'Options for "%s:%s" route must be an object', + 500 + ), + FST_ERR_ROUTE_DUPLICATED_HANDLER: createError( + 'FST_ERR_ROUTE_DUPLICATED_HANDLER', + 'Duplicate handler for "%s:%s" route is not allowed!', + 500 + ), + FST_ERR_ROUTE_HANDLER_NOT_FN: createError( + 'FST_ERR_ROUTE_HANDLER_NOT_FN', + 'Error Handler for %s:%s route, if defined, must be a function', + 500 + ), + FST_ERR_ROUTE_MISSING_HANDLER: createError( + 'FST_ERR_ROUTE_MISSING_HANDLER', + 'Missing handler function for "%s:%s" route.', + 500 + ), + FST_ERR_ROUTE_METHOD_NOT_SUPPORTED: createError( + 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', + '%s method is not supported.', + 500 + ), + FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED: createError( + 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', + 'Body validation schema for %s:%s route is not supported!', + 500 + ), + FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT: createError( + 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', + "'bodyLimit' option must be an integer > 0. Got '%s'", + 500, + TypeError + ), + FST_ERR_ROUTE_REWRITE_NOT_STR: createError( + 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', + 'Rewrite url for "%s" needs to be of type "string" but received "%s"', + 500 + ), /** * again listen when close server @@ -268,6 +360,10 @@ const codes = { 'FST_ERR_REOPENED_SERVER', 'Fastify is already listening' ), + FST_ERR_INSTANCE_ALREADY_LISTENING: createError( + 'FST_ERR_INSTANCE_ALREADY_LISTENING', + 'Fastify instance is already listening. %s' + ), /** * plugin @@ -276,6 +372,10 @@ const codes = { 'FST_ERR_PLUGIN_VERSION_MISMATCH', "fastify-plugin: %s - expected '%s' fastify version, '%s' is installed" ), + FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE: createError( + 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE', + "The decorator '%s'%s is not present in %s" + ), /** * Avvio Errors diff --git a/lib/fourOhFour.js b/lib/fourOhFour.js index b8ad4e73e4d..32921a130a0 100644 --- a/lib/fourOhFour.js +++ b/lib/fourOhFour.js @@ -22,6 +22,9 @@ const fourOhFourContext = { onError: [], errorHandler: buildErrorHandler() } +const { + FST_ERR_NOT_FOUND +} = require('./errors') /** * Each fastify instance have a: @@ -178,7 +181,7 @@ function fourOhFour (options) { request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it') request.log.warn(router.prettyPrint()) - reply.code(404).send(new Error('Not Found')) + reply.code(404).send(new FST_ERR_NOT_FOUND()) } } diff --git a/lib/hooks.js b/lib/hooks.js index 31254781775..57cb479b444 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -23,6 +23,7 @@ const { FST_ERR_HOOK_INVALID_HANDLER, FST_ERR_SEND_UNDEFINED_ERR, FST_ERR_HOOK_TIMEOUT, + FST_ERR_HOOK_NOT_SUPPORTED, AVVIO_ERRORS_MAP, appendStackTrace } = require('./errors') @@ -50,7 +51,7 @@ function Hooks () { Hooks.prototype.validate = function (hook, fn) { if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE() if (supportedHooks.indexOf(hook) === -1) { - throw new Error(`${hook} hook not supported!`) + throw new FST_ERR_HOOK_NOT_SUPPORTED(hook) } if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(fn)) } diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 84f9b6a11a3..ecb456aa493 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -7,7 +7,10 @@ const { kTestInternals } = require('./symbols.js') const { exist, existReply, existRequest } = require('./decorate') -const { FST_ERR_PLUGIN_VERSION_MISMATCH } = require('./errors') +const { + FST_ERR_PLUGIN_VERSION_MISMATCH, + FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE +} = require('./errors') function getMeta (fn) { return fn[Symbol.for('plugin-meta')] @@ -92,7 +95,7 @@ function _checkDecorators (that, instance, decorators, name) { decorators.forEach(decorator => { const withPluginName = typeof name === 'string' ? ` required by '${name}'` : '' if (!checks[instance].call(that, decorator)) { - throw new Error(`The decorator '${decorator}'${withPluginName} is not present in ${instance}`) + throw new FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE(decorator, withPluginName, instance) } }) } diff --git a/lib/route.js b/lib/route.js index ae659c5bd04..9143f7bdc86 100644 --- a/lib/route.js +++ b/lib/route.js @@ -21,7 +21,14 @@ const { FST_ERR_DUPLICATED_ROUTE, FST_ERR_INVALID_URL, FST_ERR_SEND_UNDEFINED_ERR, - FST_ERR_HOOK_INVALID_HANDLER + FST_ERR_HOOK_INVALID_HANDLER, + FST_ERR_ROUTE_OPTIONS_NOT_OBJ, + FST_ERR_ROUTE_DUPLICATED_HANDLER, + FST_ERR_ROUTE_HANDLER_NOT_FN, + FST_ERR_ROUTE_MISSING_HANDLER, + FST_ERR_ROUTE_METHOD_NOT_SUPPORTED, + FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED, + FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT } = require('./errors') const { @@ -109,7 +116,7 @@ function buildRouting (options) { } function addConstraintStrategy (strategy) { - throwIfAlreadyStarted('Cannot add constraint strategy when fastify instance is already started!') + throwIfAlreadyStarted('Cannot add constraint strategy!') return router.addConstraintStrategy(strategy) } @@ -132,12 +139,12 @@ function buildRouting (options) { options = {} } else if (handler && typeof handler === 'function') { if (Object.prototype.toString.call(options) !== '[object Object]') { - throw new Error(`Options for ${method}:${url} route must be an object`) + throw new FST_ERR_ROUTE_OPTIONS_NOT_OBJ(method, url) } else if (options.handler) { if (typeof options.handler === 'function') { - throw new Error(`Duplicate handler for ${method}:${url} route is not allowed!`) + throw new FST_ERR_ROUTE_DUPLICATED_HANDLER(method, url) } else { - throw new Error(`Handler for ${method}:${url} route must be a function`) + throw new FST_ERR_ROUTE_HANDLER_NOT_FN(method, url) } } } @@ -171,7 +178,7 @@ function buildRouting (options) { // we need to clone a set of initial options for HEAD route const headOpts = shouldExposeHead && options.method === 'GET' ? { ...options } : null - throwIfAlreadyStarted('Cannot add route when fastify instance is already started!') + throwIfAlreadyStarted('Cannot add route!') const path = opts.url || opts.path || '' @@ -185,11 +192,11 @@ function buildRouting (options) { } if (!opts.handler) { - throw new Error(`Missing handler function for ${opts.method}:${path} route.`) + throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path) } if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') { - throw new Error(`Error Handler for ${opts.method}:${path} route, if defined, must be a function`) + throw new FST_ERR_ROUTE_HANDLER_NOT_FN(opts.method, path) } validateBodyLimitOption(opts.bodyLimit) @@ -499,18 +506,18 @@ function handleTimeout () { function validateMethodAndSchemaBodyOption (method, path, schema) { if (supportedMethods.indexOf(method) === -1) { - throw new Error(`${method} method is not supported!`) + throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method) } if ((method === 'GET' || method === 'HEAD') && schema && schema.body) { - throw new Error(`Body validation schema for ${method}:${path} route is not supported!`) + throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path) } } function validateBodyLimitOption (bodyLimit) { if (bodyLimit === undefined) return if (!Number.isInteger(bodyLimit) || bodyLimit <= 0) { - throw new TypeError(`'bodyLimit' option must be an integer > 0. Got '${bodyLimit}'`) + throw new FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT(bodyLimit) } } diff --git a/lib/server.js b/lib/server.js index ca1107abe05..44ecd178c8f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -6,7 +6,11 @@ const dns = require('dns') const warnings = require('./warnings') const { kState, kOptions, kServerBindings } = require('./symbols') -const { FST_ERR_HTTP2_INVALID_VERSION, FST_ERR_REOPENED_CLOSE_SERVER, FST_ERR_REOPENED_SERVER } = require('./errors') +const { + FST_ERR_HTTP2_INVALID_VERSION, + FST_ERR_REOPENED_CLOSE_SERVER, + FST_ERR_REOPENED_SERVER +} = require('./errors') module.exports.createServer = createServer module.exports.compileValidateHTTPVersion = compileValidateHTTPVersion diff --git a/lib/validation.js b/lib/validation.js index 321b922c182..b56bc271f36 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -9,6 +9,10 @@ const { } = require('./symbols') const scChecker = /^[1-5]{1}[0-9]{2}$|^[1-5]xx$|^default$/ +const { + FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX +} = require('./errors') + function compileSchemasForSerialization (context, compile) { if (!context.schema || !context.schema.response) { return @@ -19,7 +23,7 @@ function compileSchemasForSerialization (context, compile) { const schema = context.schema.response[statusCode] statusCode = statusCode.toLowerCase() if (!scChecker.exec(statusCode)) { - throw new Error('response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') + throw new FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX() } if (schema.content) { diff --git a/test/close.test.js b/test/close.test.js index c2abf38a3c6..a8bc603a2b8 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -165,7 +165,7 @@ test('Should return error while closing (promise) - injection', t => { url: '/' }).catch(err => { t.ok(err) - t.equal(err.message, 'Server is closed') + t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') }) }, 100) }) @@ -197,7 +197,7 @@ test('Should return error while closing (callback) - injection', t => { url: '/' }, (err, res) => { t.ok(err) - t.equal(err.message, 'Server is closed') + t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') }) }, 100) }) diff --git a/test/http2/unknown-http-method.test.js b/test/http2/unknown-http-method.test.js index fdc53fca277..9d821c00b7d 100644 --- a/test/http2/unknown-http-method.test.js +++ b/test/http2/unknown-http-method.test.js @@ -27,6 +27,7 @@ fastify.listen({ port: 0 }, err => { t.equal(res.headers[':status'], 404) t.same(JSON.parse(res.body), { statusCode: 404, + code: 'FST_ERR_NOT_FOUND', error: 'Not Found', message: 'Not Found' }) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 7d4b61c4b68..6a8da41e7ed 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -1740,7 +1740,7 @@ test('cannot set the replySerializer when the server is running', t => { fastify.setReplySerializer(() => {}) t.fail('this serializer should not be setup') } catch (e) { - t.equal(e.message, 'Cannot call "setReplySerializer" when fastify instance is already started!') + t.equal(e.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') } }) }) diff --git a/test/validation-error-handling.test.js b/test/validation-error-handling.test.js index 534258af74f..ff5a8b7a705 100644 --- a/test/validation-error-handling.test.js +++ b/test/validation-error-handling.test.js @@ -568,7 +568,7 @@ test('cannot create a fastify instance with wrong type of errorFormatter', t => } }) } catch (err) { - t.equal(err.message, 'schemaErrorFormatter option should not be an async function') + t.equal(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') } try { @@ -576,14 +576,14 @@ test('cannot create a fastify instance with wrong type of errorFormatter', t => schemaErrorFormatter: 500 }) } catch (err) { - t.equal(err.message, 'schemaErrorFormatter option should be a function, instead got number') + t.equal(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') } try { const fastify = Fastify() fastify.setSchemaErrorFormatter(500) } catch (err) { - t.equal(err.message, 'schemaErrorFormatter option should be a function, instead got number') + t.equal(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') } }) diff --git a/types/errors.d.ts b/types/errors.d.ts index 170acfef33c..406960b56c1 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -49,5 +49,25 @@ export type FastifyErrorCodes = Record< 'FST_ERR_PLUGIN_NOT_VALID' | 'FST_ERR_ROOT_PLG_BOOTED' | 'FST_ERR_PARENT_PLUGIN_BOOTED' | -'FST_ERR_PLUGIN_TIMEOUT' +'FST_ERR_PLUGIN_TIMEOUT' | +'FST_ERR_OPTIONS_NOT_OBJ' | +'FST_ERR_QSP_NOT_FN' | +'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN' | +'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN' | +'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ' | +'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR' | +'FST_ERR_VERSION_CONSTRAINT_NOT_STR' | +'FST_ERR_CTP_INSTANCE_ALREADY_STARTED' | +'FST_ERR_HOOK_NOT_SUPPORTED' | +'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX' | +'FST_ERR_ROUTE_OPTIONS_NOT_OBJ' | +'FST_ERR_ROUTE_DUPLICATED_HANDLER' | +'FST_ERR_ROUTE_HANDLER_NOT_FN' | +'FST_ERR_ROUTE_MISSING_HANDLER' | +'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED' | +'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED' | +'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT' | +'FST_ERR_ROUTE_REWRITE_NOT_STR' | +'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE' | +'FST_ERR_INSTANCE_ALREADY_LISTENING' , FastifyErrorConstructor> From d4a9851e370b9db7f1a605b0ffc26fc1ae9d6446 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Thu, 9 Feb 2023 13:48:50 +0100 Subject: [PATCH 0213/1295] fix: Return 408 on client timeout. (#4552) * fix: Return 408 on client timeout. * fix: Renamed file. * fix: Fixed types. * fix: Updated response code handling. * fix: Added host header. --- docs/Reference/Server.md | 19 +++++++++++++++--- fastify.d.ts | 9 ++++++++- fastify.js | 22 ++++++++++++++------- lib/server.js | 2 +- test/client-timeout.test.js | 38 ++++++++++++++++++++++++++++++++++++ test/types/fastify.test-d.ts | 1 + 6 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 test/client-timeout.test.js diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 27fa1294c4e..87ffeee8aad 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -9,6 +9,7 @@ options object which is used to customize the resulting instance. This document describes the properties available in that options object. - [Factory](#factory) + - [`http`](#http) - [`http2`](#http2) - [`https`](#https) - [`connectionTimeout`](#connectiontimeout) @@ -91,6 +92,18 @@ describes the properties available in that options object. - [errorHandler](#errorhandler) - [initialConfig](#initialconfig) +### `http` + + +An object used to configure the server's listening socket. The options +are the same as the Node.js core [`createServer` +method](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_createserver_options_requestlistener). + +This option is ignored if options [`http2`](#factory-http2) or +[`https`](#factory-https) are set. + ++ Default: `null` + ### `http2` @@ -494,7 +507,7 @@ request-id](./Logging.md#logging-request-id) section. Setting `requestIdHeader` to `false` will always use [genReqId](#genreqid) + Default: `'request-id'` - + ```js const fastify = require('fastify')({ requestIdHeader: 'x-custom-id', // -> use 'X-Custom-Id' header if available @@ -1440,9 +1453,9 @@ plugins are registered. If you would like to augment the behavior of the default arguments `fastify.setNotFoundHandler()` within the context of these registered plugins. -> Note: Some config properties from the request object will be +> Note: Some config properties from the request object will be > undefined inside the custom not found handler. E.g: -> `request.routerPath`, `routerMethod` and `context.config`. +> `request.routerPath`, `routerMethod` and `context.config`. > This method design goal is to allow calling the common not found route. > To return a per-route customized 404 response, you can do it in > the response itself. diff --git a/fastify.d.ts b/fastify.d.ts index 6749ba8d1b7..bb736e261d5 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -61,6 +61,13 @@ declare namespace fastify { https: https.ServerOptions | null } + export type FastifyHttpOptions< + Server extends http.Server, + Logger extends FastifyBaseLogger = FastifyBaseLogger + > = FastifyServerOptions & { + http?: http.ServerOptions | null + } + type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2 export interface ConnectionError extends Error { @@ -224,7 +231,7 @@ declare function fastify< Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts?: fastify.FastifyServerOptions): FastifyInstance & PromiseLike> +>(opts?: fastify.FastifyHttpOptions): FastifyInstance & PromiseLike> // CJS export // const fastify = require('fastify') diff --git a/fastify.js b/fastify.js index b55d6a4dee7..5a17c071767 100644 --- a/fastify.js +++ b/fastify.js @@ -630,22 +630,30 @@ function fastify (options) { return } - const body = JSON.stringify({ - error: http.STATUS_CODES['400'], - message: 'Client Error', - statusCode: 400 - }) + let body, errorCode, errorStatus, errorLabel + + if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') { + errorCode = '408' + errorStatus = http.STATUS_CODES[errorCode] + body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}` + errorLabel = 'timeout' + } else { + errorCode = '400' + errorStatus = http.STATUS_CODES[errorCode] + body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}` + errorLabel = 'error' + } // Most devs do not know what to do with this error. // In the vast majority of cases, it's a network error and/or some // config issue on the load balancer side. - this.log.trace({ err }, 'client error') + this.log.trace({ err }, `client ${errorLabel}`) // Copying standard node behaviour // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666 // If the socket is not writable, there is no reason to try to send data. if (socket.writable) { - socket.write(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`) + socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`) } socket.destroy(err) } diff --git a/lib/server.js b/lib/server.js index 44ecd178c8f..3e84bbeb250 100644 --- a/lib/server.js +++ b/lib/server.js @@ -288,7 +288,7 @@ function getServerInstance (options, httpHandler) { if (options.https) { server = https.createServer(options.https, httpHandler) } else { - server = http.createServer(httpHandler) + server = http.createServer(options.http, httpHandler) } server.keepAliveTimeout = options.keepAliveTimeout server.requestTimeout = options.requestTimeout diff --git a/test/client-timeout.test.js b/test/client-timeout.test.js new file mode 100644 index 00000000000..bc08792f7eb --- /dev/null +++ b/test/client-timeout.test.js @@ -0,0 +1,38 @@ +'use strict' + +const { test } = require('tap') +const fastify = require('..')({ requestTimeout: 5, http: { connectionsCheckingInterval: 1000 } }) +const { connect } = require('net') + +test('requestTimeout should return 408', t => { + t.plan(1) + + t.teardown(() => { + fastify.close() + }) + + fastify.post('/', async function (req, reply) { + await new Promise(resolve => setTimeout(resolve, 100)) + return reply.send({ hello: 'world' }) + }) + + fastify.listen({ port: 0 }, err => { + if (err) { + throw err + } + + let data = Buffer.alloc(0) + const socket = connect(fastify.server.address().port) + + socket.write('POST / HTTP/1.1\r\nHost: example.com\r\nConnection-Length: 1\r\n') + + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.equal( + data.toString('utf-8'), + 'HTTP/1.1 408 Request Timeout\r\nContent-Length: 71\r\nContent-Type: application/json\r\n\r\n{"error":"Request Timeout","message":"Client Timeout","statusCode":408}' + ) + t.end() + }) + }) +}) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index eac0a77e4d4..249eeeaa5ae 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -25,6 +25,7 @@ import { Socket } from 'net' // http server expectType & PromiseLike>>(fastify()) expectType & PromiseLike>>(fastify({})) +expectType & PromiseLike>>(fastify({ http: {} })) // https server expectType & PromiseLike>>(fastify({ https: {} })) expectType & PromiseLike>>(fastify({ https: null })) From 8605865dbd3625764b07142cb273f7bc446b22c7 Mon Sep 17 00:00:00 2001 From: Shyam Chen Date: Thu, 9 Feb 2023 22:03:53 +0800 Subject: [PATCH 0214/1295] docs(ecosystem): add fastify-cloudinary plugin (#4576) --- docs/Guides/Ecosystem.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 7be8801a9c5..3f0b78346e1 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -270,6 +270,10 @@ section. - [`fastify-cloudevents`](https://github.com/smartiniOnGitHub/fastify-cloudevents) Fastify plugin to generate and forward Fastify events in the Cloudevents format. +- [`fastify-cloudinary`](https://github.com/Vanilla-IceCream/fastify-cloudinary) + The Cloudinary Fastify SDK allows you to quickly and easily integrate your + application with Cloudinary. Effortlessly optimize and transform your cloud's + assets. - [`fastify-cockroachdb`](https://github.com/alex-ppg/fastify-cockroachdb) Fastify plugin to connect to a CockroachDB PostgreSQL instance via the Sequelize ORM. @@ -396,7 +400,7 @@ section. Fastify plugin to parse request language. - [`fastify-lcache`](https://github.com/denbon05/fastify-lcache) Lightweight cache plugin -- [`fastify-list-routes`](https://github.com/chuongtrh/fastify-list-routes) +- [`fastify-list-routes`](https://github.com/chuongtrh/fastify-list-routes) A simple plugin for Fastify list all available routes. - [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from a directory and inject the Fastify instance in each file. @@ -550,7 +554,7 @@ section. for sharing schemas between different routes. - [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik plugin, with this you can use slonik in every part of your server. -- [`fastify-slow-down`](https://github.com/nearform/fastify-slow-down) A plugin +- [`fastify-slow-down`](https://github.com/nearform/fastify-slow-down) A plugin to delay the response from the server. - [`fastify-socket.io`](https://github.com/alemagio/fastify-socket.io) a Socket.io plugin for Fastify. @@ -601,7 +605,7 @@ section. should use. - [`fastify-wamp-router`](https://github.com/lependu/fastify-wamp-router) Web Application Messaging Protocol router for Fastify. -- [`fastify-web-response`](https://github.com/erfanium/fastify-web-response) +- [`fastify-web-response`](https://github.com/erfanium/fastify-web-response) Enables returning web streams objects `Response` and `ReadableStream` in routes. - [`fastify-webpack-hmr`](https://github.com/lependu/fastify-webpack-hmr) Webpack hot module reloading plugin for Fastify. From 58d787fc2e525dbfa264fab5be9389e13dd182d5 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 9 Feb 2023 19:19:02 +0100 Subject: [PATCH 0215/1295] Fix crash when onReady hook throws (#4579) Signed-off-by: Matteo Collina --- lib/hooks.js | 12 ++++++++---- test/hooks.on-ready.test.js | 13 +++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/hooks.js b/lib/hooks.js index 57cb479b444..7e90b091512 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -150,10 +150,14 @@ function hookRunnerApplication (hookName, boot, server, cb) { return } - const ret = fn.call(server) - if (ret && typeof ret.then === 'function') { - ret.then(done, done) - return + try { + const ret = fn.call(server) + if (ret && typeof ret.then === 'function') { + ret.then(done, done) + return + } + } catch (error) { + err = error } done(err) // auto done diff --git a/test/hooks.on-ready.test.js b/test/hooks.on-ready.test.js index 034182b2648..08bbb0d0786 100644 --- a/test/hooks.on-ready.test.js +++ b/test/hooks.on-ready.test.js @@ -372,3 +372,16 @@ t.test('ready return registered', t => { .then(instance => { t.same(instance, fastify) }) .catch(err => { t.fail(err) }) }) + +t.test('do not crash with error in follow up onReady hook', async t => { + const fastify = Fastify() + + fastify.addHook('onReady', async function () { + }) + + fastify.addHook('onReady', function () { + throw new Error('kaboom') + }) + + await t.rejects(fastify.ready()) +}) From b09fa763c56706941dc6eb9eafaead6dff51b509 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 9 Feb 2023 19:21:30 +0100 Subject: [PATCH 0216/1295] Bumped v4.13.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 5a17c071767..f20fd3afdbe 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.12.0' +const VERSION = '4.13.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 05409641dac..84bb4156016 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.12.0", + "version": "4.13.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 9dd9c6ada3c0ce3d854c6a980873901cfc76fa13 Mon Sep 17 00:00:00 2001 From: CallMe AsYouFeel Date: Sat, 11 Feb 2023 23:04:44 +0900 Subject: [PATCH 0217/1295] fix: Schema error formatter type (#4570) * refactor: type SchemaErrorFormatter * fix: refactor and fix type SchemaErrorDataVar * fix: type test for validationContext --- fastify.d.ts | 6 +++--- test/types/fastify.test-d.ts | 2 +- types/instance.d.ts | 6 +++--- types/route.d.ts | 5 ++--- types/schema.d.ts | 4 ++++ 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/fastify.d.ts b/fastify.d.ts index bb736e261d5..1d16182e61f 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -20,7 +20,7 @@ import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './type import { FastifyReply } from './types/reply' import { FastifyRequest, RequestGenericInterface } from './types/request' import { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route' -import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError } from './types/schema' +import { FastifySchema, FastifySchemaCompiler, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema' import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider' import { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils' @@ -28,7 +28,7 @@ import { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaul declare module '@fastify/error' { interface FastifyError { validation?: fastify.ValidationResult[]; - validationContext?: 'body' | 'headers' | 'parameters' | 'querystring'; + validationContext?: SchemaErrorDataVar; } } @@ -149,7 +149,7 @@ declare namespace fastify { res: FastifyReply, RawReplyDefaultExpression, RequestGeneric, FastifyContextConfig, SchemaCompiler, TypeProvider> ) => void, rewriteUrl?: (req: RawRequestDefaultExpression) => string, - schemaErrorFormatter?: (errors: FastifySchemaValidationError[], dataVar: string) => Error, + schemaErrorFormatter?: SchemaErrorFormatter, /** * listener to error events emitted by client connections */ diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 249eeeaa5ae..813e77f5175 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -240,7 +240,7 @@ expectAssignable(ajvErrorObject) expectAssignable([ajvErrorObject]) expectAssignable('body') expectAssignable('headers') -expectAssignable('parameters') +expectAssignable('params') expectAssignable('querystring') const routeGeneric: RouteGenericInterface = {} diff --git a/types/instance.d.ts b/types/instance.d.ts index f2e39770a44..e93c2600e2c 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -13,8 +13,8 @@ import { FastifySchema, FastifySchemaCompiler, FastifySchemaControllerOptions, - FastifySchemaValidationError, - FastifySerializerCompiler + FastifySerializerCompiler, + SchemaErrorFormatter } from './schema' import { FastifyTypeProvider, @@ -530,7 +530,7 @@ export interface FastifyInstance< /* * Set the schema error formatter for all routes. */ - setSchemaErrorFormatter(errorFormatter: (errors: FastifySchemaValidationError[], dataVar: string) => Error): FastifyInstance; + setSchemaErrorFormatter(errorFormatter: SchemaErrorFormatter): FastifyInstance; /** * Add a content type parser */ diff --git a/types/route.d.ts b/types/route.d.ts index f8d2675cb43..455ff1a26f1 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -1,7 +1,7 @@ import { FastifyInstance } from './instance' import { FastifyRequest, RequestGenericInterface } from './request' import { FastifyReply, ReplyGenericInterface } from './reply' -import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, FastifySerializerCompiler } from './schema' +import { FastifySchema, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorFormatter } from './schema' import { HTTPMethods, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler } from './hooks' import { FastifyError } from '@fastify/error' @@ -41,8 +41,7 @@ export interface RouteShorthandOptions< constraints?: { [name: string]: any }, prefixTrailingSlash?: 'slash'|'no-slash'|'both'; errorHandler?: (this: FastifyInstance, error: FastifyError, request: FastifyRequest, reply: FastifyReply) => void; - // TODO: Change to actual type. - schemaErrorFormatter?: (errors: FastifySchemaValidationError[], dataVar: string) => Error; + schemaErrorFormatter?: SchemaErrorFormatter; // hooks onRequest?: onRequestHookHandler | onRequestHookHandler[]; diff --git a/types/schema.d.ts b/types/schema.d.ts index e5f5eea0341..094b6f0cf75 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -54,3 +54,7 @@ export interface FastifySchemaControllerOptions{ buildSerializer?: (externalSchemas: unknown, serializerOptsServerOption: FastifyServerOptions['serializerOpts']) => FastifySerializerCompiler; }; } + +export type SchemaErrorDataVar = 'body' | 'headers' | 'params' | 'querystring' + +export type SchemaErrorFormatter = (errors: FastifySchemaValidationError[], dataVar: SchemaErrorDataVar) => Error From c6a40ebe3ec9aaa2886d8ba7646fea88559412f6 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sat, 11 Feb 2023 16:07:41 +0100 Subject: [PATCH 0218/1295] Fix typescript integration bug with ajv-compiler (#4555) * add test * use ValidatorFactory and SerializerFactory --- fastify.d.ts | 8 +++---- package.json | 2 +- test/types/schema.test-d.ts | 42 ++++++++++++++++++++++++++++++++----- types/schema.d.ts | 9 ++++---- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/fastify.d.ts b/fastify.d.ts index 1d16182e61f..3ab38696562 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -3,9 +3,9 @@ import * as http2 from 'http2' import * as https from 'https' import { Socket } from 'net' -import { Options as AjvOptions, ValidatorCompiler } from '@fastify/ajv-compiler' +import { Options as AjvOptions, ValidatorFactory } from '@fastify/ajv-compiler' import { FastifyError } from '@fastify/error' -import { Options as FJSOptions, SerializerCompiler } from '@fastify/fast-json-stringify-compiler' +import { Options as FJSOptions, SerializerFactory } from '@fastify/fast-json-stringify-compiler' import { ConstraintStrategy, HTTPVersion } from 'find-my-way' import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request' @@ -134,8 +134,8 @@ declare namespace fastify { getSchemas(): Record; }; compilersFactory?: { - buildValidator?: ValidatorCompiler; - buildSerializer?: SerializerCompiler; + buildValidator?: ValidatorFactory; + buildSerializer?: SerializerFactory; }; }; return503OnClosing?: boolean, diff --git a/package.json b/package.json index 84bb4156016..6fde0fcb621 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "yup": "^0.32.11" }, "dependencies": { - "@fastify/ajv-compiler": "^3.3.1", + "@fastify/ajv-compiler": "^3.5.0", "@fastify/error": "^3.0.0", "@fastify/fast-json-stringify-compiler": "^4.1.0", "abstract-logging": "^2.0.1", diff --git a/test/types/schema.test-d.ts b/test/types/schema.test-d.ts index ef871b1badf..801fdb11fe3 100644 --- a/test/types/schema.test-d.ts +++ b/test/types/schema.test-d.ts @@ -1,9 +1,8 @@ -import { expectAssignable, expectError } from 'tsd' -import fastify, { FastifyInstance, FastifyRequest, FastifySchema } from '../../fastify' -import { RouteGenericInterface } from '../../types/route' -import { ContextConfigDefault } from '../../types/utils' -import { FastifyReply } from '../../types/reply' +import { expectAssignable } from 'tsd' +import fastify, { FastifyInstance, FastifySchema } from '../../fastify' import Ajv from 'ajv' +import { StandaloneValidator } from '@fastify/ajv-compiler' +import { StandaloneSerializer } from '@fastify/fast-json-stringify-compiler' const server = fastify() @@ -63,3 +62,36 @@ expectAssignable(server.setValidatorCompiler(server.setSerializerCompiler( () => data => JSON.stringify(data) )) + +// https://github.com/fastify/ajv-compiler/issues/95 +{ + const factory = StandaloneValidator({ + readMode: false, + storeFunction (routeOpts, schemaValidationCode) { } + }) + + const app = fastify({ + jsonShorthand: false, + schemaController: { + compilersFactory: { + buildValidator: factory + } + } + }) +} + +{ + const factory = StandaloneSerializer({ + readMode: false, + storeFunction (routeOpts, schemaValidationCode) { } + }) + + const app = fastify({ + jsonShorthand: false, + schemaController: { + compilersFactory: { + buildSerializer: factory + } + } + }) +} diff --git a/types/schema.d.ts b/types/schema.d.ts index 094b6f0cf75..3f0fe57a40b 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -1,5 +1,6 @@ -import { ValidatorCompiler } from '@fastify/ajv-compiler' -import { FastifyInstance, FastifyServerOptions } from '../fastify' +import { ValidatorFactory } from '@fastify/ajv-compiler' +import { SerializerFactory } from '@fastify/fast-json-stringify-compiler' +import { FastifyInstance } from '../fastify' /** * Schemas in Fastify follow the JSON-Schema standard. For this reason * we have opted to not ship strict schema based types. Instead we provide @@ -50,8 +51,8 @@ export interface FastifySchemaControllerOptions{ getSchemas(): Record; }; compilersFactory?: { - buildValidator?: ValidatorCompiler; - buildSerializer?: (externalSchemas: unknown, serializerOptsServerOption: FastifyServerOptions['serializerOpts']) => FastifySerializerCompiler; + buildValidator?: ValidatorFactory; + buildSerializer?: SerializerFactory; }; } From 2e9526e6245421570a5ef54d3676421f617dc4d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 15:35:50 +0000 Subject: [PATCH 0219/1295] build(deps-dev): bump yup from 0.32.11 to 1.0.0 (#4581) Bumps [yup](https://github.com/jquense/yup) from 0.32.11 to 1.0.0. - [Release notes](https://github.com/jquense/yup/releases) - [Changelog](https://github.com/jquense/yup/blob/master/CHANGELOG.md) - [Commits](https://github.com/jquense/yup/compare/v0.32.11...v1.0.0) --- updated-dependencies: - dependency-name: yup dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6fde0fcb621..70fe55e8524 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "typescript": "^4.8.3", "undici": "^5.10.0", "vary": "^1.1.2", - "yup": "^0.32.11" + "yup": "^1.0.0" }, "dependencies": { "@fastify/ajv-compiler": "^3.5.0", From 7cbad5819738183226184950259934de8ea90a2b Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Tue, 21 Feb 2023 02:40:40 -0800 Subject: [PATCH 0220/1295] feat: new onRequestAbort hook (#4582) * feat: Added onClientAbort hook. * test: Removed leftovers. * docs: Added client detection reliability disclaimer. * feat: Renamed hook. * feat: Handle errors and don't forward the reply. * chore: Updated docs. Co-authored-by: Frazer Smith * test: apply PR review suggestions Co-authored-by: Manuel Spigolon --------- Co-authored-by: Frazer Smith Co-authored-by: Manuel Spigolon --- docs/Reference/Hooks.md | 21 ++++ fastify.d.ts | 4 +- fastify.js | 4 + lib/context.js | 1 + lib/hooks.js | 42 +++++++- lib/route.js | 22 +++- test/hooks-async.test.js | 12 +++ test/hooks.test.js | 211 +++++++++++++++++++++++++++++++++++++ test/types/hooks.test-d.ts | 13 +++ types/hooks.d.ts | 38 +++++++ types/instance.d.ts | 27 ++++- types/route.d.ts | 3 +- 12 files changed, 392 insertions(+), 6 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 2a8c6f61005..a7fb412bc95 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -19,6 +19,7 @@ are Request/Reply hooks and application hooks: - [onSend](#onsend) - [onResponse](#onresponse) - [onTimeout](#ontimeout) + - [onRequestAbort](#onrequestabort) - [Manage Errors from a hook](#manage-errors-from-a-hook) - [Respond to a request from a hook](#respond-to-a-request-from-a-hook) - [Application Hooks](#application-hooks) @@ -267,6 +268,26 @@ service (if the `connectionTimeout` property is set on the Fastify instance). The `onTimeout` hook is executed when a request is timed out and the HTTP socket has been hanged up. Therefore, you will not be able to send data to the client. +### onRequestAbort + +```js +fastify.addHook('onRequestAbort', (request, reply, done) => { + // Some code + done() +}) +``` +Or `async/await`: +```js +fastify.addHook('onRequestAbort', async (request, reply) => { + // Some code + await asyncMethod() +}) +``` +The `onRequestAbort` hook is executed when a client closes the connection before +the entire request has been received. Therefore, you will not be able to send +data to the client. + +**Notice:** client abort detection is not completely reliable. See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md) ### Manage Errors from a hook If you get an error during the execution of your hook, just pass it to `done()` diff --git a/fastify.d.ts b/fastify.d.ts index 3ab38696562..497350dfd57 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -12,7 +12,7 @@ import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequest import { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser' import { FastifyContext, FastifyContextConfig } from './types/context' import { FastifyErrorCodes } from './types/errors' -import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './types/hooks' +import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler } from './types/hooks' import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel } from './types/logger' import { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' @@ -179,7 +179,7 @@ declare namespace fastify { FastifyError, // '@fastify/error' FastifySchema, FastifySchemaCompiler, // './types/schema' HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault, // './types/utils' - DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, // './types/hooks' + DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, // './types/hooks' FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory' FastifyTypeProvider, FastifyTypeProviderDefault, // './types/type-provider' FastifyErrorCodes, // './types/errors' diff --git a/fastify.js b/fastify.js index f20fd3afdbe..6f799059a2e 100644 --- a/fastify.js +++ b/fastify.js @@ -588,6 +588,10 @@ function fastify (options) { if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) { throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() } + } else if (name === 'onRequestAbort') { + if (fn.constructor.name === 'AsyncFunction' && fn.length !== 1) { + throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() + } } else { if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) { throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() diff --git a/lib/context.js b/lib/context.js index 226d09280de..f507f5144cc 100644 --- a/lib/context.js +++ b/lib/context.js @@ -48,6 +48,7 @@ function Context ({ this.preHandler = null this.onResponse = null this.preSerialization = null + this.onRequestAbort = null this.config = config this.errorHandler = errorHandler || server[kErrorHandler] this._middie = null diff --git a/lib/hooks.js b/lib/hooks.js index 7e90b091512..b6d9643b362 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -15,7 +15,8 @@ const lifecycleHooks = [ 'preHandler', 'onSend', 'onResponse', - 'onError' + 'onError', + 'onRequestAbort' ] const supportedHooks = lifecycleHooks.concat(applicationHooks) const { @@ -46,6 +47,7 @@ function Hooks () { this.onRegister = [] this.onReady = [] this.onTimeout = [] + this.onRequestAbort = [] } Hooks.prototype.validate = function (hook, fn) { @@ -74,6 +76,7 @@ function buildHooks (h) { hooks.onRoute = h.onRoute.slice() hooks.onRegister = h.onRegister.slice() hooks.onTimeout = h.onTimeout.slice() + hooks.onRequestAbort = h.onRequestAbort.slice() hooks.onReady = [] return hooks } @@ -246,6 +249,42 @@ function onSendHookRunner (functions, request, reply, payload, cb) { next() } +function onRequestAbortHookRunner (functions, runner, request, cb) { + let i = 0 + + function next (err) { + if (err || i === functions.length) { + cb(err, request) + return + } + + let result + try { + result = runner(functions[i++], request, next) + } catch (error) { + next(error) + return + } + if (result && typeof result.then === 'function') { + result.then(handleResolve, handleReject) + } + } + + function handleResolve () { + next() + } + + function handleReject (err) { + if (!err) { + err = new FST_ERR_SEND_UNDEFINED_ERR() + } + + cb(err, request) + } + + next() +} + function hookIterator (fn, request, reply, next) { if (reply.sent === true) return undefined return fn(request, reply, next) @@ -256,6 +295,7 @@ module.exports = { buildHooks, hookRunner, onSendHookRunner, + onRequestAbortHookRunner, hookIterator, hookRunnerApplication, lifecycleHooks, diff --git a/lib/route.js b/lib/route.js index 9143f7bdc86..d2624cd7fd7 100644 --- a/lib/route.js +++ b/lib/route.js @@ -3,7 +3,7 @@ const FindMyWay = require('find-my-way') const Context = require('./context') const handleRequest = require('./handleRequest') -const { hookRunner, hookIterator, lifecycleHooks } = require('./hooks') +const { hookRunner, hookIterator, onRequestAbortHookRunner, lifecycleHooks } = require('./hooks') const { supportedMethods } = require('./httpMethods') const { normalizeSchema } = require('./schemas') const { parseHeadOnSendHandlers } = require('./headRoute') @@ -484,6 +484,20 @@ function buildRouting (options) { runPreParsing(null, request, reply) } + if (context.onRequestAbort !== null) { + req.on('close', () => { + /* istanbul ignore else */ + if (req.aborted) { + onRequestAbortHookRunner( + context.onRequestAbort, + hookIterator, + request, + handleOnRequestAbortHooksErrors.bind(null, reply) + ) + } + }) + } + if (context.onTimeout !== null) { if (!request.raw.socket._meta) { request.raw.socket.on('timeout', handleTimeout) @@ -493,6 +507,12 @@ function buildRouting (options) { } } +function handleOnRequestAbortHooksErrors (reply, err) { + if (err) { + reply.log.error({ err }, 'onRequestAborted hook failed') + } +} + function handleTimeout () { const { context, request, reply } = this._meta hookRunner( diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index 8d801ba14f8..343fd5a169f 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -584,6 +584,18 @@ test('preHandler respond with a stream', t => { }) test('Should log a warning if is an async function with `done`', t => { + t.test('2 arguments', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addHook('onRequestAbort', async (req, done) => {}) + } catch (e) { + t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } + }) + t.test('3 arguments', t => { t.plan(2) const fastify = Fastify() diff --git a/test/hooks.test.js b/test/hooks.test.js index 742bf86849b..c8f8b95fb04 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -11,6 +11,10 @@ const split = require('split2') const symbols = require('../lib/symbols.js') const payload = { hello: 'world' } const proxyquire = require('proxyquire') +const { promisify } = require('util') +const { connect } = require('net') + +const sleep = promisify(setTimeout) process.removeAllListeners('warning') @@ -3404,3 +3408,210 @@ test('registering invalid hooks should throw an error', async t => { }) }, new Error('onSend hook should be a function, instead got [object Undefined]')) }) + +test('onRequestAbort should be triggered', t => { + const fastify = Fastify() + let order = 0 + + t.plan(3) + t.teardown(() => fastify.close()) + + fastify.addHook('onRequestAbort', function (req, done) { + t.equal(++order, 1, 'called in hook') + done() + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + }, + async onRequestAbort (req) { + t.equal(++order, 2, 'called in route') + } + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should support encapsulation', t => { + const fastify = Fastify() + let order = 0 + let child + + t.plan(6) + t.teardown(() => fastify.close()) + + fastify.addHook('onRequestAbort', function (req, done) { + t.equal(++order, 1, 'called in root') + t.strictSame(this.pluginName, child.pluginName) + done() + }) + + fastify.register(async function (_child, _, done) { + child = _child + + fastify.addHook('onRequestAbort', async function (req) { + t.equal(++order, 2, 'called in child') + t.strictSame(this.pluginName, child.pluginName) + }) + + child.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + }, + async onRequestAbort (_req) { + t.equal(++order, 3, 'called in route') + } + }) + + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should handle errors / 1', t => { + const fastify = Fastify() + + t.plan(2) + t.teardown(() => fastify.close()) + + fastify.addHook('onRequestAbort', function (req, done) { + process.nextTick(() => t.pass()) + done(new Error('KABOOM!')) + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should handle errors / 2', t => { + const fastify = Fastify() + + t.plan(2) + t.teardown(() => fastify.close()) + + fastify.addHook('onRequestAbort', function (req, done) { + process.nextTick(() => t.pass()) + throw new Error('KABOOM!') + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should handle async errors / 1', t => { + const fastify = Fastify() + + t.plan(2) + t.teardown(() => fastify.close()) + + fastify.addHook('onRequestAbort', async function (req) { + process.nextTick(() => t.pass()) + throw new Error('KABOOM!') + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should handle async errors / 2', t => { + const fastify = Fastify() + + t.plan(2) + t.teardown(() => fastify.close()) + + fastify.addHook('onRequestAbort', async function (req) { + process.nextTick(() => t.pass()) + return Promise.reject() + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 5470dd927fb..0444f5662e8 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -114,6 +114,14 @@ server.addHook('onError', function (request, reply, error, done) { expectType(done()) }) +server.addHook('onRequestAbort', function (request, done) { + expectType(this) + expectType(request) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + server.addHook('onRoute', function (opts) { expectType(this) expectType(opts) @@ -201,6 +209,11 @@ server.addHook('onError', async function (request, reply, error) { expectType(error) }) +server.addHook('onRequestAbort', async function (request) { + expectType(this) + expectType(request) +}) + server.addHook('onRegister', async (instance, opts) => { expectType(instance) expectType(opts) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 03efbf8f368..893cbf95af2 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -394,6 +394,44 @@ export interface onErrorAsyncHookHandler< ): Promise; } +/** + * `onRequestAbort` is useful if you need to monitor the if the client aborts the request (if the `request.raw.aborted` property is set to `true`). + * The `onRequestAbort` hook is executed when a client closes the connection before the entire request has been received. Therefore, you will not be able to send data to the client. + * Notice: client abort detection is not completely reliable. See: https://github.com/fastify/fastify/blob/main/docs/Guides/Detecting-When-Clients-Abort.md + */ +export interface onRequestAbortHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + done: HookHandlerDoneFunction + ): void; +} + +export interface onRequestAbortAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + ): Promise; +} + // Application Hooks /** diff --git a/types/instance.d.ts b/types/instance.d.ts index e93c2600e2c..f92626745d1 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -3,7 +3,7 @@ import { ConstraintStrategy, HTTPVersion } from 'find-my-way' import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' -import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' +import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' import { FastifyBaseLogger } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' @@ -393,6 +393,31 @@ export interface FastifyInstance< hook: onTimeoutAsyncHookHandler ): FastifyInstance; + /** + * `onRequestAbort` is useful if you need to monitor the if the client aborts the request (if the `request.raw.aborted` property is set to `true`). + * The `onRequestAbort` hook is executed when a client closes the connection before the entire request has been received. Therefore, you will not be able to send data to the client. + * Notice: client abort detection is not completely reliable. See: https://github.com/fastify/fastify/blob/main/docs/Guides/Detecting-When-Clients-Abort.md + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger + >( + name: 'onRequestAbort', + hook: onRequestAbortHookHandler + ): FastifyInstance; + + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger + >( + name: 'onRequestAbort', + hook: onRequestAbortAsyncHookHandler + ): FastifyInstance; + /** * This hook is useful if you need to do some custom error logging or add some specific header in case of error. * It is not intended for changing the error, and calling reply.send will throw an exception. diff --git a/types/route.d.ts b/types/route.d.ts index 455ff1a26f1..8769c39eff6 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -3,7 +3,7 @@ import { FastifyRequest, RequestGenericInterface } from './request' import { FastifyReply, ReplyGenericInterface } from './reply' import { FastifySchema, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorFormatter } from './schema' import { HTTPMethods, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' -import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler } from './hooks' +import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler, onRequestAbortHookHandler } from './hooks' import { FastifyError } from '@fastify/error' import { FastifyContext } from './context' import { @@ -53,6 +53,7 @@ export interface RouteShorthandOptions< onResponse?: onResponseHookHandler | onResponseHookHandler[]; onTimeout?: onTimeoutHookHandler | onTimeoutHookHandler[]; onError?: onErrorHookHandler | onErrorHookHandler[]; + onRequestAbort?: onRequestAbortHookHandler | onRequestAbortHookHandler[]; } /** From dd2a88587b8d0372a66ac5289dda55877e58b887 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 22 Feb 2023 12:25:14 +0000 Subject: [PATCH 0221/1295] ci(package-manager): update ubuntu os to latest (#4591) * ci(package-manager): update ubuntu os to latest * docs(reference/lts): fix table formatting --- .github/workflows/package-manager-ci.yml | 4 ++-- docs/Reference/LTS.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 47c207a2d74..dee9b700cce 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -16,7 +16,7 @@ jobs: matrix: # Maintenance and active LTS node-version: [14, 16] - os: [ubuntu-18.04] + os: [ubuntu-latest] steps: - uses: actions/checkout@v3 @@ -44,7 +44,7 @@ jobs: matrix: # Maintenance and active LTS node-version: [14, 16] - os: [ubuntu-18.04] + os: [ubuntu-latest] steps: - uses: actions/checkout@v3 diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 8ed9431a94d..851d6529aca 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -62,7 +62,7 @@ YAML workflow labels below: | OS | YAML Workflow Label | Package Manager | Node.js | |---------|------------------------|---------------------------|--------------| | Linux | `ubuntu-latest` | npm | 14,16,18 | -| Linux | `ubuntu-18.04` | yarn,pnpm | 14,16,18 | +| Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18 | | Windows | `windows-latest` | npm | 14,16,18 | | MacOS | `macos-latest` | npm | 14,16,18 | From 1e9c10fc538abf13c1d9389daf40c220c51b4ec6 Mon Sep 17 00:00:00 2001 From: Evelyn Pirnia Date: Thu, 23 Feb 2023 12:20:29 -0800 Subject: [PATCH 0222/1295] Improve Contribution Guide's VSCode setup instructions (#4594) * add info on vscode settings and link to vscode docs * fix: add vscode setting recomendation --------- Co-authored-by: Evelyn Pirnia --- docs/Guides/Contributing.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/Guides/Contributing.md b/docs/Guides/Contributing.md index 3cd8be41d0e..2dfc80908eb 100644 --- a/docs/Guides/Contributing.md +++ b/docs/Guides/Contributing.md @@ -162,9 +162,11 @@ the left sidebar. But wait! We are not quite done yet. There are a few more baseline settings that should be set before VSCode is ready. Press `cmd+shift+p` to bring up the VSCode command input prompt. Type `open -settings (json)` and then choose the same item from the filtered menu. This will -open a document that is the settings for the editor. Paste the following JSON -into this document, overwriting any text already present, and save it: +settings (json)`. Three [VSCode Setting](https://code.visualstudio.com/docs/getstarted/settings) +options will appear in the dropdown: Workspace, Default, +and User settings. We recommend selecting Default. This will open a document +that is the settings for the editor. Paste the following JSON into this +document, overwriting any text already present, and save it: ```json { From 562332a43ebdf13e233fd45608fbbc4af2a1aa4e Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Fri, 24 Feb 2023 08:17:59 -0800 Subject: [PATCH 0223/1295] fix: Only remove implicit HEAD routes. (#4596) * fix: Only remove implicit HEAD routes. * test: Added test. * Update test/route.test.js Co-authored-by: James Sumners --------- Co-authored-by: James Sumners --- lib/route.js | 2 +- test/route.test.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/route.js b/lib/route.js index d2624cd7fd7..138ab3b5923 100644 --- a/lib/route.js +++ b/lib/route.js @@ -303,7 +303,7 @@ function buildRouting (options) { // remove the head route created by fastify if (hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) { - router.off(opts.method, opts.url, { constraints }) + router.off('HEAD', opts.url, { constraints }) } try { diff --git a/test/route.test.js b/test/route.test.js index 06f02c3b184..ccb5570b228 100644 --- a/test/route.test.js +++ b/test/route.test.js @@ -1493,3 +1493,17 @@ test('exposeHeadRoute should not reuse the same route option', async t => { } }) }) + +test('using fastify.all when a catchall is defined does not degrade performance', { timeout: 5000 }, async t => { + t.plan(1) + + const fastify = Fastify() + + fastify.get('/*', async (_, reply) => reply.json({ ok: true })) + + for (let i = 0; i < 100; i++) { + fastify.all(`/${i}`, async (_, reply) => reply.json({ ok: true })) + } + + t.pass() +}) From 09cac4adcb79ad54a9ae9b5f4526a7a969037bcb Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 26 Feb 2023 09:47:33 +0100 Subject: [PATCH 0224/1295] Fix tests in Node.js v16 (#4597) --- .github/workflows/package-manager-ci.yml | 4 ++-- test/logger.test.js | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index dee9b700cce..b6ed40c2f03 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [14, 16] + node-version: [14, 16, 18] os: [ubuntu-latest] steps: @@ -43,7 +43,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [14, 16] + node-version: [14, 16, 18] os: [ubuntu-latest] steps: diff --git a/test/logger.test.js b/test/logger.test.js index 48ba39a2035..61e651b0841 100644 --- a/test/logger.test.js +++ b/test/logger.test.js @@ -28,15 +28,17 @@ before(async function () { [localhost, localhostForURL] = await helper.getLoopbackHost() }) -teardown(() => { - files.forEach((file) => { - try { - fs.unlinkSync(file) - } catch (e) { - console.log(e) - } +if (process.env.CI) { + teardown(() => { + files.forEach((file) => { + try { + fs.unlinkSync(file) + } catch (e) { + console.log(e) + } + }) }) -}) +} test('defaults to info level', t => { let fastify = null From b8ae90a419775a922cb10bc889599aaae415e8bc Mon Sep 17 00:00:00 2001 From: Jakob Date: Sun, 26 Feb 2023 14:35:43 +0100 Subject: [PATCH 0225/1295] docs(ecosystem): add fastify-flux tool (#4599) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 3f0b78346e1..149662375e6 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -648,5 +648,7 @@ section. Reusable workflows for use in the Fastify plugin - [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration generator by directory structure. +- [`fastify-flux`](https://github.com/Jnig/fastify-flux) Tool for building + Fastify APIs using decorators and convert Typescript interface to JSON Schema. - [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to generate JSON Schema from TypeScript interfaces. From d74b2b4371d176446108ec798a3672c0b389ae60 Mon Sep 17 00:00:00 2001 From: Ivan Tymoshenko Date: Sun, 26 Feb 2023 21:33:21 +0100 Subject: [PATCH 0226/1295] feat: include origin error message into serialization error (#4601) --- lib/error-handler.js | 9 +++------ lib/errors.js | 4 ++++ test/schema-serialization.test.js | 5 +++-- types/errors.d.ts | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/error-handler.js b/lib/error-handler.js index 801c42bce41..52f4862b859 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -11,7 +11,8 @@ const { } = require('./symbols.js') const { - FST_ERR_REP_INVALID_PAYLOAD_TYPE + FST_ERR_REP_INVALID_PAYLOAD_TYPE, + FST_ERR_FAILED_ERROR_SERIALIZATION } = require('./errors') const { getSchemaSerializer } = require('./schemas') @@ -113,11 +114,7 @@ function fallbackErrorHandler (error, reply, cb) { // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed') reply.code(500) - payload = serializeError({ - error: statusCodes['500'], - message: err.message, - statusCode: 500 - }) + payload = serializeError(new FST_ERR_FAILED_ERROR_SERIALIZATION(err.message, error.message)) } if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) { diff --git a/lib/errors.js b/lib/errors.js index 8d51dd8094e..b8960e3716a 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -214,6 +214,10 @@ const codes = { 'FST_ERR_BAD_TRAILER_VALUE', "Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function." ), + FST_ERR_FAILED_ERROR_SERIALIZATION: createError( + 'FST_ERR_FAILED_ERROR_SERIALIZATION', + 'Failed to serialize an error. Error: %s. Original error: %s' + ), FST_ERR_MISSING_SERIALIZATION_FN: createError( 'FST_ERR_MISSING_SERIALIZATION_FN', 'Missing serialization function. Key "%s"' diff --git a/test/schema-serialization.test.js b/test/schema-serialization.test.js index e4e5a9c7bb5..ee2578adddb 100644 --- a/test/schema-serialization.test.js +++ b/test/schema-serialization.test.js @@ -833,8 +833,9 @@ test('do not crash if status code serializer errors', async t => { t.equal(res.statusCode, 500) t.same(res.json(), { statusCode: 500, - error: 'Internal Server Error', - message: '"code" is required!' + code: 'FST_ERR_FAILED_ERROR_SERIALIZATION', + message: 'Failed to serialize an error. Error: "code" is required!. ' + + 'Original error: querystring must have required property \'foo\'' }) }) diff --git a/types/errors.d.ts b/types/errors.d.ts index 406960b56c1..85c2bd60822 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -28,6 +28,7 @@ export type FastifyErrorCodes = Record< 'FST_ERR_BAD_STATUS_CODE'| 'FST_ERR_BAD_TRAILER_NAME' | 'FST_ERR_BAD_TRAILER_VALUE' | +'FST_ERR_FAILED_ERROR_SERIALIZATION' | 'FST_ERR_MISSING_SERIALIZATION_FN' | 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION' | 'FST_ERR_SCH_MISSING_ID' | From ec173331c76b8cb31baa5d8764cee9f4f9b6421c Mon Sep 17 00:00:00 2001 From: Harry Brundage Date: Mon, 27 Feb 2023 08:03:09 -0500 Subject: [PATCH 0227/1295] feat: log requests refused before processing starts (#4600) Fastify can reject requests outright before any of the normal request processing begins right now: an unsupported HTTP version, or if the server is currently closing and `return503OnClosing` is true. Before this change, these requests wouldn't appear in the logs at all, which can make debugging them kind of confusing. This change adds a log message for these requests, so that they can be found by operators and a strange looking status code can be explained more easily. --- docs/Reference/Server.md | 4 +++- lib/route.js | 34 ++++++++++++++++++---------------- test/close.test.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 87ffeee8aad..b8befdbed3a 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -382,7 +382,9 @@ fastify.addHook('onResponse', (req, reply, done) => { ``` Please note that this setting will also disable an error log written by the -default `onResponse` hook on reply callback errors. +default `onResponse` hook on reply callback errors. Other log messages +emitted by Fastify will stay enabled, like deprecation warnings and messages +emitted when requests are received while the server is closing. ### `serverFactory` diff --git a/lib/route.js b/lib/route.js index 138ab3b5923..8f6069093d8 100644 --- a/lib/route.js +++ b/lib/route.js @@ -394,8 +394,25 @@ function buildRouting (options) { // HTTP request entry point, the routing has already been executed function routeHandler (req, res, params, context, query) { + const id = genReqId(req) + + const loggerBinding = { + [requestIdLogLabel]: id + } + + const loggerOpts = { + level: context.logLevel + } + + if (context.logSerializers) { + loggerOpts.serializers = context.logSerializers + } + const childLogger = logger.child(loggerBinding, loggerOpts) + childLogger[kDisableRequestLogging] = disableRequestLogging + // TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core. if (!validateHTTPVersion(req.httpVersion)) { + childLogger.info({ res: { statusCode: 505 } }, 'request aborted - invalid HTTP version') const message = '{"error":"HTTP Version Not Supported","message":"HTTP Version Not Supported","statusCode":505}' const headers = { 'Content-Type': 'application/json', @@ -424,6 +441,7 @@ function buildRouting (options) { } res.writeHead(503, headers) res.end('{"error":"Service Unavailable","message":"Service Unavailable","statusCode":503}') + childLogger.info({ res: { statusCode: 503 } }, 'request aborted - refusing to accept new requests as server is closing') return } } @@ -445,22 +463,6 @@ function buildRouting (options) { req.headers[kRequestAcceptVersion] = undefined } - const id = genReqId(req) - - const loggerBinding = { - [requestIdLogLabel]: id - } - - const loggerOpts = { - level: context.logLevel - } - - if (context.logSerializers) { - loggerOpts.serializers = context.logSerializers - } - const childLogger = logger.child(loggerBinding, loggerOpts) - childLogger[kDisableRequestLogging] = disableRequestLogging - const request = new context.Request(id, params, req, query, childLogger, context) const reply = new context.Reply(res, request, childLogger) diff --git a/test/close.test.js b/test/close.test.js index a8bc603a2b8..d9ac6866c39 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -7,6 +7,7 @@ const test = t.test const Fastify = require('..') const { Client } = require('undici') const semver = require('semver') +const split = require('split2') test('close callback', t => { t.plan(4) @@ -303,6 +304,40 @@ t.test('Current opened connection should not accept new incoming connections', t }) }) +t.test('rejected incoming connections should be logged', t => { + t.plan(2) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + + const messages = [] + stream.on('data', message => { + messages.push(message) + }) + fastify.get('/', (req, reply) => { + fastify.close() + setTimeout(() => { + reply.send({ hello: 'world' }) + }, 250) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + const instance = new Client('http://localhost:' + fastify.server.address().port) + // initial request to trigger close + instance.request({ path: '/', method: 'GET' }) + // subsequent request should be rejected + instance.request({ path: '/', method: 'GET' }).then(() => { + t.ok(messages.find(message => message.msg.includes('request aborted'))) + }) + }) +}) + test('Cannot be reopened the closed server without listen callback', async t => { t.plan(2) const fastify = Fastify() From 5a288bc42ad698e63f054a95259ac0d279db5747 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Fri, 3 Mar 2023 11:49:45 +0100 Subject: [PATCH 0228/1295] fix: add missed symbol (#4608) --- lib/symbols.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/symbols.js b/lib/symbols.js index c82b4e05769..3d509d89569 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -30,6 +30,7 @@ const keys = { kRequestValidateFns: Symbol('fastify.request.cache.validateFns'), kRequestPayloadStream: Symbol('fastify.RequestPayloadStream'), kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'), + kRequestValidateWeakMap: Symbol('fastify.request.cache.validators'), // 404 kFourOhFour: Symbol('fastify.404'), kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'), From a489611b4f92e1d302ef36e2307074629240505a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 3 Mar 2023 12:35:47 +0100 Subject: [PATCH 0229/1295] bumped v4.14.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 117 +++++++++++++++++----------------------- package.json | 2 +- 3 files changed, 51 insertions(+), 70 deletions(-) diff --git a/fastify.js b/fastify.js index 6f799059a2e..be072b0c83b 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.13.0' +const VERSION = '4.14.0' const Avvio = require('avvio') const http = require('http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index afb17021fe0..b539bad6cf2 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -2,15 +2,14 @@ /* istanbul ignore file */ 'use strict' - // eslint-disable-next-line const STR_ESCAPE = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]|[\ud800-\udbff](?![\udc00-\udfff])|(?:[^\ud800-\udbff]|^)[\udc00-\udfff]/ class Serializer { - constructor (options = {}) { - switch (options.rounding) { + constructor (options) { + switch (options && options.rounding) { case 'floor': this.parseInteger = Math.floor break @@ -20,6 +19,7 @@ class Serializer { case 'round': this.parseInteger = Math.round break + case 'trunc': default: this.parseInteger = Math.trunc break @@ -27,17 +27,28 @@ class Serializer { } asInteger (i) { - if (typeof i === 'bigint') { + if (typeof i === 'number') { + if (i === Infinity || i === -Infinity) { + throw new Error(`The value "${i}" cannot be converted to an integer.`) + } + if (Number.isInteger(i)) { + return '' + i + } + if (Number.isNaN(i)) { + throw new Error(`The value "${i}" cannot be converted to an integer.`) + } + return this.parseInteger(i) + } else if (i === null) { + return '0' + } else if (typeof i === 'bigint') { return i.toString() - } else if (Number.isInteger(i)) { - return '' + i } else { /* eslint no-undef: "off" */ const integer = this.parseInteger(i) - if (Number.isNaN(integer) || !Number.isFinite(integer)) { - throw new Error(`The value "${i}" cannot be converted to an integer.`) - } else { + if (Number.isFinite(integer)) { return '' + integer + } else { + throw new Error(`The value "${i}" cannot be converted to an integer.`) } } } @@ -91,23 +102,24 @@ class Serializer { } asString (str) { - const quotes = '"' - if (str instanceof Date) { - return quotes + str.toISOString() + quotes - } else if (str === null) { - return quotes + quotes - } else if (str instanceof RegExp) { - str = str.source - } else if (typeof str !== 'string') { - str = str.toString() + if (typeof str !== 'string') { + if (str === null) { + return '""' + } + if (str instanceof Date) { + return '"' + str.toISOString() + '"' + } + if (str instanceof RegExp) { + str = str.source + } else { + str = str.toString() + } } // Fast escape chars check if (!STR_ESCAPE.test(str)) { - return quotes + str + quotes - } - - if (str.length < 42) { + return '"' + str + '"' + } else if (str.length < 42) { return this.asStringSmall(str) } else { return JSON.stringify(str) @@ -151,81 +163,50 @@ class Serializer { } - - const serializer = new Serializer({"mode":"standalone"}) + const serializer = new Serializer() - function main (input) { - let json = '' - json += anonymous0(input) - return json - } function anonymous0 (input) { // # - var obj = (input && typeof input.toJSON === 'function') + const obj = (input && typeof input.toJSON === 'function') ? input.toJSON() : input - var json = '{' - var addComma = false + let json = '{' + let addComma = false if (obj["statusCode"] !== undefined) { - - if (addComma) { - json += ',' - } else { - addComma = true - } - - json += "\"statusCode\"" + ':' + !addComma && (addComma = true) || (json += ',') + json += "\"statusCode\":" json += serializer.asNumber(obj["statusCode"]) } if (obj["code"] !== undefined) { - - if (addComma) { - json += ',' - } else { - addComma = true - } - - json += "\"code\"" + ':' + !addComma && (addComma = true) || (json += ',') + json += "\"code\":" json += serializer.asString(obj["code"]) } if (obj["error"] !== undefined) { - - if (addComma) { - json += ',' - } else { - addComma = true - } - - json += "\"error\"" + ':' + !addComma && (addComma = true) || (json += ',') + json += "\"error\":" json += serializer.asString(obj["error"]) } if (obj["message"] !== undefined) { - - if (addComma) { - json += ',' - } else { - addComma = true - } - - json += "\"message\"" + ':' + !addComma && (addComma = true) || (json += ',') + json += "\"message\":" json += serializer.asString(obj["message"]) } - json += '}' - return json + return json + '}' } + const main = anonymous0 + - module.exports = main - diff --git a/package.json b/package.json index 70fe55e8524..a1df463c50a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.13.0", + "version": "4.14.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 3f4bddfeaf96d7f607f3460b9ed833a3b1cf3864 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Fri, 3 Mar 2023 18:46:33 +0000 Subject: [PATCH 0230/1295] chore(.gitignore): add bun lockfile (#4609) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 875f42e09fa..37985cb1fb9 100644 --- a/.gitignore +++ b/.gitignore @@ -136,6 +136,7 @@ dist .DS_Store # lock files +bun.lockb package-lock.json pnpm-lock.yaml yarn.lock From 59107420b908dc8dbecbe9288e3326097a7c9d9d Mon Sep 17 00:00:00 2001 From: Nadhif Ikbar Wibowo <10264286+nadhifikbarw@users.noreply.github.com> Date: Sat, 4 Mar 2023 23:06:40 +0700 Subject: [PATCH 0231/1295] docs: consistent note and example (#4610) --- docs/Reference/Server.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index b8befdbed3a..a38b6651b19 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -738,7 +738,8 @@ Fastify provides default error handlers for the most common use cases. It is possible to override one or more of those handlers with custom code using this option. -*Note: Only `FST_ERR_BAD_URL` is implemented at the moment.* +*Note: Only `FST_ERR_BAD_URL` and `FST_ERR_ASYNC_CONSTRAINT` are implemented at +the moment.* ```js const fastify = require('fastify')({ From 4bb798fccb152f8126f6bdd923d09b661e7ddbe9 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 4 Mar 2023 23:08:23 +0100 Subject: [PATCH 0232/1295] fix: onRequestAbort hook request pending (#4611) --- docs/Reference/Hooks.md | 2 +- lib/route.js | 1 + test/hooks.test.js | 27 +++++++++++++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index a7fb412bc95..4c988f966b2 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -284,7 +284,7 @@ fastify.addHook('onRequestAbort', async (request, reply) => { }) ``` The `onRequestAbort` hook is executed when a client closes the connection before -the entire request has been received. Therefore, you will not be able to send +the entire request has been processed. Therefore, you will not be able to send data to the client. **Notice:** client abort detection is not completely reliable. See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md) diff --git a/lib/route.js b/lib/route.js index 8f6069093d8..849f2cea18c 100644 --- a/lib/route.js +++ b/lib/route.js @@ -513,6 +513,7 @@ function handleOnRequestAbortHooksErrors (reply, err) { if (err) { reply.log.error({ err }, 'onRequestAborted hook failed') } + reply[kReplyIsError] = true } function handleTimeout () { diff --git a/test/hooks.test.js b/test/hooks.test.js index c8f8b95fb04..919e98a1b1f 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3413,11 +3413,29 @@ test('onRequestAbort should be triggered', t => { const fastify = Fastify() let order = 0 - t.plan(3) + t.plan(9) t.teardown(() => fastify.close()) fastify.addHook('onRequestAbort', function (req, done) { t.equal(++order, 1, 'called in hook') + t.ok(req.pendingResolve, 'request has pendingResolve') + req.pendingResolve() + done() + }) + + fastify.addHook('onError', function hook (request, reply, error, done) { + t.same(error, { hello: 'world' }, 'onError should be called') + t.ok(request.raw.aborted, 'request should be aborted') + done() + }) + + fastify.addHook('onSend', function hook (request, reply, payload, done) { + t.equal(payload, '{"hello":"world"}', 'onSend should be called') + done(null, payload) + }) + + fastify.addHook('onResponse', function hook (request, reply, done) { + t.fail('onResponse should not be called') done() }) @@ -3425,7 +3443,12 @@ test('onRequestAbort should be triggered', t => { method: 'GET', path: '/', async handler (request, reply) { - await sleep(1000) + t.pass('handler called') + let resolvePromise + const promise = new Promise(resolve => { resolvePromise = resolve }) + request.pendingResolve = resolvePromise + await promise + t.pass('handler promise resolved') return { hello: 'world' } }, async onRequestAbort (req) { From 1c9bfbf5b8e3539d173c58fab2272bdf304f6f38 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Mon, 6 Mar 2023 10:56:05 +0100 Subject: [PATCH 0233/1295] fix: cache duplicated symbols (#4612) * refactor: cleanup duplicated symbols * refactor: rename to kRequestCacheValidateFns * refactor: rename to kReplyCacheSerializeFns * test: extend test cases * test: add test case for context --- lib/context.js | 8 +++--- lib/reply.js | 18 +++++++------- lib/request.js | 18 +++++++------- lib/symbols.js | 5 ++-- test/internals/context.test.js | 33 +++++++++++++++++++++++++ test/internals/reply-serialize.test.js | 14 +++++------ test/internals/reply.test.js | 4 ++- test/internals/request-validate.test.js | 14 +++++------ test/internals/request.test.js | 2 ++ 9 files changed, 76 insertions(+), 40 deletions(-) create mode 100644 test/internals/context.test.js diff --git a/lib/context.js b/lib/context.js index f507f5144cc..5491df03c4d 100644 --- a/lib/context.js +++ b/lib/context.js @@ -11,8 +11,8 @@ const { kLogLevel, kContentTypeParser, kRouteByFastify, - kRequestValidateWeakMap, - kReplySerializeWeakMap, + kRequestCacheValidateFns, + kReplyCacheSerializeFns, kPublicRouteContext } = require('./symbols.js') @@ -68,8 +68,8 @@ function Context ({ defaultSchemaErrorFormatter this[kRouteByFastify] = isFastify - this[kRequestValidateWeakMap] = null - this[kReplySerializeWeakMap] = null + this[kRequestCacheValidateFns] = null + this[kReplyCacheSerializeFns] = null this.validatorCompiler = validatorCompiler || null this.serializerCompiler = serializerCompiler || null diff --git a/lib/reply.js b/lib/reply.js index 9f1332146e5..814cfb38347 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -18,7 +18,7 @@ const { kReplyNextErrorHandler, kDisableRequestLogging, kSchemaResponse, - kReplySerializeWeakMap, + kReplyCacheSerializeFns, kSchemaController, kOptions, kRouteContext @@ -323,7 +323,7 @@ Reply.prototype.getSerializationFunction = function (schemaOrStatus, contentType serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus] } } else if (typeof schemaOrStatus === 'object') { - serialize = this[kRouteContext][kReplySerializeWeakMap]?.get(schemaOrStatus) + serialize = this[kRouteContext][kReplyCacheSerializeFns]?.get(schemaOrStatus) } return serialize @@ -334,8 +334,8 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null const { method, url } = request // Check if serialize function already compiled - if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) { - return this[kRouteContext][kReplySerializeWeakMap].get(schema) + if (this[kRouteContext][kReplyCacheSerializeFns]?.has(schema)) { + return this[kRouteContext][kReplyCacheSerializeFns].get(schema) } const serializerCompiler = this[kRouteContext].serializerCompiler || @@ -360,11 +360,11 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null // if it is not used // TODO: Explore a central cache for all the schemas shared across // encapsulated contexts - if (this[kRouteContext][kReplySerializeWeakMap] == null) { - this[kRouteContext][kReplySerializeWeakMap] = new WeakMap() + if (this[kRouteContext][kReplyCacheSerializeFns] == null) { + this[kRouteContext][kReplyCacheSerializeFns] = new WeakMap() } - this[kRouteContext][kReplySerializeWeakMap].set(schema, serializeFn) + this[kRouteContext][kReplyCacheSerializeFns].set(schema, serializeFn) return serializeFn } @@ -393,8 +393,8 @@ Reply.prototype.serializeInput = function (input, schema, httpStatus, contentTyp } } else { // Check if serialize function already compiled - if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) { - serialize = this[kRouteContext][kReplySerializeWeakMap].get(schema) + if (this[kRouteContext][kReplyCacheSerializeFns]?.has(schema)) { + serialize = this[kRouteContext][kReplyCacheSerializeFns].get(schema) } else { serialize = this.compileSerializationSchema(schema, httpStatus, contentType) } diff --git a/lib/request.js b/lib/request.js index 25d4c4ec0c5..eb95d3ac263 100644 --- a/lib/request.js +++ b/lib/request.js @@ -11,7 +11,7 @@ const { kSchemaQuerystring, kSchemaController, kOptions, - kRequestValidateWeakMap, + kRequestCacheValidateFns, kRouteContext, kPublicRouteContext } = require('./symbols') @@ -254,7 +254,7 @@ Object.defineProperties(Request.prototype, { const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema] return this[kRouteContext][symbol] } else if (typeof httpPartOrSchema === 'object') { - return this[kRouteContext][kRequestValidateWeakMap]?.get(httpPartOrSchema) + return this[kRouteContext][kRequestCacheValidateFns]?.get(httpPartOrSchema) } } }, @@ -262,8 +262,8 @@ Object.defineProperties(Request.prototype, { value: function (schema, httpPart = null) { const { method, url } = this - if (this[kRouteContext][kRequestValidateWeakMap]?.has(schema)) { - return this[kRouteContext][kRequestValidateWeakMap].get(schema) + if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) { + return this[kRouteContext][kRequestCacheValidateFns].get(schema) } const validatorCompiler = this[kRouteContext].validatorCompiler || @@ -287,11 +287,11 @@ Object.defineProperties(Request.prototype, { // if it is not used // TODO: Explore a central cache for all the schemas shared across // encapsulated contexts - if (this[kRouteContext][kRequestValidateWeakMap] == null) { - this[kRouteContext][kRequestValidateWeakMap] = new WeakMap() + if (this[kRouteContext][kRequestCacheValidateFns] == null) { + this[kRouteContext][kRequestCacheValidateFns] = new WeakMap() } - this[kRouteContext][kRequestValidateWeakMap].set(schema, validateFn) + this[kRouteContext][kRequestCacheValidateFns].set(schema, validateFn) return validateFn } @@ -317,8 +317,8 @@ Object.defineProperties(Request.prototype, { } if (validate == null) { - if (this[kRouteContext][kRequestValidateWeakMap]?.has(schema)) { - validate = this[kRouteContext][kRequestValidateWeakMap].get(schema) + if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) { + validate = this[kRouteContext][kRequestCacheValidateFns].get(schema) } else { // We proceed to compile if there's no validate function yet validate = this.compileValidationSchema(schema, httpPart) diff --git a/lib/symbols.js b/lib/symbols.js index 3d509d89569..cdf4db84a4c 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -27,10 +27,9 @@ const keys = { kSchemaVisited: Symbol('fastify.schemas.visited'), // Request kRequest: Symbol('fastify.Request'), - kRequestValidateFns: Symbol('fastify.request.cache.validateFns'), kRequestPayloadStream: Symbol('fastify.RequestPayloadStream'), kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'), - kRequestValidateWeakMap: Symbol('fastify.request.cache.validators'), + kRequestCacheValidateFns: Symbol('fastify.request.cache.validateFns'), // 404 kFourOhFour: Symbol('fastify.404'), kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'), @@ -51,7 +50,7 @@ const keys = { kReplyErrorHandlerCalled: Symbol('fastify.reply.errorHandlerCalled'), kReplyIsRunningOnErrorHook: Symbol('fastify.reply.isRunningOnErrorHook'), kReplySerializerDefault: Symbol('fastify.replySerializerDefault'), - kReplySerializeWeakMap: Symbol('fastify.reply.cache.serializeFns'), + kReplyCacheSerializeFns: Symbol('fastify.reply.cache.serializeFns'), // This symbol is only meant to be used for fastify tests and should not be used for any other purpose kTestInternals: Symbol('fastify.testInternals'), kErrorHandler: Symbol('fastify.errorHandler'), diff --git a/test/internals/context.test.js b/test/internals/context.test.js new file mode 100644 index 00000000000..71c7cd40236 --- /dev/null +++ b/test/internals/context.test.js @@ -0,0 +1,33 @@ +'use strict' + +const { test } = require('tap') + +const { kRouteContext } = require('../../lib/symbols') +const Context = require('../../lib/context') + +const Fastify = require('../..') + +test('context', context => { + context.plan(1) + + context.test('Should not contain undefined as key prop', async t => { + const app = Fastify() + + app.get('/', (req, reply) => { + t.type(req[kRouteContext], Context) + t.type(reply[kRouteContext], Context) + t.notOk('undefined' in reply[kRouteContext]) + t.notOk('undefined' in req[kRouteContext]) + + reply.send('hello world!') + }) + + try { + await app.inject('/') + } catch (e) { + t.fail(e) + } + + t.plan(4) + }) +}) diff --git a/test/internals/reply-serialize.test.js b/test/internals/reply-serialize.test.js index 2166c7bb915..161fde01919 100644 --- a/test/internals/reply-serialize.test.js +++ b/test/internals/reply-serialize.test.js @@ -1,7 +1,7 @@ 'use strict' const { test } = require('tap') -const { kReplySerializeWeakMap, kRouteContext } = require('../../lib/symbols') +const { kReplyCacheSerializeFns, kRouteContext } = require('../../lib/symbols') const Fastify = require('../../fastify') function getDefaultSchema () { @@ -207,9 +207,9 @@ test('Reply#compileSerializationSchema', t => { fastify.get('/', (req, reply) => { const input = { hello: 'world' } - t.equal(reply[kRouteContext][kReplySerializeWeakMap], null) + t.equal(reply[kRouteContext][kReplyCacheSerializeFns], null) t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) - t.type(reply[kRouteContext][kReplySerializeWeakMap], WeakMap) + t.type(reply[kRouteContext][kReplyCacheSerializeFns], WeakMap) t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) reply.send({ hello: 'world' }) @@ -408,9 +408,9 @@ test('Reply#getSerializationFunction', t => { fastify.get('/', (req, reply) => { t.notOk(reply.getSerializationFunction(getDefaultSchema())) - t.equal(reply[kRouteContext][kReplySerializeWeakMap], null) + t.equal(reply[kRouteContext][kReplyCacheSerializeFns], null) t.notOk(reply.getSerializationFunction('200')) - t.equal(reply[kRouteContext][kReplySerializeWeakMap], null) + t.equal(reply[kRouteContext][kReplyCacheSerializeFns], null) reply.send({ hello: 'world' }) }) @@ -684,9 +684,9 @@ test('Reply#serializeInput', t => { fastify.get('/', (req, reply) => { const input = { hello: 'world' } - t.equal(reply[kRouteContext][kReplySerializeWeakMap], null) + t.equal(reply[kRouteContext][kReplyCacheSerializeFns], null) t.equal(reply.serializeInput(input, getDefaultSchema()), JSON.stringify(input)) - t.type(reply[kRouteContext][kReplySerializeWeakMap], WeakMap) + t.type(reply[kRouteContext][kReplyCacheSerializeFns], WeakMap) reply.send({ hello: 'world' }) }) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 6a8da41e7ed..d1754b63d4a 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -32,7 +32,7 @@ const doGet = function (url) { } test('Once called, Reply should return an object with methods', t => { - t.plan(13) + t.plan(14) const response = { res: 'res' } const context = {} const request = { [kRouteContext]: context } @@ -50,6 +50,8 @@ test('Once called, Reply should return an object with methods', t => { t.same(reply.raw, response) t.equal(reply[kRouteContext], context) t.equal(reply.request, request) + // Aim to not bad property keys (including Symbols) + t.notOk('undefined' in reply) }) test('reply.send will logStream error and destroy the stream', t => { diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js index 44cbb4b3cd4..2d59b1074a9 100644 --- a/test/internals/request-validate.test.js +++ b/test/internals/request-validate.test.js @@ -2,7 +2,7 @@ const { test } = require('tap') const Ajv = require('ajv') -const { kRequestValidateWeakMap, kRouteContext } = require('../../lib/symbols') +const { kRequestCacheValidateFns, kRouteContext } = require('../../lib/symbols') const Fastify = require('../../fastify') const defaultSchema = { @@ -231,11 +231,11 @@ test('#compileValidationSchema', subtest => { t.plan(5) fastify.get('/', (req, reply) => { - t.equal(req[kRouteContext][kRequestValidateWeakMap], null) + t.equal(req[kRouteContext][kRequestCacheValidateFns], null) t.type(req.compileValidationSchema(defaultSchema), Function) - t.type(req[kRouteContext][kRequestValidateWeakMap], WeakMap) + t.type(req[kRouteContext][kRequestCacheValidateFns], WeakMap) t.type(req.compileValidationSchema(Object.assign({}, defaultSchema)), Function) - t.type(req[kRouteContext][kRequestValidateWeakMap], WeakMap) + t.type(req[kRouteContext][kRequestCacheValidateFns], WeakMap) reply.send({ hello: 'world' }) }) @@ -424,7 +424,7 @@ test('#getValidationFunction', subtest => { req.getValidationFunction(defaultSchema) req.getValidationFunction('body') - t.equal(req[kRouteContext][kRequestValidateWeakMap], null) + t.equal(req[kRouteContext][kRequestCacheValidateFns], null) reply.send({ hello: 'world' }) }) @@ -724,9 +724,9 @@ test('#validate', subtest => { t.plan(3) fastify.get('/', (req, reply) => { - t.equal(req[kRouteContext][kRequestValidateWeakMap], null) + t.equal(req[kRouteContext][kRequestCacheValidateFns], null) t.equal(req.validateInput({ hello: 'world' }, defaultSchema), true) - t.type(req[kRouteContext][kRequestValidateWeakMap], WeakMap) + t.type(req[kRouteContext][kRequestCacheValidateFns], WeakMap) reply.send({ hello: 'world' }) }) diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 38c7a1e9ed8..0395749eff1 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -66,6 +66,8 @@ test('Regular request', t => { t.equal(request.routerMethod, context.config.method) t.equal(request.routeConfig, context[kPublicRouteContext].config) t.equal(request.routeSchema, context[kPublicRouteContext].schema) + // Aim to not bad property keys (including Symbols) + t.notOk('undefined' in request) // This will be removed, it's deprecated t.equal(request.connection, req.connection) From ac121b6581bbdb939155619e82cbdfc583918e64 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 6 Mar 2023 12:28:44 +0100 Subject: [PATCH 0234/1295] Bumped v4.14.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index be072b0c83b..dc88b6eed33 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.14.0' +const VERSION = '4.14.1' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index a1df463c50a..fdeecea8235 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.14.0", + "version": "4.14.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 4c416774ae20c5ea80a5301f0485877512a36da1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 15:20:47 +0000 Subject: [PATCH 0235/1295] build(deps-dev): bump tsd from 0.25.0 to 0.26.0 (#4614) Bumps [tsd](https://github.com/SamVerschueren/tsd) from 0.25.0 to 0.26.0. - [Release notes](https://github.com/SamVerschueren/tsd/releases) - [Commits](https://github.com/SamVerschueren/tsd/compare/v0.25.0...v0.26.0) --- updated-dependencies: - dependency-name: tsd dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fdeecea8235..5bae8002d41 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,7 @@ "split2": "^4.1.0", "standard": "^17.0.0", "tap": "^16.3.0", - "tsd": "^0.25.0", + "tsd": "^0.26.0", "typescript": "^4.8.3", "undici": "^5.10.0", "vary": "^1.1.2", From eafeed737f5e753b0d2fbf33e68a995d3149b769 Mon Sep 17 00:00:00 2001 From: Ivan Tymoshenko Date: Sat, 11 Mar 2023 17:07:30 +0100 Subject: [PATCH 0236/1295] feat: use internal trees for prettyPrint (#4618) * feat: use internal trees for prettyPrint * Update package.json Co-authored-by: Manuel Spigolon --------- Co-authored-by: Uzlopak Co-authored-by: Manuel Spigolon --- package.json | 2 +- test/pretty-print.test.js | 145 ++++++++++++++++++++++---------------- 2 files changed, 84 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 5bae8002d41..fe07ef1ea1b 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "abstract-logging": "^2.0.1", "avvio": "^8.2.0", "fast-content-type-parse": "^1.0.0", - "find-my-way": "^7.3.0", + "find-my-way": "^7.6.0", "light-my-request": "^5.6.1", "pino": "^8.5.0", "process-warning": "^2.0.0", diff --git a/test/pretty-print.test.js b/test/pretty-print.test.js index 47ec61f8e11..d86ca66580f 100644 --- a/test/pretty-print.test.js +++ b/test/pretty-print.test.js @@ -15,7 +15,8 @@ test('pretty print - static routes', t => { fastify.ready(() => { const tree = fastify.printRoutes() - const expected = `└── / + const expected = `\ +└── / ├── test (GET) │ └── /hello (GET) └── hello/world (GET) @@ -37,10 +38,13 @@ test('pretty print - parametric routes', t => { fastify.ready(() => { const tree = fastify.printRoutes() - const expected = `└── / + const expected = `\ +└── / ├── test (GET) - │ └── /:hello (GET) - └── hello/:world (GET) + │ └── / + │ └── :hello (GET) + └── hello/ + └── :world (GET) ` t.equal(typeof tree, 'string') @@ -60,11 +64,12 @@ test('pretty print - mixed parametric routes', t => { fastify.ready(() => { const tree = fastify.printRoutes() - const expected = `└── /test (GET) - └── / - └── :hello (GET) - :hello (POST) - └── /world (GET) + const expected = `\ +└── / + └── test (GET) + └── / + └── :hello (GET, POST) + └── /world (GET) ` t.equal(typeof tree, 'string') @@ -83,10 +88,13 @@ test('pretty print - wildcard routes', t => { fastify.ready(() => { const tree = fastify.printRoutes() - const expected = `└── / + const expected = `\ +└── / ├── test (GET) - │ └── /* (GET) - └── hello/* (GET) + │ └── / + │ └── * (GET) + └── hello/ + └── * (GET) ` t.equal(typeof tree, 'string') @@ -134,17 +142,15 @@ test('pretty print - commonPrefix', t => { const radixTree = fastify.printRoutes() const flatTree = fastify.printRoutes({ commonPrefix: false }) - const radixExpected = `└── / - ├── hel - │ ├── lo (GET) - │ │ lo (HEAD) - │ └── icopter (GET) - │ icopter (HEAD) - └── hello (PUT) + const radixExpected = `\ +└── / + └── hel + ├── lo (GET, HEAD, PUT) + └── icopter (GET, HEAD) ` - const flatExpected = `└── / (-) - ├── helicopter (GET, HEAD) - └── hello (GET, HEAD, PUT) + const flatExpected = `\ +├── /hello (GET, HEAD, PUT) +└── /helicopter (GET, HEAD) ` t.equal(typeof radixTree, 'string') t.equal(typeof flatTree, 'string') @@ -170,49 +176,64 @@ test('pretty print - includeMeta, includeHooks', t => { const flatTree = fastify.printRoutes({ commonPrefix: false, includeHooks: true, includeMeta: ['errorHandler'] }) const hooksOnly = fastify.printRoutes({ commonPrefix: false, includeHooks: true }) - const radixExpected = `└── / - ├── hel - │ ├── lo (GET) - │ │ • (onTimeout) ["onTimeout()"] - │ │ • (onRequest) ["anonymous()"] - │ │ • (errorHandler) "defaultErrorHandler()" - │ │ lo (HEAD) - │ │ • (onTimeout) ["onTimeout()"] - │ │ • (onRequest) ["anonymous()"] - │ │ • (onSend) ["headRouteOnSendHandler()"] - │ │ • (errorHandler) "defaultErrorHandler()" - │ └── icopter (GET) - │ • (onTimeout) ["onTimeout()"] - │ • (onRequest) ["anonymous()"] - │ • (errorHandler) "defaultErrorHandler()" - │ icopter (HEAD) - │ • (onTimeout) ["onTimeout()"] - │ • (onRequest) ["anonymous()"] - │ • (onSend) ["headRouteOnSendHandler()"] - │ • (errorHandler) "defaultErrorHandler()" - └── hello (PUT) - • (onTimeout) ["onTimeout()"] - • (onRequest) ["anonymous()"] - • (errorHandler) "defaultErrorHandler()" + const radixExpected = `\ +└── / + └── hel + ├── lo (GET, PUT) + │ • (onTimeout) ["onTimeout()"] + │ • (onRequest) ["anonymous()"] + │ • (errorHandler) "defaultErrorHandler()" + │ lo (HEAD) + │ • (onTimeout) ["onTimeout()"] + │ • (onRequest) ["anonymous()"] + │ • (onSend) ["headRouteOnSendHandler()"] + │ • (errorHandler) "defaultErrorHandler()" + └── icopter (GET) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (errorHandler) "defaultErrorHandler()" + icopter (HEAD) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (onSend) ["headRouteOnSendHandler()"] + • (errorHandler) "defaultErrorHandler()" ` - const flatExpected = `└── / (-) - ├── helicopter (GET, HEAD) - │ • (onTimeout) ["onTimeout()"] - │ • (onRequest) ["anonymous()"] - │ • (errorHandler) "defaultErrorHandler()" - └── hello (GET, HEAD, PUT) - • (onTimeout) ["onTimeout()"] - • (onRequest) ["anonymous()"] - • (errorHandler) "defaultErrorHandler()" + const flatExpected = `\ +├── /hello (GET, PUT) +│ • (onTimeout) ["onTimeout()"] +│ • (onRequest) ["anonymous()"] +│ • (errorHandler) "defaultErrorHandler()" +│ /hello (HEAD) +│ • (onTimeout) ["onTimeout()"] +│ • (onRequest) ["anonymous()"] +│ • (onSend) ["headRouteOnSendHandler()"] +│ • (errorHandler) "defaultErrorHandler()" +└── /helicopter (GET) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (errorHandler) "defaultErrorHandler()" + /helicopter (HEAD) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (onSend) ["headRouteOnSendHandler()"] + • (errorHandler) "defaultErrorHandler()" ` - const hooksOnlyExpected = `└── / (-) - ├── helicopter (GET, HEAD) - │ • (onTimeout) ["onTimeout()"] - │ • (onRequest) ["anonymous()"] - └── hello (GET, HEAD, PUT) - • (onTimeout) ["onTimeout()"] - • (onRequest) ["anonymous()"] + const hooksOnlyExpected = `\ +├── /hello (GET, PUT) +│ • (onTimeout) ["onTimeout()"] +│ • (onRequest) ["anonymous()"] +│ /hello (HEAD) +│ • (onTimeout) ["onTimeout()"] +│ • (onRequest) ["anonymous()"] +│ • (onSend) ["headRouteOnSendHandler()"] +└── /helicopter (GET) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + /helicopter (HEAD) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (onSend) ["headRouteOnSendHandler()"] ` t.equal(typeof radixTree, 'string') t.equal(typeof flatTree, 'string') From 8e75c50a2fd14fa831c703924ec720d06fe6abe9 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Sun, 12 Mar 2023 11:28:20 +0100 Subject: [PATCH 0237/1295] docs: add metcoder95 as collaborator (#4622) --- README.md | 6 +++++- package.json | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 227269693b3..6dc822cc8df 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,8 @@ listed in alphabetical order. , * [__Denis Fäcke__](https://github.com/SerayaEryn), , +* [__Carlos Fuentes__](https://github.com/metcoder95), + , * [__Rafael Gonzaga__](https://github.com/rafaelgss), , * [__Vincent Le Goff__](https://github.com/zekth) @@ -327,6 +329,8 @@ listed in alphabetical order. , * [__Ayoub El Khattabi__](https://github.com/AyoubElk), , +* [__Carlos Fuentes__](https://github.com/metcoder95), + , * [__Vincent Le Goff__](https://github.com/zekth) * [__Salman Mitha__](https://github.com/salmanm), @@ -338,7 +342,7 @@ listed in alphabetical order. * [__Rafael Gonzaga__](https://github.com/rafaelgss), , * [__Simone Busoli__](https://github.com/simoneb), - , + , ### Great Contributors Great contributors on a specific area in the Fastify ecosystem will be invited diff --git a/package.json b/package.json index fe07ef1ea1b..3d5f4388f75 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,11 @@ "name": "Luis Orbaiceta", "email": "luisorbaiceta@gmail.com", "url": "https://luisorbaiceta.com" + }, + { + "name": "Carlos Fuentes", + "email": "me@metcoder.dev", + "url": "https://metcoder.dev" } ], "license": "MIT", From 2ba790f86c89d0885e441d8a5838ff8abb00696f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Mar 2023 13:50:23 +0100 Subject: [PATCH 0238/1295] build(deps): bump lycheeverse/lychee-action from 1.5.4 to 1.6.1 (#4603) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.5.4 to 1.6.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/4dcb8bee2a0a4531cba1a1f392c54e8375d6dd81...9ace499fe66cee282a29eaa628fdac2c72fa087f) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Uzlopak --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 5d30204111d..f8cf6298c7a 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@4dcb8bee2a0a4531cba1a1f392c54e8375d6dd81 + uses: lycheeverse/lychee-action@9ace499fe66cee282a29eaa628fdac2c72fa087f with: fail: true # As external links behaviour is not predictable, we check only internal links From 9e92e4f3838794c1d4a6b51f46d835e74460bb85 Mon Sep 17 00:00:00 2001 From: Victor Tostes <51336150+victortosts@users.noreply.github.com> Date: Sun, 12 Mar 2023 12:42:07 -0300 Subject: [PATCH 0239/1295] fix: logger validator throws (#4520) --- docs/Reference/Errors.md | 6 +++ lib/errors.js | 5 +++ lib/logger.js | 92 ++++++++++++++++++++++------------------ test/logger.test.js | 32 +++++++++++++- types/errors.d.ts | 1 + 5 files changed, 94 insertions(+), 42 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index f919e222be2..0aadb14058e 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -281,6 +281,12 @@ A callback for a hook timed out The logger accepts either a `'stream'` or a `'file'` as the destination. +#### FST_ERR_LOG_INVALID_LOGGER + + +The logger should have all these methods: `'info'`, `'error'`, +`'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`. + #### FST_ERR_REP_INVALID_PAYLOAD_TYPE diff --git a/lib/errors.js b/lib/errors.js index b8960e3716a..cf303751a34 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -177,6 +177,11 @@ const codes = { 'Cannot specify both logger.stream and logger.file options' ), + FST_ERR_LOG_INVALID_LOGGER: createError( + 'FST_ERR_LOG_INVALID_LOGGER', + "Invalid logger object provided. The logger instance should have these functions(s): '%s'." + ), + /** * reply */ diff --git a/lib/logger.js b/lib/logger.js index 0a441505062..a2934d1ee23 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -9,17 +9,17 @@ const nullLogger = require('abstract-logging') const pino = require('pino') const { serializersSym } = pino.symbols -const { FST_ERR_LOG_INVALID_DESTINATION } = require('./errors') +const { + FST_ERR_LOG_INVALID_DESTINATION, + FST_ERR_LOG_INVALID_LOGGER +} = require('./errors') -function createPinoLogger (opts, stream) { - stream = stream || opts.stream - delete opts.stream - - if (stream && opts.file) { +function createPinoLogger (opts) { + if (opts.stream && opts.file) { throw new FST_ERR_LOG_INVALID_DESTINATION() } else if (opts.file) { // we do not have stream - stream = pino.destination(opts.file) + opts.stream = pino.destination(opts.file) delete opts.file } @@ -39,7 +39,7 @@ function createPinoLogger (opts, stream) { opts.logger = prevLogger opts.genReqId = prevGenReqId } else { - logger = pino(opts, stream) + logger = pino(opts, opts.stream) } return logger @@ -70,50 +70,60 @@ function now () { } function createLogger (options) { - if (isValidLogger(options.logger)) { + if (!options.logger) { + const logger = nullLogger + logger.child = () => logger + return { logger, hasLogger: false } + } + + if (validateLogger(options.logger)) { const logger = createPinoLogger({ logger: options.logger, serializers: Object.assign({}, serializers, options.logger.serializers) }) return { logger, hasLogger: true } - } else if (!options.logger) { - const logger = nullLogger - logger.child = () => logger - return { logger, hasLogger: false } - } else { - const localLoggerOptions = {} - if (Object.prototype.toString.call(options.logger) === '[object Object]') { - Reflect.ownKeys(options.logger).forEach(prop => { - Object.defineProperty(localLoggerOptions, prop, { - value: options.logger[prop], - writable: true, - enumerable: true, - configurable: true - }) - }) - } - localLoggerOptions.level = localLoggerOptions.level || 'info' - localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers) - options.logger = localLoggerOptions - const logger = createPinoLogger(options.logger) - return { logger, hasLogger: true } } -} -function isValidLogger (logger) { - if (!logger) { - return false + const localLoggerOptions = {} + if (Object.prototype.toString.call(options.logger) === '[object Object]') { + Reflect.ownKeys(options.logger).forEach(prop => { + Object.defineProperty(localLoggerOptions, prop, { + value: options.logger[prop], + writable: true, + enumerable: true, + configurable: true + }) + }) } + localLoggerOptions.level = localLoggerOptions.level || 'info' + localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers) + options.logger = localLoggerOptions + const logger = createPinoLogger(options.logger) + return { logger, hasLogger: true } +} - let result = true +/** + * Determines if a provided logger object meets the requirements + * of a Fastify compatible logger. + * + * @param {object} logger Object to validate. + * + * @returns {boolean} `true` when the logger meets the requirements. + * + * @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is + * missing required methods. + */ +function validateLogger (logger) { const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child'] - for (let i = 0; i < methods.length; i += 1) { - if (!logger[methods[i]] || typeof logger[methods[i]] !== 'function') { - result = false - break - } + const missingMethods = methods.filter(method => !logger[method] || typeof logger[method] !== 'function') + + if (!missingMethods.length) { + return true + } else if (missingMethods.length === methods.length) { + return false + } else { + throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(',')) } - return result } module.exports = { diff --git a/test/logger.test.js b/test/logger.test.js index 61e651b0841..8fcaeb4e62f 100644 --- a/test/logger.test.js +++ b/test/logger.test.js @@ -1,7 +1,6 @@ 'use strict' const { test, teardown, before } = require('tap') -const helper = require('./helper') const http = require('http') const stream = require('stream') const split = require('split2') @@ -13,6 +12,9 @@ const fs = require('fs') const sget = require('simple-get').concat const dns = require('dns') +const helper = require('./helper') +const { FST_ERR_LOG_INVALID_LOGGER } = require('../lib/errors') + const files = [] let count = 0 let localhost @@ -268,6 +270,34 @@ test('can use external logger instance with custom serializer', t => { }) }) +test('should throw in case the external logger provided does not have a child method', t => { + t.plan(1) + const loggerInstance = { + info: console.info, + error: console.error, + debug: console.debug, + fatal: console.error, + warn: console.warn, + trace: console.trace + } + + t.throws( + () => Fastify({ logger: loggerInstance }), + FST_ERR_LOG_INVALID_LOGGER, + "Invalid logger object provided. The logger instance should have these functions(s): 'child'." + ) +}) + +test('should throw in case a partially matching logger is provided', t => { + t.plan(1) + + t.throws( + () => Fastify({ logger: console }), + FST_ERR_LOG_INVALID_LOGGER, + "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'." + ) +}) + test('expose the logger', t => { t.plan(2) let fastify = null diff --git a/types/errors.d.ts b/types/errors.d.ts index 85c2bd60822..ab2c7dc2ad0 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -20,6 +20,7 @@ export type FastifyErrorCodes = Record< 'FST_ERR_MISSING_MIDDLEWARE' | 'FST_ERR_HOOK_TIMEOUT' | 'FST_ERR_LOG_INVALID_DESTINATION' | +'FST_ERR_LOG_INVALID_LOGGER' | 'FST_ERR_REP_INVALID_PAYLOAD_TYPE' | 'FST_ERR_REP_ALREADY_SENT' | 'FST_ERR_REP_SENT_VALUE'| From b69ae0f8549900d1a127f003501f0cc0eedcadd3 Mon Sep 17 00:00:00 2001 From: Ivan Tymoshenko Date: Mon, 13 Mar 2023 10:11:11 +0100 Subject: [PATCH 0240/1295] feat: expose prettyPrint method param (#4623) --- docs/Reference/Server.md | 115 ++++++++++++++++++++++++---------- test/pretty-print.test.js | 111 ++++++++++++++++++++++++++++++++ test/types/instance.test-d.ts | 2 + types/instance.d.ts | 3 +- 4 files changed, 198 insertions(+), 33 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index a38b6651b19..2b62a4321cb 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1545,35 +1545,61 @@ a custom constraint strategy with the same name. #### printRoutes -`fastify.printRoutes()`: Prints the representation of the internal radix tree -used by the router, useful for debugging. Alternatively, `fastify.printRoutes({ -commonPrefix: false })` can be used to print the flattened routes tree. +`fastify.printRoutes()`: Fastify router builds a tree of routes for each HTTP +method. If you call the prettyPrint without specifying an HTTP method, it will +merge all the trees into one and print it. The merged tree doesn't represent the +internal router structure. **Don't use it for debugging.** *Remember to call it inside or after a `ready` call.* ```js fastify.get('/test', () => {}) fastify.get('/test/hello', () => {}) -fastify.get('/hello/world', () => {}) -fastify.get('/helicopter', () => {}) +fastify.get('/testing', () => {}) +fastify.get('/testing/:param', () => {}) +fastify.put('/update', () => {}) fastify.ready(() => { console.log(fastify.printRoutes()) // └── / // ├── test (GET) - // │ └── /hello (GET) - // └── hel - // ├── lo/world (GET) - // └── licopter (GET) + // │ ├── /hello (GET) + // │ └── ing (GET) + // │ └── / + // │ └── :param (GET) + // └── update (PUT) +}) +``` - console.log(fastify.printRoutes({ commonPrefix: false })) - // └── / (-) - // ├── test (GET) - // │ └── /hello (GET) - // ├── hello/world (GET) - // └── helicopter (GET) +If you want to print the internal router tree, you should specify the `method` +param. Printed tree will represent the internal router structure. +**You can use it for debugging.** -}) +```js + console.log(fastify.printRoutes({ method: 'GET' })) + // └── / + // └── test (GET) + // ├── /hello (GET) + // └── ing (GET) + // └── / + // └── :param (GET) + + console.log(fastify.printRoutes({ method: 'PUT' })) + // └── / + // └── update (PUT) +``` + +`fastify.printRoutes({ commonPrefix: false })` will print compressed trees. This +might useful when you have a large number of routes with common prefixes. +It doesn't represent the internal router structure. **Don't use it for debugging.** + +```js + console.log(fastify.printRoutes({ commonPrefix: false })) + // ├── /test (GET) + // │ ├── /hello (GET) + // │ └── ing (GET) + // │ └── /:param (GET) + // └── /update (PUT) ``` `fastify.printRoutes({ includeMeta: (true | []) })` will display properties from @@ -1583,26 +1609,51 @@ A shorthand option, `fastify.printRoutes({ includeHooks: true })` will include all [hooks](./Hooks.md). ```js - console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['metaProperty'] })) + fastify.get('/test', () => {}) + fastify.get('/test/hello', () => {}) + + const onTimeout = () => {} + + fastify.addHook('onRequest', () => {}) + fastify.addHook('onTimeout', onTimeout) + + console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['errorHandler'] })) // └── / - // ├── test (GET) - // │ • (onRequest) ["anonymous()","namedFunction()"] - // │ • (metaProperty) "value" - // │ └── /hello (GET) - // └── hel - // ├── lo/world (GET) - // │ • (onTimeout) ["anonymous()"] - // └── licopter (GET) + // └── test (GET) + // • (onTimeout) ["onTimeout()"] + // • (onRequest) ["anonymous()"] + // • (errorHandler) "defaultErrorHandler()" + // test (HEAD) + // • (onTimeout) ["onTimeout()"] + // • (onRequest) ["anonymous()"] + // • (onSend) ["headRouteOnSendHandler()"] + // • (errorHandler) "defaultErrorHandler()" + // └── /hello (GET) + // • (onTimeout) ["onTimeout()"] + // • (onRequest) ["anonymous()"] + // • (errorHandler) "defaultErrorHandler()" + // /hello (HEAD) + // • (onTimeout) ["onTimeout()"] + // • (onRequest) ["anonymous()"] + // • (onSend) ["headRouteOnSendHandler()"] + // • (errorHandler) "defaultErrorHandler()" console.log(fastify.printRoutes({ includeHooks: true })) // └── / - // ├── test (GET) - // │ • (onRequest) ["anonymous()","namedFunction()"] - // │ └── /hello (GET) - // └── hel - // ├── lo/world (GET) - // │ • (onTimeout) ["anonymous()"] - // └── licopter (GET) + // └── test (GET) + // • (onTimeout) ["onTimeout()"] + // • (onRequest) ["anonymous()"] + // test (HEAD) + // • (onTimeout) ["onTimeout()"] + // • (onRequest) ["anonymous()"] + // • (onSend) ["headRouteOnSendHandler()"] + // └── /hello (GET) + // • (onTimeout) ["onTimeout()"] + // • (onRequest) ["anonymous()"] + // /hello (HEAD) + // • (onTimeout) ["onTimeout()"] + // • (onRequest) ["anonymous()"] + // • (onSend) ["headRouteOnSendHandler()"] ``` #### printPlugins diff --git a/test/pretty-print.test.js b/test/pretty-print.test.js index d86ca66580f..08d45ace5e4 100644 --- a/test/pretty-print.test.js +++ b/test/pretty-print.test.js @@ -27,6 +27,41 @@ test('pretty print - static routes', t => { }) }) +test('pretty print - internal tree - static routes', t => { + t.plan(4) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/hello', () => {}) + fastify.get('/hello/world', () => {}) + + fastify.put('/test', () => {}) + fastify.put('/test/foo', () => {}) + + fastify.ready(() => { + const getTree = fastify.printRoutes({ method: 'GET' }) + const expectedGetTree = `\ +└── / + ├── test (GET) + │ └── /hello (GET) + └── hello/world (GET) +` + + t.equal(typeof getTree, 'string') + t.equal(getTree, expectedGetTree) + + const putTree = fastify.printRoutes({ method: 'PUT' }) + const expectedPutTree = `\ +└── / + └── test (PUT) + └── /foo (PUT) +` + + t.equal(typeof putTree, 'string') + t.equal(putTree, expectedPutTree) + }) +}) + test('pretty print - parametric routes', t => { t.plan(2) @@ -52,6 +87,44 @@ test('pretty print - parametric routes', t => { }) }) +test('pretty print - internal tree - parametric routes', t => { + t.plan(4) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/:hello', () => {}) + fastify.get('/hello/:world', () => {}) + + fastify.put('/test', () => {}) + fastify.put('/test/:hello', () => {}) + + fastify.ready(() => { + const getTree = fastify.printRoutes({ method: 'GET' }) + const expectedGetTree = `\ +└── / + ├── test (GET) + │ └── / + │ └── :hello (GET) + └── hello/ + └── :world (GET) +` + + t.equal(typeof getTree, 'string') + t.equal(getTree, expectedGetTree) + + const putTree = fastify.printRoutes({ method: 'PUT' }) + const expectedPutTree = `\ +└── / + └── test (PUT) + └── / + └── :hello (PUT) +` + + t.equal(typeof putTree, 'string') + t.equal(putTree, expectedPutTree) + }) +}) + test('pretty print - mixed parametric routes', t => { t.plan(2) @@ -102,6 +175,44 @@ test('pretty print - wildcard routes', t => { }) }) +test('pretty print - internal tree - wildcard routes', t => { + t.plan(4) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/*', () => {}) + fastify.get('/hello/*', () => {}) + + fastify.put('/*', () => {}) + fastify.put('/test/*', () => {}) + + fastify.ready(() => { + const getTree = fastify.printRoutes({ method: 'GET' }) + const expectedGetTree = `\ +└── / + ├── test (GET) + │ └── / + │ └── * (GET) + └── hello/ + └── * (GET) +` + + t.equal(typeof getTree, 'string') + t.equal(getTree, expectedGetTree) + + const putTree = fastify.printRoutes({ method: 'PUT' }) + const expectedPutTree = `\ +└── / + ├── test/ + │ └── * (PUT) + └── * (PUT) +` + + t.equal(typeof putTree, 'string') + t.equal(putTree, expectedPutTree) + }) +}) + test('pretty print - empty plugins', t => { t.plan(2) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index f0d65daf2f5..1ca98c2fb51 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -284,6 +284,8 @@ expectType(server.printRoutes({ includeHooks: true, commonPrefix: false, expectType(server.printRoutes({ includeMeta: ['key1', Symbol('key2')] })) +expectType(server.printRoutes({ method: 'GET' })) + expectType(server.printRoutes()) server.decorate<(x: string) => void>('test', function (x: string): void { diff --git a/types/instance.d.ts b/types/instance.d.ts index f92626745d1..173ffcbb496 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -20,10 +20,11 @@ import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' -import { ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' +import { HTTPMethods, ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' import { AddressInfo } from 'net' export interface PrintRoutesOptions { + method?: HTTPMethods; includeMeta?: boolean | (string | symbol)[] commonPrefix?: boolean includeHooks?: boolean From 71abc48379d37acc217246b318b40b01920b95e1 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 13 Mar 2023 11:19:48 +0100 Subject: [PATCH 0241/1295] ensure that generated validators are up to date (#4508) * ensure that generated validators are up to date * Update package.json Co-authored-by: KaKa * test: ci failing * run integrity check on prepublishOnly --------- Co-authored-by: KaKa Co-authored-by: Manuel Spigolon --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d5f4388f75..5d850d9b73f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "scripts": { "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"", "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"", + "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "coverage": "npm run unit -- --cov --coverage-report=html", "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage -R terse", "coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100", @@ -17,10 +18,11 @@ "lint:markdown": "markdownlint-cli2", "lint:standard": "standard | snazzy", "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", - "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js", + "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", + "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", "test:typescript": "tsc test/types/import.ts && tsd", "test:watch": "npm run unit -- -w --no-coverage-report -R terse", "unit": "tap", From 6b5957d0a766b3fc4c8926b9683ae956fc888edf Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 13 Mar 2023 11:22:51 +0100 Subject: [PATCH 0242/1295] fix: check if validation schema is undefined (#4620) --- lib/validation.js | 8 ++++ test/schema-feature.test.js | 85 +++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/lib/validation.js b/lib/validation.js index b56bc271f36..981dcb9d402 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -80,18 +80,26 @@ function compileSchemasForValidation (context, compile, isCustom) { }) } context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' }) + } else if (Object.hasOwnProperty.call(schema, 'headers')) { + throw new Error('headers schema is undefined') } if (schema.body) { context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) + } else if (Object.hasOwnProperty.call(schema, 'body')) { + throw new Error('body schema is undefined') } if (schema.querystring) { context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' }) + } else if (Object.hasOwnProperty.call(schema, 'querystring')) { + throw new Error('querystring schema is undefined') } if (schema.params) { context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' }) + } else if (Object.hasOwnProperty.call(schema, 'params')) { + throw new Error('params schema is undefined') } } diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index 35c9f46861f..083c89f078b 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -253,6 +253,91 @@ test('Should not change the input schemas', t => { }) }) +test('Should throw if the schema body is undefined', t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:id', { + handler: echoParams, + schema: { + body: undefined + } + }) + + fastify.ready(err => { + t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error body schema is undefined') + }) +}) + +test('Should throw if the schema headers is undefined', t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:id', { + handler: echoParams, + schema: { + headers: undefined + } + }) + + fastify.ready(err => { + t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error headers schema is undefined') + }) +}) + +test('Should throw if the schema params is undefined', t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:id', { + handler: echoParams, + schema: { + params: undefined + } + }) + + fastify.ready(err => { + t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error params schema is undefined') + }) +}) + +test('Should throw if the schema query is undefined', t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:id', { + handler: echoParams, + schema: { + querystring: undefined + } + }) + + fastify.ready(err => { + t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error querystring schema is undefined') + }) +}) + +test('Should throw if the schema query is undefined', t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:id', { + handler: echoParams, + schema: { + querystring: undefined + } + }) + + fastify.ready(err => { + t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error querystring schema is undefined') + }) +}) + test('First level $ref', t => { t.plan(2) const fastify = Fastify() From 4723c1b228f4a3d9e0ba38a137084b7fa2b622db Mon Sep 17 00:00:00 2001 From: KaKa Date: Mon, 13 Mar 2023 22:29:05 +0800 Subject: [PATCH 0243/1295] fix: content-type mis-handling for invalid non-essence content-type (#4509) * fix: content-type mis-handling for invalid non-essence content-type * Update lib/contentTypeParser.js Co-authored-by: Uzlopak * fixup: empty string and semicolon edge case * test: add edge case * chore: update comment * test: remove duplicate test --------- Co-authored-by: Uzlopak --- lib/contentTypeParser.js | 12 ++++- test/content-parser.test.js | 101 ++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 10f4bff5f81..59d46afa3f4 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -396,10 +396,18 @@ function ParserListItem (contentType) { // we pre-calculate all the needed information // before content-type comparsion const parsed = safeParseContentType(contentType) - this.type = parsed.type + this.isEssence = contentType.indexOf(';') === -1 + // we should not allow empty string for parser list item + // because it would become a match-all handler + if (this.isEssence === false && parsed.type === '') { + // handle semicolon or empty string + const tmp = contentType.split(';')[0] + this.type = tmp === '' ? contentType : tmp + } else { + this.type = parsed.type + } this.parameters = parsed.parameters this.parameterKeys = Object.keys(parsed.parameters) - this.isEssence = contentType.indexOf(';') === -1 } // used in ContentTypeParser.remove diff --git a/test/content-parser.test.js b/test/content-parser.test.js index d677ecfedb8..2e49c10bb44 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -649,3 +649,104 @@ test('content-type regexp list should be cloned when plugin override', async t = t.same(payload, 'png') } }) + +test('allow partial content-type - essence check', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser('json', function (request, body, done) { + t.pass('should be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; foo=bar; charset=utf8' + }, + body: '' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'image/jpeg' + }, + body: '' + }) +}) + +test('allow partial content-type - not essence check', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser('json;', function (request, body, done) { + t.pass('should be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; foo=bar; charset=utf8' + }, + body: '' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'image/jpeg' + }, + body: '' + }) +}) + +test('edge case content-type - ;', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser(';', function (request, body, done) { + t.fail('should not be called') + done(null, body) + }) + + fastify.post('/', async () => { + return 'ok' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'application/json; foo=bar; charset=utf8' + }, + body: '' + }) + + await fastify.inject({ + method: 'POST', + path: '/', + headers: { + 'content-type': 'image/jpeg' + }, + body: '' + }) + + t.pass('end') +}) From ca9785a3fadd9050ec163c6a0841a06f387a71c6 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sun, 19 Mar 2023 09:42:47 +0100 Subject: [PATCH 0244/1295] Bump version of fast-json-stringify-compiler (#4630) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d850d9b73f..9209bdd4021 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "dependencies": { "@fastify/ajv-compiler": "^3.5.0", "@fastify/error": "^3.0.0", - "@fastify/fast-json-stringify-compiler": "^4.1.0", + "@fastify/fast-json-stringify-compiler": "^4.2.0", "abstract-logging": "^2.0.1", "avvio": "^8.2.0", "fast-content-type-parse": "^1.0.0", From da9ace5ef2f5828bc71a2e54af9fdc4107e1258d Mon Sep 17 00:00:00 2001 From: Fabian Meyer <3982806+meyfa@users.noreply.github.com> Date: Mon, 20 Mar 2023 09:54:26 +0100 Subject: [PATCH 0245/1295] fix: avoid invoking onError hook when aborted handler resolves (#4631) Fixes #4626. When a client aborts the request while the route handler is still processing, this no longer triggers an error when the route handler finishes processing. --- lib/route.js | 1 - test/hooks.test.js | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/route.js b/lib/route.js index 849f2cea18c..8f6069093d8 100644 --- a/lib/route.js +++ b/lib/route.js @@ -513,7 +513,6 @@ function handleOnRequestAbortHooksErrors (reply, err) { if (err) { reply.log.error({ err }, 'onRequestAborted hook failed') } - reply[kReplyIsError] = true } function handleTimeout () { diff --git a/test/hooks.test.js b/test/hooks.test.js index 919e98a1b1f..4fc98a0a39f 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3413,7 +3413,7 @@ test('onRequestAbort should be triggered', t => { const fastify = Fastify() let order = 0 - t.plan(9) + t.plan(7) t.teardown(() => fastify.close()) fastify.addHook('onRequestAbort', function (req, done) { @@ -3424,8 +3424,7 @@ test('onRequestAbort should be triggered', t => { }) fastify.addHook('onError', function hook (request, reply, error, done) { - t.same(error, { hello: 'world' }, 'onError should be called') - t.ok(request.raw.aborted, 'request should be aborted') + t.fail('onError should not be called') done() }) From 87b644087c5b15bd70766bc4efeb940308d97066 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 20 Mar 2023 10:03:41 +0100 Subject: [PATCH 0246/1295] Bumped v4.15.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index dc88b6eed33..63dae72ce7f 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.14.1' +const VERSION = '4.15.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 9209bdd4021..acd019a5dfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.14.1", + "version": "4.15.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From f1991cf60a8034161ea226344c902903d7a6d138 Mon Sep 17 00:00:00 2001 From: shusak Date: Tue, 28 Mar 2023 15:10:14 -0400 Subject: [PATCH 0247/1295] docs: spelling, punctuation & grammar minor nits (#4642) Co-authored-by: Steve Husak --- docs/Guides/Ecosystem.md | 8 ++++---- docs/Guides/Getting-Started.md | 2 +- docs/Guides/Plugins-Guide.md | 2 +- docs/Guides/Recommendations.md | 2 +- docs/Guides/Write-Type-Provider.md | 6 +++--- docs/Reference/Hooks.md | 6 +++--- docs/Reference/Reply.md | 4 ++-- docs/Reference/Routes.md | 4 ++-- docs/Reference/Type-Providers.md | 2 +- docs/Reference/TypeScript.md | 6 +++--- docs/index.md | 4 ++-- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 149662375e6..ee12e5320ef 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -11,7 +11,7 @@ section. - [`@fastify/accepts`](https://github.com/fastify/fastify-accepts) to have [accepts](https://www.npmjs.com/package/accepts) in your request object. - [`@fastify/accepts-serializer`](https://github.com/fastify/fastify-accepts-serializer) - to serialize to output according to `Accept` header. + to serialize to output according to the `Accept` header. - [`@fastify/any-schema`](https://github.com/fastify/any-schema-you-like) Save multiple schemas and decide which one to use to serialize the payload - [`@fastify/auth`](https://github.com/fastify/fastify-auth) Run multiple auth @@ -151,7 +151,7 @@ section. - [`@eropple/fastify-openapi3`](https://github.com/eropple/fastify-openapi3) Provides easy, developer-friendly OpenAPI 3.1 specs + doc explorer based on your routes. - [`@ethicdevs/fastify-custom-session`](https://github.com/EthicDevs/fastify-custom-session) - A plugin that let you use session and decide only where to load/save from/to. Has + A plugin lets you use session and decide only where to load/save from/to. Has great TypeScript support + built-in adapters for common ORMs/databases (Firebase, Prisma Client, Postgres (wip), InMemory) and you can easily make your own adapter! - [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server) @@ -391,7 +391,7 @@ section. - [`fastify-keycloak-adapter`](https://github.com/yubinTW/fastify-keycloak-adapter) A keycloak adapter for a Fastify app. - [`fastify-knexjs`](https://github.com/chapuletta/fastify-knexjs) Fastify - plugin for support KnexJS Query Builder. + plugin for supporting KnexJS Query Builder. - [`fastify-knexjs-mock`](https://github.com/chapuletta/fastify-knexjs-mock) Fastify Mock KnexJS for testing support. - [`fastify-kubernetes`](https://github.com/greguz/fastify-kubernetes) Fastify @@ -401,7 +401,7 @@ section. - [`fastify-lcache`](https://github.com/denbon05/fastify-lcache) Lightweight cache plugin - [`fastify-list-routes`](https://github.com/chuongtrh/fastify-list-routes) - A simple plugin for Fastify list all available routes. + A simple plugin for Fastify to list all available routes. - [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from a directory and inject the Fastify instance in each file. - [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index 8f89026628d..6d1690bd0e9 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -538,7 +538,7 @@ an amazing [ecosystem](./Ecosystem.md)! Fastify does not offer a testing framework, but we do recommend a way to write -your tests that uses the features and architecture of Fastify. +your tests that use the features and architecture of Fastify. Read the [testing](./Testing.md) documentation to learn more! diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index de131b92428..50b6fc4a6b3 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -427,7 +427,7 @@ variables that were injected by preceding plugins in the order of declaration. ESM is supported as well from [Node.js `v13.3.0`](https://nodejs.org/api/esm.html) and above! Just export your plugin -as ESM module and you are good to go! +as an ESM module and you are good to go! ```js // plugin.mjs diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index 9af219617d6..9ae694c959d 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -326,7 +326,7 @@ frequently. Also, the main thread won't have to stop to let the GC run. * To optimize for throughput (handling the largest possible amount of requests per second per vCPU available), consider using a smaller amount of vCPUs -per app instance. It is totally fine to run Node.js application with 1 vCPU. +per app instance. It is totally fine to run Node.js applications with 1 vCPU. * You may experiment with an even smaller amount of vCPU, which may provide even better throughput in certain use-cases. There are reports of API gateway diff --git a/docs/Guides/Write-Type-Provider.md b/docs/Guides/Write-Type-Provider.md index 685a87dae82..fadba38765d 100644 --- a/docs/Guides/Write-Type-Provider.md +++ b/docs/Guides/Write-Type-Provider.md @@ -13,11 +13,11 @@ up to `unknown`. The reasoning is that certain methods of `FastifyInstance` are contravariant on `TypeProvider`, which can lead to TypeScript surfacing assignability issues unless the custom type provider interface is -substitutible with `FastifyTypeProviderDefault`. +substitutable with `FastifyTypeProviderDefault`. For example, `FastifyTypeProviderDefault` will not be assignable to the following: ```ts -export interface NotSubstitutibleTypeProvider extends FastifyTypeProvider { +export interface NotSubstitutableTypeProvider extends FastifyTypeProvider { // bad, nothing is assignable to `never` (except for itself) output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : never; } @@ -25,7 +25,7 @@ export interface NotSubstitutibleTypeProvider extends FastifyTypeProvider { Unless changed to: ```ts -export interface SubstitutibleTypeProvider extends FastifyTypeProvider { +export interface SubstitutableTypeProvider extends FastifyTypeProvider { // good, anything can be assigned to `unknown` output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown; } diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 4c988f966b2..12ca0b05833 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -266,7 +266,7 @@ fastify.addHook('onTimeout', async (request, reply) => { `onTimeout` is useful if you need to monitor the request timed out in your service (if the `connectionTimeout` property is set on the Fastify instance). The `onTimeout` hook is executed when a request is timed out and the HTTP socket -has been hanged up. Therefore, you will not be able to send data to the client. +has been hung up. Therefore, you will not be able to send data to the client. ### onRequestAbort @@ -728,7 +728,7 @@ fastify.get('/me/is-admin', async function (req, reply) { ``` Note that `.authenticatedUser` could actually be any property name -choosen by yourself. Using your own custom property prevents you +chosen by yourself. Using your own custom property prevents you from mutating existing properties, which would be a dangerous and destructive operation. So be careful and make sure your property is entirely new, also using this approach @@ -774,7 +774,7 @@ initialization of the tracking package, in the typical "require instrumentation tools first" fashion. ```js -const tracer = /* retrieved from elsehwere in the package */ +const tracer = /* retrieved from elsewhere in the package */ const dc = require('diagnostics_channel') const channel = dc.channel('fastify.initialization') const spans = new WeakMap() diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 0f8e99db793..9a7e78b11f2 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -243,7 +243,7 @@ reply.trailer('server-timing', function() { }) const { createHash } = require('crypto') -// trailer function also recieve two argument +// trailer function also receive two argument // @param {object} reply fastify reply // @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent // @param {function} done callback to set trailer value @@ -396,7 +396,7 @@ The function returned (a.k.a. _serialization function_) returned is compiled by using the provided `SerializerCompiler`. Also this is cached by using a `WeakMap` for reducing compilation calls. -The optional paramaters `httpStatus` and `contentType`, if provided, +The optional parameters `httpStatus` and `contentType`, if provided, are forwarded directly to the `SerializerCompiler`, so it can be used to compile the serialization function if a custom `SerializerCompiler` is used. diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 0e47e6e49ca..b1283a5ba45 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -78,7 +78,7 @@ fastify.route(options) when a response has been sent, so you will not be able to send more data to the client. It could also be an array of functions. * `onTimeout(request, reply, done)`: a [function](./Hooks.md#ontimeout) called - when a request is timed out and the HTTP socket has been hanged up. + when a request is timed out and the HTTP socket has been hung up. * `onError(request, reply, error, done)`: a [function](./Hooks.md#onerror) called when an Error is thrown or sent to the client by the route handler. * `handler(request, reply)`: the function that will handle this request. The @@ -219,7 +219,7 @@ fastify.get('/', opts) ``` > Note: if the handler is specified in both the `options` and as the third -> parameter to the shortcut method then throws duplicate `handler` error. +> parameter to the shortcut method then throws a duplicate `handler` error. ### Url building diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 724a4291260..c199bf83df2 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -170,7 +170,7 @@ function plugin1(fastify: FastifyInstance, _opts, done): void { }) } }, (req) => { - // it doesn't works! in a new scope needs to call `withTypeProvider` again + // it doesn't work! in a new scope needs to call `withTypeProvider` again const { x, y, z } = req.body }); done() diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 3d9c0c21f4c..5632acd6c2a 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -186,7 +186,7 @@ Serialization](./Validation-and-Serialization.md) documentation for more info. Also it has the advantage to use the defined type within your handlers (including pre-validation, etc.). -Here are some options how to achieve this. +Here are some options on how to achieve this. #### Fastify Type Providers @@ -1307,10 +1307,10 @@ types defined in this section are used under-the-hood by the Fastify instance [src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#L105) A type declaration for the route handler methods. Has two arguments, `request` -and `reply` which are typed by `FastifyRequest` and `FastifyReply` respectfully. +and `reply` which are typed by `FastifyRequest` and `FastifyReply` respectively. The generics parameters are passed through to these arguments. The method returns either `void` or `Promise` for synchronous and asynchronous -handlers respectfully. +handlers respectively. ##### fastify.RouteOptions<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> diff --git a/docs/index.md b/docs/index.md index 0a89105a957..625d373b903 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,8 +7,8 @@ The documentation for Fastify is split into two categories: The reference documentation utilizes a very formal style in an effort to document Fastify's API and implementation details thoroughly for the developer who needs -such. The guides category utilizes an informal, educational, style as a means to -introduce newcomers to core, and advanced, Fastify concepts. +such. The guides category utilizes an informal educational style as a means to +introduce newcomers to core and advanced Fastify concepts. ## Where To Start From bf1214879d9ad7baecba0b9af7d1e9ddbf41044e Mon Sep 17 00:00:00 2001 From: Phillip Lassen Date: Wed, 29 Mar 2023 16:50:10 +0200 Subject: [PATCH 0248/1295] docs(mongodb): update to mongodb 5 (#4646) As `@fastify/mongodb` updated to mongodb 5, callbacks are not supported anymore and we need to switch to promises https://github.com/fastify/fastify-mongodb/pull/212 --- docs/Guides/Database.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md index d9d9b09754d..4a626026a06 100644 --- a/docs/Guides/Database.md +++ b/docs/Guides/Database.md @@ -130,19 +130,18 @@ fastify.register(require('@fastify/mongodb'), { url: 'mongodb://mongo/mydb' }) -fastify.get('/user/:id', function (req, reply) { +fastify.get('/user/:id', async function (req, reply) { // Or this.mongo.client.db('mydb').collection('users') const users = this.mongo.db.collection('users') // if the id is an ObjectId format, you need to create a new ObjectId const id = this.mongo.ObjectId(req.params.id) - users.findOne({ id }, (err, user) => { - if (err) { - reply.send(err) - return - } - reply.send(user) - }) + try { + const user = await users.findOne({ id }) + return user + } catch (err) { + return err + } }) fastify.listen({ port: 3000 }, err => { From bfedfd12e8deecbc2cb6be58e1e6a7e226b3020b Mon Sep 17 00:00:00 2001 From: Ivad Yves HABIMANA <54445417+Yvad60@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:10:33 +0200 Subject: [PATCH 0249/1295] ci: Install pnpm version 7 to support Node.js 14 in CI (#4645) * Update pnpm ci flow to support node 16 * Add a colon to the pnpm version * Add comment for why pnpm v7 * update to pnpm@7 in integration actions --------- Co-authored-by: James Sumners --- .github/workflows/integration.yml | 6 ++++-- .github/workflows/package-manager-ci.yml | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ee1f6f22bef..2571439fa7a 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -32,8 +32,10 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install Pnpm - run: | - npm i -g pnpm + uses: pnpm/action-setup@v2 + with: + # pnpm@8 does not support Node.js 14 + version: 7 - name: Install Production run: | diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index b6ed40c2f03..102ea8ab0be 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -29,9 +29,10 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install with pnpm - run: | - curl -L https://unpkg.com/@pnpm/self-installer | node - pnpm install + uses: pnpm/action-setup@v2 + with: + # pnpm@8 does not support Node.js 14 + version: 7 - name: Run tests run: | From 1c455a4ef58c9cbdf9fed09eb5e2f032c025c0e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:16:42 +0000 Subject: [PATCH 0250/1295] build(deps-dev): Bump typescript from 4.9.5 to 5.0.3 (#4658) Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.5 to 5.0.3. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/commits) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index acd019a5dfd..89d98a3b385 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "standard": "^17.0.0", "tap": "^16.3.0", "tsd": "^0.26.0", - "typescript": "^4.8.3", + "typescript": "^5.0.3", "undici": "^5.10.0", "vary": "^1.1.2", "yup": "^1.0.0" From 6eb145aeb905b6c9e1a84cdc67ae8b9f038fd5fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:23:04 +0000 Subject: [PATCH 0251/1295] build(deps-dev): Bump @sinclair/typebox from 0.25.24 to 0.26.8 (#4660) Bumps [@sinclair/typebox](https://github.com/sinclairzx81/typebox) from 0.25.24 to 0.26.8. - [Release notes](https://github.com/sinclairzx81/typebox/releases) - [Commits](https://github.com/sinclairzx81/typebox/compare/0.25.24...0.26.8) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89d98a3b385..4d81ab72c45 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "homepage": "https://www.fastify.io/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.25.2", + "@sinclair/typebox": "^0.26.8", "@sinonjs/fake-timers": "^10.0.0", "@types/node": "^18.7.18", "@typescript-eslint/eslint-plugin": "^5.37.0", From ab32174cd8dfb3e2099a98ccdd599dd030f8738e Mon Sep 17 00:00:00 2001 From: Roberto Bianchi Date: Wed, 5 Apr 2023 10:50:26 +0200 Subject: [PATCH 0252/1295] Add `fastify-koa` plugin (#4654) * Add `fastify-koa` plugin * Update Ecosystem.md --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index ee12e5320ef..f025230def3 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -394,6 +394,8 @@ section. plugin for supporting KnexJS Query Builder. - [`fastify-knexjs-mock`](https://github.com/chapuletta/fastify-knexjs-mock) Fastify Mock KnexJS for testing support. +- [`fastify-koa`](https://github.com/rozzilla/fastify-koa) Convert Koa +middlewares into Fastify plugins - [`fastify-kubernetes`](https://github.com/greguz/fastify-kubernetes) Fastify Kubernetes client plugin. - [`fastify-language-parser`](https://github.com/lependu/fastify-language-parser) From 30eba8b97be115843f00f63ea377d80ac8940c73 Mon Sep 17 00:00:00 2001 From: Luis Orbaiceta <44276180+luisorbaiceta@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:09:44 +0200 Subject: [PATCH 0253/1295] chore: prevent labeler from removing existing labels (#4662) * Update labeler.yml Prevent labeler from removing existing labels * Add warning for workaround --- .github/workflows/labeler.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index e0fdabd7396..903bb139647 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -11,4 +11,6 @@ jobs: steps: - uses: actions/labeler@v4 with: + # Workaround only valid until the next major version bump (v5) + sync-labels: '' repo-token: "${{ secrets.GITHUB_TOKEN }}" From d0a1e8c1c9be33aa6cae9631163dd18e2bd3f784 Mon Sep 17 00:00:00 2001 From: Fran Herrero Date: Fri, 7 Apr 2023 08:56:51 +0200 Subject: [PATCH 0254/1295] fix: Emit a warning when validating undefined schemas (#4647) --- lib/validation.js | 10 ++- lib/warnings.js | 2 + test/schema-feature.test.js | 163 ++++++++++++++++++++++++++++-------- 3 files changed, 136 insertions(+), 39 deletions(-) diff --git a/lib/validation.js b/lib/validation.js index 981dcb9d402..9ab0839991a 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -13,6 +13,8 @@ const { FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX } = require('./errors') +const warning = require('./warnings') + function compileSchemasForSerialization (context, compile) { if (!context.schema || !context.schema.response) { return @@ -81,25 +83,25 @@ function compileSchemasForValidation (context, compile, isCustom) { } context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' }) } else if (Object.hasOwnProperty.call(schema, 'headers')) { - throw new Error('headers schema is undefined') + warning.emit('FSTWRN001', 'headers', method, url) } if (schema.body) { context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) } else if (Object.hasOwnProperty.call(schema, 'body')) { - throw new Error('body schema is undefined') + warning.emit('FSTWRN001', 'body', method, url) } if (schema.querystring) { context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' }) } else if (Object.hasOwnProperty.call(schema, 'querystring')) { - throw new Error('querystring schema is undefined') + warning.emit('FSTWRN001', 'querystring', method, url) } if (schema.params) { context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' }) } else if (Object.hasOwnProperty.call(schema, 'params')) { - throw new Error('params schema is undefined') + warning.emit('FSTWRN001', 'params', method, url) } } diff --git a/lib/warnings.js b/lib/warnings.js index 3105f1e8558..eb9e653a1cb 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -27,4 +27,6 @@ warning.create('FastifyDeprecation', 'FSTDEP013', 'Direct return of "trailers" f 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.') +warning.create('FastifyWarning', 'FSTWRN001', 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', { unlimited: true }) + module.exports = warning diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index 083c89f078b..6cc9df29491 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -6,6 +6,7 @@ const fp = require('fastify-plugin') const deepClone = require('rfdc')({ circles: true, proto: false }) const Ajv = require('ajv') const { kSchemaController } = require('../lib/symbols.js') +const warning = require('../lib/warnings') const echoParams = (req, reply) => { reply.send(req.params) } const echoBody = (req, reply) => { reply.send(req.body) } @@ -253,88 +254,180 @@ test('Should not change the input schemas', t => { }) }) -test('Should throw if the schema body is undefined', t => { - t.plan(2) +test('Should emit warning if the schema headers is undefined', t => { + t.plan(4) const fastify = Fastify() - fastify.get('/:id', { + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyWarning') + t.equal(warning.code, 'FSTWRN001') + } + + t.teardown(() => { + process.removeListener('warning', onWarning) + warning.emitted.set('FSTWRN001', false) + }) + + fastify.post('/:id', { handler: echoParams, schema: { - body: undefined + headers: undefined } }) - fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error body schema is undefined') + fastify.inject({ + method: 'POST', + url: '/123' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) }) }) -test('Should throw if the schema headers is undefined', t => { - t.plan(2) +test('Should emit warning if the schema body is undefined', t => { + t.plan(4) const fastify = Fastify() - fastify.get('/:id', { + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyWarning') + t.equal(warning.code, 'FSTWRN001') + } + + t.teardown(() => { + process.removeListener('warning', onWarning) + warning.emitted.set('FSTWRN001', false) + }) + + fastify.post('/:id', { handler: echoParams, schema: { - headers: undefined + body: undefined } }) - fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error headers schema is undefined') + fastify.inject({ + method: 'POST', + url: '/123' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) }) }) -test('Should throw if the schema params is undefined', t => { - t.plan(2) +test('Should emit warning if the schema query is undefined', t => { + t.plan(4) const fastify = Fastify() - fastify.get('/:id', { + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyWarning') + t.equal(warning.code, 'FSTWRN001') + } + + t.teardown(() => { + process.removeListener('warning', onWarning) + warning.emitted.set('FSTWRN001', false) + }) + + fastify.post('/:id', { handler: echoParams, schema: { - params: undefined + querystring: undefined } }) - fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error params schema is undefined') + fastify.inject({ + method: 'POST', + url: '/123' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) }) }) -test('Should throw if the schema query is undefined', t => { - t.plan(2) +test('Should emit warning if the schema params is undefined', t => { + t.plan(4) const fastify = Fastify() - fastify.get('/:id', { + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyWarning') + t.equal(warning.code, 'FSTWRN001') + } + + t.teardown(() => { + process.removeListener('warning', onWarning) + warning.emitted.set('FSTWRN001', false) + }) + + fastify.post('/:id', { handler: echoParams, schema: { - querystring: undefined + params: undefined } }) - fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error querystring schema is undefined') + fastify.inject({ + method: 'POST', + url: '/123' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) }) }) -test('Should throw if the schema query is undefined', t => { - t.plan(2) +test('Should emit a warning for every route with undefined schema', t => { + t.plan(16) const fastify = Fastify() - fastify.get('/:id', { + let runs = 0 + const expectedWarningEmitted = [0, 1, 2, 3] + // It emits 4 warnings: + // - 2 - GET and HEAD for /undefinedParams/:id + // - 2 - GET and HEAD for /undefinedBody/:id + // => 3 x 4 assertions = 12 assertions + function onWarning (warning) { + t.equal(warning.name, 'FastifyWarning') + t.equal(warning.code, 'FSTWRN001') + t.equal(runs++, expectedWarningEmitted.shift()) + } + + process.on('warning', onWarning) + t.teardown(() => { + process.removeListener('warning', onWarning) + warning.emitted.set('FSTWRN001', false) + }) + + fastify.get('/undefinedParams/:id', { handler: echoParams, schema: { - querystring: undefined + params: undefined } }) - fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error querystring schema is undefined') + fastify.get('/undefinedBody/:id', { + handler: echoParams, + schema: { + body: undefined + } + }) + + fastify.inject({ + method: 'GET', + url: '/undefinedParams/123' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/undefinedBody/123' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) }) }) From 849ddfa694ade03f905106f95a08962ce1929726 Mon Sep 17 00:00:00 2001 From: Mateo Nunez Date: Sat, 8 Apr 2023 16:44:52 +0200 Subject: [PATCH 0255/1295] chore(ecosystem): rename to fastify-orama plugin (#4667) --- docs/Guides/Ecosystem.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f025230def3..0616c76dce5 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -174,7 +174,6 @@ section. - [`@immobiliarelabs/fastify-sentry`](https://github.com/immobiliare/fastify-sentry) Sentry errors handler that just works! Install, add your DSN and you're good to go! -- [`@mateonunez/fastify-lyra`](https://github.com/mateonunez/fastify-lyra) A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine on Fastify - [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit) @@ -409,7 +408,6 @@ middlewares into Fastify plugins - [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua scripts with [fastify-redis](https://github.com/fastify/fastify-redis) and [lured](https://github.com/enobufs/lured). -- [`fastify-lyra`](https://github.com/mateonunez/fastify-lyra) A plugin to implement [Lyra](https://github.com/LyraSearch/lyra) search engine on Fastify. - [`fastify-mailer`](https://github.com/coopflow/fastify-mailer) Plugin to @@ -477,6 +475,7 @@ middlewares into Fastify plugins - [`fastify-oracle`](https://github.com/cemremengu/fastify-oracle) Attaches an [`oracledb`](https://github.com/oracle/node-oracledb) connection pool to a Fastify server instance. +- [`fastify-orama`](https://github.com/mateonunez/fastify-orama) - [`fastify-orientdb`](https://github.com/mahmed8003/fastify-orientdb) Fastify OrientDB connection plugin, with which you can share the OrientDB connection across every part of your server. From 8cf305473599b44ef6468720fbbff3d55c683818 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Sun, 9 Apr 2023 20:19:29 +0300 Subject: [PATCH 0256/1295] Add workflow for benchmarking POST with custom parsers (#4669) --- .github/workflows/benchmark-parser.yml | 96 ++++++++++++++++++++++++++ examples/benchmark/parser.js | 47 +++++++++++++ package.json | 1 + 3 files changed, 144 insertions(+) create mode 100644 .github/workflows/benchmark-parser.yml create mode 100644 examples/benchmark/parser.js diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml new file mode 100644 index 00000000000..712425d832f --- /dev/null +++ b/.github/workflows/benchmark-parser.yml @@ -0,0 +1,96 @@ +name: Benchmark-parser + +on: + pull_request_target: + types: [labeled] + +jobs: + benchmark: + if: ${{ github.event.label.name == 'benchmark' }} + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + PR-BENCH-14: ${{ steps.benchmark-pr.outputs.BENCH_RESULT14 }} + PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }} + PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} + MAIN-BENCH-14: ${{ steps.benchmark-main.outputs.BENCH_RESULT14 }} + MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }} + MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} + strategy: + matrix: + node-version: [14, 16, 18] + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + ref: ${{github.event.pull_request.head.sha}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Install + run: | + npm install --only=production --ignore-scripts + + - name: Run benchmark + id: benchmark-pr + run: | + npm run --silent benchmark:parser > ./bench-result.md + result=$(awk '/requests in/' ./bench-result.md) + echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" + + # main benchmark + - uses: actions/checkout@v3 + with: + persist-credentials: false + ref: 'main' + + - name: Install + run: | + npm install --only=production --ignore-scripts + + - name: Run benchmark + id: benchmark-main + run: | + npm run --silent benchmark:parser > ./bench-result.md + result=$(awk '/requests in/' ./bench-result.md) + echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" + + output-benchmark: + needs: [benchmark] + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + message: | + **Node**: 14 + **Type**: Parser + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-14 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-14 }} + + --- + + **Node**: 16 + **Type**: Parser + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }} + + --- + + **Node**: 18 + **Type**: Parser + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} + + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: | + benchmark + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/benchmark/parser.js b/examples/benchmark/parser.js new file mode 100644 index 00000000000..54109722505 --- /dev/null +++ b/examples/benchmark/parser.js @@ -0,0 +1,47 @@ +'use strict' + +const fastify = require('../../fastify')({ + logger: false +}) + +const jsonParser = require('fast-json-body') +const querystring = require('querystring') + +// Handled by fastify +// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/json' http://localhost:3000/ + +// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/jsoff' http://localhost:3000/ +fastify.addContentTypeParser('application/jsoff', function (request, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) +}) + +// curl -X POST -d 'hello=world' -H'Content-type: application/x-www-form-urlencoded' http://localhost:3000/ +fastify.addContentTypeParser('application/x-www-form-urlencoded', function (request, payload, done) { + let body = '' + payload.on('data', function (data) { + body += data + }) + payload.on('end', function () { + try { + const parsed = querystring.parse(body) + done(null, parsed) + } catch (e) { + done(e) + } + }) + payload.on('error', done) +}) + +// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/vnd.custom+json' http://localhost:3000/ +fastify.addContentTypeParser(/^application\/.+\+json$/, { parseAs: 'string' }, fastify.getDefaultJsonParser('error', 'ignore')) + +fastify + .post('/', function (req, reply) { + reply.send(req.body) + }) + +fastify.listen({ port: 3000 }, (err, address) => { + if (err) throw err +}) diff --git a/package.json b/package.json index 4d81ab72c45..3ab6a0c2033 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "scripts": { "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"", "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"", + "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -b \"{\"hello\":\"world\"}\" -H \"content-type=application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "coverage": "npm run unit -- --cov --coverage-report=html", "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage -R terse", From 12914de76a98f2f71d4cc4ba8786d0fb4cd73823 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 9 Apr 2023 20:12:23 +0200 Subject: [PATCH 0257/1295] chore: add `github actions` label automatically (#4664) --- .github/labeler.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index 71d7124fbb9..5c8239485a8 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -2,6 +2,9 @@ documentation: - all: ["docs/**/*"] +"github actions": +- all: [".github/workflows/*"] + # PRs that only touch type files typescript: - all: ["**/*[.|-]d.ts"] From 4c02647b790b955e463e08da9811d501e666b227 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Richardet Date: Mon, 10 Apr 2023 09:50:38 +0200 Subject: [PATCH 0258/1295] Adds async hooks signature on route level (#4655) --- docs/Reference/Hooks.md | 6 ++ test/route-hooks.test.js | 29 ++++++++ test/types/hooks.test-d.ts | 66 ++++++++++++++--- test/types/route.test-d.ts | 111 +++++++++++++++++++++++++++-- test/types/type-provider.test-d.ts | 87 +++++++++++++++++++--- types/route.d.ts | 52 +++++++++++--- 6 files changed, 314 insertions(+), 37 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 12ca0b05833..8c42c716b54 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -655,6 +655,12 @@ fastify.route({ // This hook will always be executed after the shared `onRequest` hooks done() }, + // // Example with an async hook. All hooks support this syntax + // + // onRequest: async function (request, reply) { + // // This hook will always be executed after the shared `onRequest` hooks + // await ... + // } onResponse: function (request, reply, done) { // this hook will always be executed after the shared `onResponse` hooks done() diff --git a/test/route-hooks.test.js b/test/route-hooks.test.js index 43c78631a8e..3b9e7e979ce 100644 --- a/test/route-hooks.test.js +++ b/test/route-hooks.test.js @@ -101,6 +101,35 @@ function testExecutionHook (hook) { }) }) + test(`${hook} option could accept an array of async functions`, t => { + t.plan(3) + const fastify = Fastify() + const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { + get: function () { return ++this.calledTimes } + }) + + fastify.post('/', { + [hook]: [ + async (req, reply) => { + t.equal(checker.check, 1) + }, + async (req, reply) => { + t.equal(checker.check, 2) + } + ] + }, (req, reply) => { + reply.send({}) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.error(err) + }) + }) + test(`${hook} option does not interfere with ${hook} hook`, t => { t.plan(7) const fastify = Fastify() diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 0444f5662e8..45c16819835 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -124,7 +124,7 @@ server.addHook('onRequestAbort', function (request, done) { server.addHook('onRoute', function (opts) { expectType(this) - expectType(opts) + expectType(opts) }) server.addHook('onRegister', (instance, opts, done) => { @@ -256,40 +256,84 @@ type CustomContextConfig = FastifyContextConfig & { server.route({ method: 'GET', url: '/', - handler: () => {}, - onRequest: (request, reply) => { + handler: () => { }, + onRequest: (request, reply, done) => { expectType(request.context.config) expectType(reply.context.config) }, - preParsing: (request, reply) => { + preParsing: (request, reply, payload, done) => { expectType(request.context.config) expectType(reply.context.config) }, - preValidation: (request, reply) => { + preValidation: (request, reply, done) => { expectType(request.context.config) expectType(reply.context.config) }, - preHandler: (request, reply) => { + preHandler: (request, reply, done) => { expectType(request.context.config) expectType(reply.context.config) }, - preSerialization: (request, reply) => { + preSerialization: (request, reply, payload, done) => { expectType(request.context.config) expectType(reply.context.config) }, - onSend: (request, reply) => { + onSend: (request, reply, payload, done) => { expectType(request.context.config) expectType(reply.context.config) }, - onResponse: (request, reply) => { + onResponse: (request, reply, done) => { expectType(request.context.config) expectType(reply.context.config) }, - onTimeout: (request, reply) => { + onTimeout: (request, reply, done) => { expectType(request.context.config) expectType(reply.context.config) }, - onError: (request, reply) => { + onError: (request, reply, error, done) => { + expectType(request.context.config) + expectType(reply.context.config) + } +}) + +type CustomContextRequest = FastifyRequest +type CustomContextReply = FastifyReply +server.route({ + method: 'GET', + url: '/', + handler: () => { }, + onRequest: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + preParsing: async (request: CustomContextRequest, reply: CustomContextReply, payload: RequestPayload) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + preValidation: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + preHandler: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + preSerialization: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + onSend: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + onResponse: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + onTimeout: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + onError: async (request: CustomContextRequest, reply: CustomContextReply, error: FastifyError) => { expectType(request.context.config) expectType(reply.context.config) } diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 697105dca8a..53bb0bb4d0f 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -35,9 +35,9 @@ const routeHandlerWithReturnValue: RouteHandlerMethod = function (request, reply return reply.send() } -type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'options' +type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'options'; -;['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE', 'OPTIONS'].forEach(method => { +['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE', 'OPTIONS'].forEach(method => { // route method expectType(fastify().route({ method: method as HTTPMethods, @@ -146,7 +146,7 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(res.context.config.bar) expectType(res.statusCode) }, - onError: (req, res, done) => { + onError: (req, res, error, done) => { expectType(req.body) expectType(req.query) expectType(req.params) @@ -156,7 +156,7 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(res.context.config.foo) expectType(res.context.config.bar) }, - preSerialization: (req, res, done) => { + preSerialization: (req, res, payload, done) => { expectType(req.body) expectType(req.query) expectType(req.params) @@ -166,7 +166,108 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(res.context.config.foo) expectType(res.context.config.bar) }, - onSend: (req, res, done) => { + onSend: (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + }, + handler: (req, res) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + } + }) + + fastify().route({ + url: '/', + method: method as HTTPMethods, + config: { foo: 'bar', bar: 100 }, + prefixTrailingSlash: 'slash', + onRequest: async (req, res, done) => { // these handlers are tested in `hooks.test-d.ts` + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + }, + preParsing: async (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + expectType(payload) + expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + }, + preValidation: async (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + }, + preHandler: async (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + }, + onResponse: async (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + expectType(res.statusCode) + }, + onError: async (req, res, error, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + }, + preSerialization: async (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(res.context.config.foo) + expectType(res.context.config.bar) + }, + onSend: async (req, res, payload, done) => { expectType(req.body) expectType(req.query) expectType(req.params) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index ac798e1863b..c9fb9f1212b 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -148,6 +148,73 @@ expectAssignable(server.withTypeProvider().withTypeProvider().get( + '/', + { + schema: { + body: Type.Object({ + x: Type.Number(), + y: Type.String(), + z: Type.Boolean() + }) + }, + preHandler: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preParsing: (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preSerialization: (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preValidation: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onError: (req, reply, error, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onRequest: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onResponse: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onTimeout: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onSend: (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } + }, + req => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +)) + +// Async handlers + expectAssignable(server.withTypeProvider().get( '/', { @@ -158,47 +225,47 @@ expectAssignable(server.withTypeProvider().get( z: Type.Boolean() }) }, - preHandler: req => { + preHandler: async (req, reply, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) }, - preParsing: req => { + preParsing: async (req, reply, payload, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) }, - preSerialization: req => { + preSerialization: async (req, reply, payload, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) }, - preValidation: req => { + preValidation: async (req, reply, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) }, - onError: req => { + onError: async (req, reply, error, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) }, - onRequest: req => { + onRequest: async (req, reply, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) }, - onResponse: req => { + onResponse: async (req, reply, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) }, - onTimeout: req => { + onTimeout: async (req, reply, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) }, - onSend: req => { + onSend: async (req, reply, payload, done) => { expectType(req.body.x) expectType(req.body.y) expectType(req.body.z) @@ -793,7 +860,7 @@ interface InlineHandlerProvider extends FastifyTypeProvider { output: 'handler-i expectAssignable(server.withTypeProvider().get( '/', { - onRequest: (req, res) => { + onRequest: (req, res, done) => { expectType<'handler-inline'>(req.body) }, schema: { body: null } diff --git a/types/route.d.ts b/types/route.d.ts index 8769c39eff6..937516340f5 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -3,7 +3,7 @@ import { FastifyRequest, RequestGenericInterface } from './request' import { FastifyReply, ReplyGenericInterface } from './reply' import { FastifySchema, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorFormatter } from './schema' import { HTTPMethods, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' -import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler, onRequestAbortHookHandler } from './hooks' +import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, preParsingAsyncHookHandler, preValidationAsyncHookHandler, preHandlerAsyncHookHandler, preSerializationAsyncHookHandler, onSendAsyncHookHandler, onResponseAsyncHookHandler, onTimeoutAsyncHookHandler, onErrorAsyncHookHandler, onRequestAbortAsyncHookHandler } from './hooks' import { FastifyError } from '@fastify/error' import { FastifyContext } from './context' import { @@ -44,16 +44,46 @@ export interface RouteShorthandOptions< schemaErrorFormatter?: SchemaErrorFormatter; // hooks - onRequest?: onRequestHookHandler | onRequestHookHandler[]; - preParsing?: preParsingHookHandler | preParsingHookHandler[]; - preValidation?: preValidationHookHandler | preValidationHookHandler[]; - preHandler?: preHandlerHookHandler | preHandlerHookHandler[]; - preSerialization?: preSerializationHookHandler | preSerializationHookHandler[]; - onSend?: onSendHookHandler | onSendHookHandler[]; - onResponse?: onResponseHookHandler | onResponseHookHandler[]; - onTimeout?: onTimeoutHookHandler | onTimeoutHookHandler[]; - onError?: onErrorHookHandler | onErrorHookHandler[]; - onRequestAbort?: onRequestAbortHookHandler | onRequestAbortHookHandler[]; + onRequest?: onRequestHookHandler + | onRequestHookHandler[] + | onRequestAsyncHookHandler + | onRequestAsyncHookHandler[]; + preParsing?: preParsingHookHandler + | preParsingHookHandler[] + | preParsingAsyncHookHandler + | preParsingAsyncHookHandler[]; + preValidation?: preValidationHookHandler + | preValidationHookHandler[] + | preValidationAsyncHookHandler + | preValidationAsyncHookHandler[]; + preHandler?: preHandlerHookHandler + | preHandlerHookHandler[] + | preHandlerAsyncHookHandler + | preHandlerAsyncHookHandler[]; + preSerialization?: preSerializationHookHandler + | preSerializationHookHandler[] + | preSerializationAsyncHookHandler + | preSerializationAsyncHookHandler[]; + onSend?: onSendHookHandler + | onSendHookHandler[] + | onSendAsyncHookHandler + | onSendAsyncHookHandler[]; + onResponse?: onResponseHookHandler + | onResponseHookHandler[] + | onResponseAsyncHookHandler + | onResponseAsyncHookHandler[]; + onTimeout?: onTimeoutHookHandler + | onTimeoutHookHandler[] + | onTimeoutAsyncHookHandler + | onTimeoutAsyncHookHandler[]; + onError?: onErrorHookHandler + | onErrorHookHandler[] + | onErrorAsyncHookHandler + | onErrorAsyncHookHandler[]; + onRequestAbort?: onRequestAbortHookHandler + | onRequestAbortHookHandler[] + | onRequestAbortAsyncHookHandler + | onRequestAbortAsyncHookHandler[]; } /** From 4363cd15d70c2b1d45044ca178e61bb5710ab841 Mon Sep 17 00:00:00 2001 From: "Simen A. W. Olsen" Date: Mon, 10 Apr 2023 23:25:37 +0200 Subject: [PATCH 0259/1295] chore: update from cobraz to simenandre (#4671) --- docs/Guides/Ecosystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 0616c76dce5..b121a2c09a5 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -638,7 +638,7 @@ middlewares into Fastify plugins - [`openapi-validator-middleware`](https://github.com/PayU/openapi-validator-middleware#fastify) Swagger and OpenAPI 3.0 spec-based request validation middleware that supports Fastify. -- [`pubsub-http-handler`](https://github.com/cobraz/pubsub-http-handler) A Fastify +- [`pubsub-http-handler`](https://github.com/simenandre/pubsub-http-handler) A Fastify plugin to easily create Google Cloud PubSub endpoints. - [`sequelize-fastify`](https://github.com/hsynlms/sequelize-fastify) A simple and lightweight Sequelize plugin for Fastify. From 662706bdca4c385616f3f3d1806c4b94a2a97b8a Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Thu, 13 Apr 2023 08:46:19 +0300 Subject: [PATCH 0260/1295] chore(docs): add link to routeOptions (#4678) Link from Hooks onRoute to routeOptions definition --- docs/Reference/Hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 8c42c716b54..f385f5aa09f 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -437,7 +437,7 @@ fastify.addHook('onClose', async (instance) => { ### onRoute -Triggered when a new route is registered. Listeners are passed a `routeOptions` +Triggered when a new route is registered. Listeners are passed a [`routeOptions`](./Routes.md#routes-options) object as the sole parameter. The interface is synchronous, and, as such, the listeners are not passed a callback. This hook is encapsulated. From fc9e7c02eca13f26fbd5db37af1052e332c741c3 Mon Sep 17 00:00:00 2001 From: Tim Shilov Date: Mon, 17 Apr 2023 17:19:12 +0100 Subject: [PATCH 0261/1295] docs: incorrect example for 'onRequestAbort' hook (#4679) --- docs/Reference/Hooks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index f385f5aa09f..e4076e5c489 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -271,14 +271,14 @@ has been hung up. Therefore, you will not be able to send data to the client. ### onRequestAbort ```js -fastify.addHook('onRequestAbort', (request, reply, done) => { +fastify.addHook('onRequestAbort', (request, done) => { // Some code done() }) ``` Or `async/await`: ```js -fastify.addHook('onRequestAbort', async (request, reply) => { +fastify.addHook('onRequestAbort', async (request) => { // Some code await asyncMethod() }) From 66393a4a557fb83d24cfa12487cb85844d913d24 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 19 Apr 2023 09:15:30 +0200 Subject: [PATCH 0262/1295] fixup package-manager-ci.yml Signed-off-by: Matteo Collina --- .github/workflows/package-manager-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 102ea8ab0be..23e9d7fb1e9 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -34,6 +34,8 @@ jobs: # pnpm@8 does not support Node.js 14 version: 7 + - run: pnpm install + - name: Run tests run: | pnpm run test:ci @@ -62,6 +64,8 @@ jobs: curl -o- -L https://yarnpkg.com/install.sh | bash yarn install --ignore-engines + - run: yarn + - name: Run tests run: | yarn run test:ci From 0042248c3a1665a1e2ebc60b34c8bab7c2710518 Mon Sep 17 00:00:00 2001 From: Nex Zhu <4370605+NexZhu@users.noreply.github.com> Date: Wed, 19 Apr 2023 15:57:01 +0800 Subject: [PATCH 0263/1295] doc: add fastify-type-provider-effect-schema (#4683) --- docs/Guides/Ecosystem.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index b121a2c09a5..ef907ec9f92 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -587,6 +587,10 @@ middlewares into Fastify plugins TOTP (e.g. for 2FA). - [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools) Useful functions for Twitch Extension Backend Services (EBS). +- [`fastify-type-provider-effect-schema`](https://github.com/daotl/fastify-type-provider-effect-schema) + Fastify + [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) + for [@effect/schema](https://github.com/effect-ts/schema). - [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod) Fastify [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) From 831500088f5ba48f02129db2fc822946d40597ff Mon Sep 17 00:00:00 2001 From: Ali Akbar <48174866+AliakbarETH@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:58:57 +0500 Subject: [PATCH 0264/1295] docs: update prototype poisoning (#4651) * Updated prototype poisoning doc #3610 * Update docs/Guides/Prototype-Poisoning.md Co-authored-by: Manuel Spigolon * Update docs/Guides/Prototype-Poisoning.md Co-authored-by: Manuel Spigolon * Update docs/Guides/Prototype-Poisoning.md Co-authored-by: Manuel Spigolon * Linting issue resolved for line 17 and 19 * Fixed the story/non technical portions --------- Co-authored-by: Manuel Spigolon --- docs/Guides/Prototype-Poisoning.md | 70 +++++++++++++----------------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/docs/Guides/Prototype-Poisoning.md b/docs/Guides/Prototype-Poisoning.md index 8d29cf6e7b6..bf5f502d887 100644 --- a/docs/Guides/Prototype-Poisoning.md +++ b/docs/Guides/Prototype-Poisoning.md @@ -4,29 +4,24 @@ > but otherwise remains the same. The original HTML can be retrieved from the > above permission link. -## A Tale of (prototype) Poisoning +## History behind prototype poisoning -This story is a behind-the-scenes look at the process and drama created by a -particularity interesting web security issue. It is also a perfect illustration -of the efforts required to maintain popular pieces of open source software and -the limitations of existing communication channels. - -But first, if you use a JavaScript framework to process incoming JSON data, take -a moment to read up on [Prototype -Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96) -in general, and the specific [technical -details](https://github.com/hapijs/hapi/issues/3916) of this issue. I'll explain -it all in a bit, but since this could be a critical issue, you might want to -verify your own code first. While this story is focused on a specific framework, -any solution that uses `JSON.parse()` to process external data is potentially at -risk. +Based on the article by Eran Hammer,the issue is created by a web security bug. +It is also a perfect illustration of the efforts required to maintain +open-source software and the limitations of existing communication channels. + +But first, if we use a JavaScript framework to process incoming JSON data, take +a moment to read up on [Prototype Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96) +in general, and the specific [technical details] +(https://github.com/hapijs/hapi/issues/3916) of this issue. +This could be a critical issue so, we might need to verify your own code first. +It focuses on specific framework however, any solution that uses `JSON.parse()` +to process external data is potentially at risk. ### BOOM -Our story begins with a bang. - The engineering team at Lob (long time generous supporters of my work!) reported a critical security vulnerability they identified in our data validation module — [joi](https://github.com/hapijs/joi). They provided some technical @@ -34,21 +29,22 @@ details and a proposed solution. The main purpose of a data validation library is to ensure the output fully complies with the rules defined. If it doesn't, validation fails. If it passes, -your can blindly trust that the data you are working with is safe. In fact, most +we can blindly trust that the data you are working with is safe. In fact, most developers treat validated input as completely safe from a system integrity -perspective. This is crucial. +perspective which is crucial! -In our case, the Lob team provided an example where some data was able to sneak +In our case, the Lob team provided an example where some data was able to escape by the validation logic and pass through undetected. This is the worst possible defect a validation library can have. ### Prototype in a nutshell -To understand this story, you need to understand how JavaScript works a bit. +To understand this, we need to understand how JavaScript works a bit. Every object in JavaScript can have a prototype. It is a set of methods and -properties it "inherits" from another object. I put inherits in quotes because -JavaScript isn't really an object oriented language. +properties it "inherits" from another object. I have put inherits in quotes +because JavaScript isn't really an object-oriented language.It is prototype- +based object-oriented language. A long time ago, for a bunch of irrelevant reasons, someone decided that it would be a good idea to use the special property name `__proto__` to access (and @@ -68,22 +64,21 @@ To demonstrate: { b: 5 } ``` -As you can see, the object doesn't have a `c` property, but its prototype does. +The object doesn't have a `c` property, but its prototype does. When validating the object, the validation library ignores the prototype and only validates the object's own properties. This allows `c` to sneak in via the prototype. -Another important part of this story is the way `JSON.parse()` — a utility -provided by the language to convert JSON formatted text into objects  —  handles -this magic `__proto__` property name. +Another important part is the way `JSON.parse()` — a utility +provided by the language to convert JSON formatted text into +objects  —  handles this magic `__proto__` property name. ``` -> const text = '{ "b": 5, "__proto__": { "c": 6 } }'; +> const text = '{"b": 5, "__proto__": { "c": 6 }}'; > const a = JSON.parse(text); > a; -{ b: 5, __proto__: { c: 6 } } +{b: 5, __proto__: { c: 6 }} ``` - Notice how `a` has a `__proto__` property. This is not a prototype reference. It is a simple object property key, just like `b`. As we've seen from the first example, we can't actually create this key through assignment as that invokes @@ -111,17 +106,17 @@ level properties of `a` into the provided empty `{}` object), the magic Surprise! -Put together, if you get some external text input, parse it with `JSON.parse()` -then perform some simple manipulation of that object (say, shallow clone and add -an `id` ), and then pass it to our validation library, anything passed through -via `__proto__` would sneak in undetected. +If you get some external text input and parse it with `JSON.parse()` +then perform some simple manipulation of that object (e.g shallow clone and add +an `id` ), and pass it to our validation library, it would sneak in undetected +via `__proto__`. ### Oh joi! The first question is, of course, why does the validation module **joi** ignore the prototype and let potentially harmful data through? We asked ourselves the -same question and our instant thought was "it was an oversight". A bug. A really +same question and our instant thought was "it was an oversight". A bug - a really big mistake. The joi module should not have allowed this to happen. But… While joi is used primarily for validating web input data, it also has a @@ -166,7 +161,6 @@ will share with the world how to exploit this vulnerability while also making it more time consuming for systems to upgrade (breaking changes never get applied automatically by build tools). -Lose — Lose. ### A detour @@ -386,6 +380,4 @@ plan](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi- coming in March. You can read more about it [here](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898). -Of all the time consuming things, security is at the very top. I hope this story -successfully conveyed not just the technical details, but also the human drama and -what it takes to keep the web secure. + From bc3f95734581c1fa22e1c4a4da9b898cf9e3ec7f Mon Sep 17 00:00:00 2001 From: Mateus Sampaio Date: Wed, 19 Apr 2023 04:59:26 -0300 Subject: [PATCH 0265/1295] docs: add optional URL param to upgrade guide and route docs (#4680) * Update v4 migration guide and route docs * Apply suggestions from code review Thanks, @Fdawgs Co-authored-by: Frazer Smith --------- Co-authored-by: Frazer Smith --- docs/Guides/Migration-Guide-V4.md | 21 +++++++++++++++++++++ docs/Reference/Routes.md | 11 +++++++++++ 2 files changed, 32 insertions(+) diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 71c228d7f56..2eee6bffabf 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -130,6 +130,27 @@ As a result, if you specify an `onRoute` hook in a plugin you should now either: }); ``` +### Optional URL parameters + +If you've already used any implicitly optional parameters, you'll get a 404 +error when trying to access the route. You will now need to declare the +optional parameters explicitly. + +For example, if you have the same route for listing and showing a post, +refactor this: +```js +fastify.get('/posts/:id', (request, reply) => { + const { id } = request.params; +}); +``` + +Into this: +```js +fastify.get('/posts/:id?', (request, reply) => { + const { id } = request.params; +}); +``` + ## Non-Breaking Changes ### Deprecation of variadic `.listen()` signature diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index b1283a5ba45..8d0ffb522b8 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -290,6 +290,17 @@ fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', function (request, r In this case as parameter separator it is possible to use whatever character is not matched by the regular expression. +The last parameter can be made optional if you add a question mark ("?") to the +end of the parameters name. +```js +fastify.get('/example/posts/:id?', function (request, reply) { + const { id } = request.params; + // your code here +}) +``` +In this case you can request `/example/posts` as well as `/example/posts/1`. +The optional param will be undefined if not specified. + Having a route with multiple parameters may negatively affect performance, so prefer a single parameter approach whenever possible, especially on routes that are on the hot path of your application. If you are interested in how we handle From f8c5bc679b232413c246100c96e0b79a6d3d9c1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Apr 2023 11:49:48 -0400 Subject: [PATCH 0266/1295] build(deps-dev): Bump markdownlint-cli2 from 0.6.0 to 0.7.0 (#4695) Bumps [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/DavidAnson/markdownlint-cli2/releases) - [Changelog](https://github.com/DavidAnson/markdownlint-cli2/blob/main/CHANGELOG.md) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: markdownlint-cli2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ab6a0c2033..794f7e9eb6a 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "json-schema-to-ts": "^2.5.5", "JSONStream": "^1.3.5", "license-checker": "^25.0.1", - "markdownlint-cli2": "^0.6.0", + "markdownlint-cli2": "^0.7.0", "proxyquire": "^2.1.3", "pump": "^3.0.0", "self-cert": "^2.0.0", From 82c1554292fddcf6b2d269ee600f8670fcfee961 Mon Sep 17 00:00:00 2001 From: Mohammad Raufzahed Date: Tue, 25 Apr 2023 11:04:28 +0330 Subject: [PATCH 0267/1295] docs: add fastify-redis-session to ecosystem (#4656) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index ef907ec9f92..e03f2c62b15 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -519,6 +519,8 @@ middlewares into Fastify plugins - [`fastify-redis-channels`](https://github.com/hearit-io/fastify-redis-channels) A plugin for fast, reliable, and scalable channels implementation based on Redis streams. +- [`fastify-redis-session`](https://github.com/mohammadraufzahed/fastify-redis-session) + Redis Session plugin for fastify. - [`fastify-register-routes`](https://github.com/israeleriston/fastify-register-routes) Plugin to automatically load routes from a specified path and optionally limit loaded file names by a regular expression. From 155bda9495a292dde63e126a0ff0b0d96aed9edf Mon Sep 17 00:00:00 2001 From: KaKa Date: Tue, 25 Apr 2023 19:48:12 +0800 Subject: [PATCH 0268/1295] test: refactor logger test (#4648) * test: refactor logger test to async * split into 2 files * fixup: resume ci and remove tap debug mode * fixup: resume ci needs * ci: resume terse reporter --------- Co-authored-by: Manuel Spigolon --- test/logger.test.js | 1721 ------------------------------- test/route.test.js | 2 +- test/serial/logger.0.test.js | 861 ++++++++++++++++ test/serial/logger.1.test.js | 862 ++++++++++++++++ test/serial/tap-parallel-not-ok | 0 5 files changed, 1724 insertions(+), 1722 deletions(-) delete mode 100644 test/logger.test.js create mode 100644 test/serial/logger.0.test.js create mode 100644 test/serial/logger.1.test.js create mode 100644 test/serial/tap-parallel-not-ok diff --git a/test/logger.test.js b/test/logger.test.js deleted file mode 100644 index 8fcaeb4e62f..00000000000 --- a/test/logger.test.js +++ /dev/null @@ -1,1721 +0,0 @@ -'use strict' - -const { test, teardown, before } = require('tap') -const http = require('http') -const stream = require('stream') -const split = require('split2') -const Fastify = require('..') -const pino = require('pino') -const path = require('path') -const os = require('os') -const fs = require('fs') -const sget = require('simple-get').concat -const dns = require('dns') - -const helper = require('./helper') -const { FST_ERR_LOG_INVALID_LOGGER } = require('../lib/errors') - -const files = [] -let count = 0 -let localhost -let localhostForURL - -function file () { - const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${process.hrtime().toString()}-${count++}`) - files.push(file) - return file -} - -before(async function () { - [localhost, localhostForURL] = await helper.getLoopbackHost() -}) - -if (process.env.CI) { - teardown(() => { - files.forEach((file) => { - try { - fs.unlinkSync(file) - } catch (e) { - console.log(e) - } - }) - }) -} - -test('defaults to info level', t => { - let fastify = null - const stream = split(JSON.parse) - try { - fastify = Fastify({ - logger: { - stream - } - }) - } catch (e) { - t.fail() - } - - fastify.get('/', function (req, reply) { - t.ok(req.log) - reply.send({ hello: 'world' }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - dns.lookup('localhost', { all: true }, function (err, addresses) { - t.error(err) - let toSkip = addresses.length - - function skip (data) { - if (--toSkip === 0) { - stream.removeListener('data', skip) - check() - } - } - - stream.on('data', skip) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port) - }) - }) - - function check () { - stream.once('data', line => { - const id = line.reqId - t.ok(line.reqId, 'reqId is defined') - t.ok(line.req, 'req is defined') - t.equal(line.msg, 'incoming request', 'message is set') - t.equal(line.req.method, 'GET', 'method is get') - - stream.once('data', line => { - t.equal(line.reqId, id) - t.ok(line.reqId, 'reqId is defined') - t.ok(line.res, 'res is defined') - t.equal(line.msg, 'request completed', 'message is set') - t.equal(line.res.statusCode, 200, 'statusCode is 200') - t.ok(line.responseTime, 'responseTime is defined') - t.end() - }) - }) - } -}) - -test('test log stream', t => { - t.plan(12) - let fastify = null - const stream = split(JSON.parse) - try { - fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - } catch (e) { - t.fail() - } - - fastify.get('/', function (req, reply) { - t.ok(req.log) - reply.send({ hello: 'world' }) - }) - - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port) - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - - stream.once('data', line => { - const id = line.reqId - t.ok(line.reqId, 'reqId is defined') - t.ok(line.req, 'req is defined') - t.equal(line.msg, 'incoming request', 'message is set') - t.equal(line.req.method, 'GET', 'method is get') - - stream.once('data', line => { - t.equal(line.reqId, id) - t.ok(line.reqId, 'reqId is defined') - t.ok(line.res, 'res is defined') - t.equal(line.msg, 'request completed', 'message is set') - t.equal(line.res.statusCode, 200, 'statusCode is 200') - }) - }) - }) - }) -}) - -test('test error log stream', t => { - t.plan(11) - let fastify = null - const stream = split(JSON.parse) - try { - fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - } catch (e) { - t.fail() - } - - fastify.get('/error', function (req, reply) { - t.ok(req.log) - reply.send(new Error('kaboom')) - }) - - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port + '/error') - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - - stream.once('data', line => { - t.ok(line.reqId, 'reqId is defined') - t.ok(line.req, 'req is defined') - t.equal(line.msg, 'incoming request', 'message is set') - t.equal(line.req.method, 'GET', 'method is get') - - stream.once('data', line => { - t.ok(line.reqId, 'reqId is defined') - t.ok(line.res, 'res is defined') - t.equal(line.msg, 'kaboom', 'message is set') - t.equal(line.res.statusCode, 500, 'statusCode is 500') - }) - }) - }) - }) -}) - -test('can use external logger instance', t => { - const lines = [/^Server listening at /, /^incoming request$/, /^log success$/, /^request completed$/] - t.plan(lines.length + 2) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - const regex = lines.shift() - t.ok(regex.test(line.msg), '"' + line.msg + '" dont match "' + regex + '"') - }) - - const logger = require('pino')(splitStream) - - const localFastify = Fastify({ logger }) - - localFastify.get('/foo', function (req, reply) { - t.ok(req.log) - req.log.info('log success') - reply.send({ hello: 'world' }) - }) - - localFastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - http.get(`http://${localhostForURL}:` + localFastify.server.address().port + '/foo', (res) => { - res.resume() - res.on('end', () => { - localFastify.server.close() - }) - }) - }) -}) - -test('can use external logger instance with custom serializer', t => { - const lines = [['level', 30], ['req', { url: '/foo' }], ['level', 30], ['res', { statusCode: 200 }]] - t.plan(lines.length + 2) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - const check = lines.shift() - const key = check[0] - const value = check[1] - - t.same(line[key], value) - }) - - const logger = require('pino')({ - level: 'info', - serializers: { - req: function (req) { - return { - url: req.url - } - } - } - }, splitStream) - - const localFastify = Fastify({ - logger - }) - - localFastify.get('/foo', function (req, reply) { - t.ok(req.log) - req.log.info('log success') - reply.send({ hello: 'world' }) - }) - - localFastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - http.get(`http://${localhostForURL}:` + localFastify.server.address().port + '/foo', (res) => { - res.resume() - res.on('end', () => { - localFastify.server.close() - }) - }) - }) -}) - -test('should throw in case the external logger provided does not have a child method', t => { - t.plan(1) - const loggerInstance = { - info: console.info, - error: console.error, - debug: console.debug, - fatal: console.error, - warn: console.warn, - trace: console.trace - } - - t.throws( - () => Fastify({ logger: loggerInstance }), - FST_ERR_LOG_INVALID_LOGGER, - "Invalid logger object provided. The logger instance should have these functions(s): 'child'." - ) -}) - -test('should throw in case a partially matching logger is provided', t => { - t.plan(1) - - t.throws( - () => Fastify({ logger: console }), - FST_ERR_LOG_INVALID_LOGGER, - "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'." - ) -}) - -test('expose the logger', t => { - t.plan(2) - let fastify = null - const stream = split(JSON.parse) - try { - fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - } catch (e) { - t.fail() - } - - t.ok(fastify.log) - t.same(typeof fastify.log, 'object') -}) - -test('The request id header key can be customized', t => { - t.plan(9) - const REQUEST_ID = '42' - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: 'my-custom-request-id' - }) - t.teardown(() => fastify.close()) - - fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'my-custom-request-id': REQUEST_ID - } - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, REQUEST_ID) - - stream.once('data', line => { - t.equal(line.reqId, REQUEST_ID) - t.equal(line.msg, 'incoming request', 'message is set') - - stream.once('data', line => { - t.equal(line.reqId, REQUEST_ID) - t.equal(line.msg, 'some log message', 'message is set') - - stream.once('data', line => { - t.equal(line.reqId, REQUEST_ID) - t.equal(line.msg, 'request completed', 'message is set') - }) - }) - }) - }) -}) - -test('The request id header key can be ignored', t => { - t.plan(9) - const REQUEST_ID = 'ignore-me' - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: false - }) - t.teardown(() => fastify.close()) - - fastify.get('/', (req, reply) => { - t.equal(req.id, 'req-1') - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'request-id': REQUEST_ID - } - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, 'req-1') - - stream.once('data', line => { - t.equal(line.reqId, 'req-1') - t.equal(line.msg, 'incoming request', 'message is set') - - stream.once('data', line => { - t.equal(line.reqId, 'req-1') - t.equal(line.msg, 'some log message', 'message is set') - - stream.once('data', line => { - t.equal(line.reqId, 'req-1') - t.equal(line.msg, 'request completed', 'message is set') - }) - }) - }) - }) -}) - -test('The request id header key can be customized along with a custom id generator', t => { - t.plan(12) - const REQUEST_ID = '42' - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: 'my-custom-request-id', - genReqId (req) { - return 'foo' - } - }) - t.teardown(() => fastify.close()) - - fastify.get('/one', (req, reply) => { - t.equal(req.id, REQUEST_ID) - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - fastify.get('/two', (req, reply) => { - t.equal(req.id, 'foo') - req.log.info('some log message 2') - reply.send({ id: req.id }) - }) - - const matches = [ - { reqId: REQUEST_ID, msg: /incoming request/ }, - { reqId: REQUEST_ID, msg: /some log message/ }, - { reqId: REQUEST_ID, msg: /request completed/ }, - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message 2/ }, - { reqId: 'foo', msg: /request completed/ } - ] - - let i = 0 - stream.on('data', line => { - t.match(line, matches[i]) - i += 1 - }) - - fastify.inject({ - method: 'GET', - url: '/one', - headers: { - 'my-custom-request-id': REQUEST_ID - } - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, REQUEST_ID) - }) - - fastify.inject({ - method: 'GET', - url: '/two' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - }) -}) - -test('The request id header key can be ignored along with a custom id generator', t => { - t.plan(12) - const REQUEST_ID = 'ignore-me' - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: false, - genReqId (req) { - return 'foo' - } - }) - t.teardown(() => fastify.close()) - - fastify.get('/one', (req, reply) => { - t.equal(req.id, 'foo') - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - fastify.get('/two', (req, reply) => { - t.equal(req.id, 'foo') - req.log.info('some log message 2') - reply.send({ id: req.id }) - }) - - const matches = [ - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message/ }, - { reqId: 'foo', msg: /request completed/ }, - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message 2/ }, - { reqId: 'foo', msg: /request completed/ } - ] - - let i = 0 - stream.on('data', line => { - t.match(line, matches[i]) - i += 1 - }) - - fastify.inject({ - method: 'GET', - url: '/one', - headers: { - 'request-id': REQUEST_ID - } - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - }) - - fastify.inject({ - method: 'GET', - url: '/two' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - }) -}) - -test('The request id log label can be changed', t => { - t.plan(6) - const REQUEST_ID = '42' - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: 'my-custom-request-id', - requestIdLogLabel: 'traceId' - }) - t.teardown(() => fastify.close()) - - fastify.get('/one', (req, reply) => { - t.equal(req.id, REQUEST_ID) - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - const matches = [ - { traceId: REQUEST_ID, msg: /incoming request/ }, - { traceId: REQUEST_ID, msg: /some log message/ }, - { traceId: REQUEST_ID, msg: /request completed/ } - ] - - let i = 0 - stream.on('data', line => { - t.match(line, matches[i]) - i += 1 - }) - - fastify.inject({ - method: 'GET', - url: '/one', - headers: { - 'my-custom-request-id': REQUEST_ID - } - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, REQUEST_ID) - }) -}) - -test('The logger should accept custom serializer', t => { - t.plan(9) - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info', - serializers: { - req: function (req) { - return { - url: req.url - } - } - } - } - }) - - fastify.get('/custom', function (req, reply) { - t.ok(req.log) - reply.send(new Error('kaboom')) - }) - - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port + '/custom') - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - - stream.once('data', line => { - t.ok(line.req, 'req is defined') - t.equal(line.msg, 'incoming request', 'message is set') - t.same(line.req, { url: '/custom' }, 'custom req serializer is use') - - stream.once('data', line => { - t.ok(line.res, 'res is defined') - t.equal(line.msg, 'kaboom', 'message is set') - t.same(line.res, { statusCode: 500 }, 'default res serializer is use') - }) - }) - }) - }) -}) - -test('reply.send logs an error if called twice in a row', t => { - const lines = ['incoming request', 'request completed', 'Reply already sent', 'Reply already sent'] - t.plan(lines.length + 2) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - t.same(line.msg, lines.shift()) - }) - - const logger = pino(splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.get('/', (req, reply) => { - reply.send({ hello: 'world' }) - reply.send({ hello: 'world2' }) - reply.send({ hello: 'world3' }) - }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('logger can be silented', t => { - t.plan(17) - const fastify = Fastify({ - logger: false - }) - t.ok(fastify.log) - t.same(typeof fastify.log, 'object') - t.same(typeof fastify.log.fatal, 'function') - t.same(typeof fastify.log.error, 'function') - t.same(typeof fastify.log.warn, 'function') - t.same(typeof fastify.log.info, 'function') - t.same(typeof fastify.log.debug, 'function') - t.same(typeof fastify.log.trace, 'function') - t.same(typeof fastify.log.child, 'function') - - const childLog = fastify.log.child() - - t.same(typeof childLog, 'object') - t.same(typeof childLog.fatal, 'function') - t.same(typeof childLog.error, 'function') - t.same(typeof childLog.warn, 'function') - t.same(typeof childLog.info, 'function') - t.same(typeof childLog.debug, 'function') - t.same(typeof childLog.trace, 'function') - t.same(typeof childLog.child, 'function') -}) - -test('Should set a custom logLevel for a plugin', t => { - const lines = ['incoming request', 'Hello', 'request completed'] - t.plan(7) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - t.same(line.msg, lines.shift()) - }) - - const logger = pino({ level: 'error' }, splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.get('/', (req, reply) => { - req.log.info('Hello') // we should not see this log - reply.send({ hello: 'world' }) - }) - - fastify.register(function (instance, opts, done) { - instance.get('/plugin', (req, reply) => { - req.log.info('Hello') // we should see this log - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'info' }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) - - fastify.inject({ - method: 'GET', - url: '/plugin' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should set a custom logSerializers for a plugin', t => { - t.plan(3) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - if (line.test) { - t.same(line.test, 'XHello') - } - }) - - const logger = pino({ level: 'error' }, splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.register(function (instance, opts, done) { - instance.get('/plugin', (req, reply) => { - req.log.info({ test: 'Hello' }) // we should see this log - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'info', logSerializers: { test: value => 'X' + value } }) - - fastify.inject({ - method: 'GET', - url: '/plugin' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should set a custom logLevel for every plugin', t => { - const lines = ['incoming request', 'request completed', 'info', 'debug'] - t.plan(18) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - t.ok(line.level === 30 || line.level === 20) - t.ok(lines.indexOf(line.msg) > -1) - }) - - const logger = pino({ level: 'error' }, splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.get('/', (req, reply) => { - req.log.warn('Hello') // we should not see this log - reply.send({ hello: 'world' }) - }) - - fastify.register(function (instance, opts, done) { - instance.get('/info', (req, reply) => { - req.log.info('info') // we should see this log - req.log.debug('hidden log') - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'info' }) - - fastify.register(function (instance, opts, done) { - instance.get('/debug', (req, reply) => { - req.log.debug('debug') // we should see this log - req.log.trace('hidden log') - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'debug' }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) - - fastify.inject({ - method: 'GET', - url: '/info' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) - - fastify.inject({ - method: 'GET', - url: '/debug' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should set a custom logSerializers for every plugin', async t => { - const lines = ['Hello', 'XHello', 'ZHello'] - t.plan(6) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - if (line.test) { - t.same(line.test, lines.shift()) - } - }) - - const logger = pino({ level: 'info' }, splitStream) - const fastify = Fastify({ - logger - }) - - fastify.get('/', (req, reply) => { - req.log.warn({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - - fastify.register(function (instance, opts, done) { - instance.get('/test1', (req, reply) => { - req.log.info({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - done() - }, { logSerializers: { test: value => 'X' + value } }) - - fastify.register(function (instance, opts, done) { - instance.get('/test2', (req, reply) => { - req.log.info({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - done() - }, { logSerializers: { test: value => 'Z' + value } }) - - let res = await fastify.inject({ - method: 'GET', - url: '/' - }) - t.same(res.json(), { hello: 'world' }) - - res = await fastify.inject({ - method: 'GET', - url: '/test1' - }) - t.same(res.json(), { hello: 'world' }) - - res = await fastify.inject({ - method: 'GET', - url: '/test2' - }) - t.same(res.json(), { hello: 'world' }) -}) - -test('Should override serializers from route', t => { - t.plan(3) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - if (line.test) { - t.same(line.test, 'ZHello') - } - }) - - const logger = pino({ level: 'info' }, splitStream) - const fastify = Fastify({ - logger - }) - - fastify.register(function (instance, opts, done) { - instance.get('/', { - logSerializers: { - test: value => 'Z' + value // should override - } - }, (req, reply) => { - req.log.info({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - done() - }, { logSerializers: { test: value => 'X' + value } }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should override serializers from plugin', t => { - t.plan(3) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - if (line.test) { - t.same(line.test, 'ZHello') - } - }) - - const logger = pino({ level: 'info' }, splitStream) - const fastify = Fastify({ - logger - }) - - fastify.register(function (instance, opts, done) { - instance.register(context1, { - logSerializers: { - test: value => 'Z' + value // should override - } - }) - done() - }, { logSerializers: { test: value => 'X' + value } }) - - function context1 (instance, opts, done) { - instance.get('/', (req, reply) => { - req.log.info({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - done() - } - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should use serializers from plugin and route', t => { - t.plan(4) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - if (line.test) { - t.same(line.test, 'XHello') - } - if (line.test2) { - t.same(line.test2, 'ZHello') - } - }) - - const logger = pino({ level: 'info' }, splitStream) - const fastify = Fastify({ - logger - }) - - fastify.register(context1, { - logSerializers: { test: value => 'X' + value } - }) - - function context1 (instance, opts, done) { - instance.get('/', { - logSerializers: { - test2: value => 'Z' + value - } - }, (req, reply) => { - req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } - reply.send({ hello: 'world' }) - }) - done() - } - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should use serializers from instance fastify and route', t => { - t.plan(4) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - if (line.test) { - t.same(line.test, 'XHello') - } - if (line.test2) { - t.same(line.test2, 'ZHello') - } - }) - - const logger = pino({ - level: 'info', - serializers: { - test: value => 'X' + value, - test2: value => 'This should be override - ' + value - } - }, splitStream) - const fastify = Fastify({ - logger - }) - - fastify.get('/', { - logSerializers: { - test2: value => 'Z' + value - } - }, (req, reply) => { - req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } - reply.send({ hello: 'world' }) - }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should use serializers inherit from contexts', t => { - t.plan(5) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - if (line.test && line.test2 && line.test3) { - t.same(line.test, 'XHello') - t.same(line.test2, 'YHello') - t.same(line.test3, 'ZHello') - } - }) - - const logger = pino({ - level: 'info', - serializers: { - test: value => 'X' + value - } - }, splitStream) - - const fastify = Fastify({ logger }) - fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } }) - - function context1 (instance, opts, done) { - instance.get('/', { - logSerializers: { - test3: value => 'Z' + value - } - }, (req, reply) => { - req.log.info({ test: 'Hello', test2: 'Hello', test3: 'Hello' }) // { test: 'XHello', test2: 'YHello', test3: 'ZHello' } - reply.send({ hello: 'world' }) - }) - done() - } - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should increase the log level for a specific plugin', t => { - t.plan(4) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - t.same(line.msg, 'Hello') - t.ok(line.level === 50) - }) - - const logger = pino({ level: 'info' }, splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.register(function (instance, opts, done) { - instance.get('/', (req, reply) => { - req.log.error('Hello') // we should see this log - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'error' }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('Should set the log level for the customized 404 handler', t => { - t.plan(4) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - t.same(line.msg, 'Hello') - t.ok(line.level === 50) - }) - - const logger = pino({ level: 'warn' }, splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.register(function (instance, opts, done) { - instance.setNotFoundHandler(function (req, reply) { - req.log.error('Hello') - reply.code(404).send() - }) - done() - }, { logLevel: 'error' }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) -}) - -test('Should set the log level for the customized 500 handler', t => { - t.plan(4) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - t.same(line.msg, 'Hello') - t.ok(line.level === 60) - }) - - const logger = pino({ level: 'warn' }, splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.register(function (instance, opts, done) { - instance.get('/', (req, reply) => { - req.log.error('kaboom') - reply.send(new Error('kaboom')) - }) - - instance.setErrorHandler(function (e, request, reply) { - reply.log.fatal('Hello') - reply.code(500).send() - }) - done() - }, { logLevel: 'fatal' }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - }) -}) - -test('Should set a custom log level for a specific route', t => { - const lines = ['incoming request', 'Hello', 'request completed'] - t.plan(7) - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - t.same(line.msg, lines.shift()) - }) - - const logger = pino({ level: 'error' }, splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.get('/log', { logLevel: 'info' }, (req, reply) => { - req.log.info('Hello') - reply.send({ hello: 'world' }) - }) - - fastify.get('/no-log', (req, reply) => { - req.log.info('Hello') - reply.send({ hello: 'world' }) - }) - - fastify.inject({ - method: 'GET', - url: '/log' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) - - fastify.inject({ - method: 'GET', - url: '/no-log' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) -}) - -test('The default 404 handler logs the incoming request', t => { - t.plan(5) - - const expectedMessages = [ - 'incoming request', - 'Route GET:/not-found not found', - 'request completed' - ] - - const splitStream = split(JSON.parse) - splitStream.on('data', (line) => { - t.same(line.msg, expectedMessages.shift()) - }) - - const logger = pino({ level: 'trace' }, splitStream) - - const fastify = Fastify({ - logger - }) - - fastify.inject({ - method: 'GET', - url: '/not-found' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) -}) - -test('should serialize request and response', t => { - t.plan(3) - const lines = [] - const dest = new stream.Writable({ - write: function (chunk, enc, cb) { - lines.push(JSON.parse(chunk)) - cb() - } - }) - const fastify = Fastify({ logger: { level: 'info', stream: dest } }) - - fastify.get('/500', (req, reply) => { - reply.code(500).send(Error('500 error')) - }) - - fastify.inject({ - url: '/500', - method: 'GET' - }, (e, res) => { - const l = lines.find((line) => line.res && line.res.statusCode === 500) - t.ok(l.req) - t.same(l.req.method, 'GET') - t.same(l.req.url, '/500') - }) -}) - -{ - const interfaces = os.networkInterfaces() - const ipv6 = Object.keys(interfaces) - .filter(name => name.substr(0, 2) === 'lo') - .map(name => interfaces[name]) - .reduce((list, set) => list.concat(set), []) - .filter(info => info.family === 'IPv6') - .map(info => info.address) - .shift() - - if (ipv6 !== undefined) { - test('Wrap IPv6 address in listening log message', t => { - t.plan(2) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - fastify.listen({ port: 0, host: ipv6 }, err => { - t.error(err) - stream.once('data', line => { - const expected = 'Server listening at http://[' + ipv6 + ']:' + - fastify.server.address().port - t.same(line.msg, expected) - fastify.close() - }) - }) - }) - } -} - -test('Do not wrap IPv4 address', t => { - t.plan(2) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - fastify.listen({ port: 0, host: '127.0.0.1' }, err => { - t.error(err) - stream.once('data', line => { - const expected = 'Server listening at http://127.0.0.1:' + - fastify.server.address().port - t.same(line.msg, expected) - fastify.close() - }) - }) -}) - -test('file option', t => { - t.plan(13) - let fastify = null - const dest = file() - - fastify = Fastify({ - logger: { - file: dest - } - }) - - fastify.get('/', function (req, reply) { - t.ok(req.log) - reply.send({ hello: 'world' }) - }) - - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port, () => { - const stream = fs.createReadStream(dest).pipe(split(JSON.parse)) - - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - - stream.once('data', line => { - const id = line.reqId - t.ok(line.reqId, 'reqId is defined') - t.ok(line.req, 'req is defined') - t.equal(line.msg, 'incoming request', 'message is set') - t.equal(line.req.method, 'GET', 'method is get') - - stream.once('data', line => { - t.equal(line.reqId, id) - t.ok(line.reqId, 'reqId is defined') - t.ok(line.res, 'res is defined') - t.equal(line.msg, 'request completed', 'message is set') - t.equal(line.res.statusCode, 200, 'statusCode is 200') - t.ok(line.responseTime, 'responseTime is defined') - stream.resume() - }) - }) - }) - }) - }) -}) - -test('should log the error if no error handler is defined', t => { - t.plan(8) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - fastify.get('/error', function (req, reply) { - t.ok(req.log) - reply.send(new Error('a generic error')) - }) - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port + '/error') - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - stream.once('data', line => { - t.equal(line.msg, 'incoming request', 'message is set') - stream.once('data', line => { - t.equal(line.level, 50, 'level is correct') - t.equal(line.msg, 'a generic error', 'message is set') - stream.once('data', line => { - t.equal(line.msg, 'request completed', 'message is set') - t.same(line.res, { statusCode: 500 }, 'status code is set') - }) - }) - }) - }) - }) -}) - -test('should log as info if error status code >= 400 and < 500 if no error handler is defined', t => { - t.plan(8) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - fastify.get('/400', function (req, reply) { - t.ok(req.log) - reply.send(Object.assign(new Error('a 400 error'), { statusCode: 400 })) - }) - fastify.get('/503', function (req, reply) { - t.ok(req.log) - reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) - }) - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port + '/400') - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - stream.once('data', line => { - t.equal(line.msg, 'incoming request', 'message is set') - stream.once('data', line => { - t.equal(line.level, 30, 'level is correct') - t.equal(line.msg, 'a 400 error', 'message is set') - stream.once('data', line => { - t.equal(line.msg, 'request completed', 'message is set') - t.same(line.res, { statusCode: 400 }, 'status code is set') - }) - }) - }) - }) - }) -}) - -test('should log as error if error status code >= 500 if no error handler is defined', t => { - t.plan(8) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - fastify.get('/503', function (req, reply) { - t.ok(req.log) - reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) - }) - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port + '/503') - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - stream.once('data', line => { - t.equal(line.msg, 'incoming request', 'message is set') - stream.once('data', line => { - t.equal(line.level, 50, 'level is correct') - t.equal(line.msg, 'a 503 error', 'message is set') - stream.once('data', line => { - t.equal(line.msg, 'request completed', 'message is set') - t.same(line.res, { statusCode: 503 }, 'status code is set') - }) - }) - }) - }) - }) -}) - -test('should not log the error if error handler is defined and it does not error', t => { - t.plan(8) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - fastify.get('/error', function (req, reply) { - t.ok(req.log) - reply.send(new Error('something happened')) - }) - fastify.setErrorHandler((err, req, reply) => { - t.ok(err) - reply.send('something bad happened') - }) - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port + '/error') - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - stream.once('data', line => { - t.equal(line.msg, 'incoming request', 'message is set') - stream.once('data', line => { - t.equal(line.level, 30, 'level is correct') - t.equal(line.msg, 'request completed', 'message is set') - t.same(line.res, { statusCode: 200 }, 'status code is set') - }) - }) - }) - }) -}) - -test('should not rely on raw request to log errors', t => { - t.plan(7) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - fastify.get('/error', function (req, reply) { - t.ok(req.log) - reply.status(415).send(new Error('something happened')) - }) - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - http.get(`http://${localhostForURL}:` + fastify.server.address().port + '/error') - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - stream.once('data', line => { - t.equal(line.msg, 'incoming request', 'message is set') - stream.once('data', line => { - t.equal(line.level, 30, 'level is correct') - t.equal(line.msg, 'something happened', 'message is set') - t.same(line.res, { statusCode: 415 }, 'status code is set') - }) - }) - }) - }) -}) - -test('should redact the authorization header if so specified', t => { - t.plan(7) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - redact: ['req.headers.authorization'], - level: 'info', - serializers: { - req (req) { - return { - method: req.method, - url: req.url, - headers: req.headers, - hostname: req.hostname, - remoteAddress: req.ip, - remotePort: req.socket.remotePort - } - } - } - } - }) - fastify.get('/', function (req, reply) { - t.same(req.headers.authorization, 'Bearer abcde') - reply.send({ hello: 'world' }) - }) - stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') - stream.once('data', line => { - t.equal(line.req.headers.authorization, '[Redacted]', 'authorization is redacted') - }) - }) - fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'GET', - url: `http://${localhostForURL}:` + fastify.server.address().port, - headers: { - authorization: 'Bearer abcde' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - }) -}) - -test('should not log incoming request and outgoing response when disabled', t => { - t.plan(3) - const lines = [] - const dest = new stream.Writable({ - write: function (chunk, enc, cb) { - lines.push(JSON.parse(chunk)) - cb() - } - }) - const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream: dest } }) - - fastify.get('/500', (req, reply) => { - reply.code(500).send(Error('500 error')) - }) - - fastify.inject({ - url: '/500', - method: 'GET' - }, (e, res) => { - t.same(lines.length, 1) - t.ok(lines[0].msg) - t.same(lines[0].msg, '500 error') - }) -}) - -test('should not log incoming request and outgoing response for 404 onBadUrl when disabled', t => { - t.plan(1) - const lines = [] - const dest = new stream.Writable({ - write: function (chunk, enc, cb) { - lines.push(JSON.parse(chunk)) - cb() - } - }) - const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream: dest } }) - - fastify.inject({ - url: '/%c0', - method: 'GET' - }, (e, res) => { - // it will log 1 line only because of basic404 - t.same(lines.length, 1) - }) -}) - -test('should pass when using unWritable props in the logger option', t => { - t.plan(1) - Fastify({ - logger: Object.defineProperty({}, 'level', { value: 'info' }) - }) - t.pass() -}) - -test('should be able to use a custom logger', t => { - t.plan(1) - - const logger = { - fatal: () => {}, - error: () => {}, - warn: () => {}, - info: () => {}, - debug: () => {}, - trace: () => {}, - child: () => {} - } - - Fastify({ logger }) - - t.pass() -}) - -test('should create a default logger if provided one is invalid', t => { - t.plan(1) - - const logger = new Date() - - Fastify({ logger }) - - t.pass() -}) - -test('should not throw error when serializing custom req', t => { - t.plan(1) - - const lines = [] - const dest = new stream.Writable({ - write: function (chunk, enc, cb) { - lines.push(JSON.parse(chunk)) - cb() - } - }) - const fastify = Fastify({ logger: { level: 'info', stream: dest } }) - fastify.log.info({ req: {} }) - - t.same(lines[0].req, {}) -}) diff --git a/test/route.test.js b/test/route.test.js index ccb5570b228..2e44fb511d0 100644 --- a/test/route.test.js +++ b/test/route.test.js @@ -1494,7 +1494,7 @@ test('exposeHeadRoute should not reuse the same route option', async t => { }) }) -test('using fastify.all when a catchall is defined does not degrade performance', { timeout: 5000 }, async t => { +test('using fastify.all when a catchall is defined does not degrade performance', { timeout: 30000 }, async t => { t.plan(1) const fastify = Fastify() diff --git a/test/serial/logger.0.test.js b/test/serial/logger.0.test.js new file mode 100644 index 00000000000..3c0b6769b2a --- /dev/null +++ b/test/serial/logger.0.test.js @@ -0,0 +1,861 @@ +'use strict' + +const http = require('http') +const stream = require('stream') + +const t = require('tap') +const split = require('split2') +const pino = require('pino') + +const Fastify = require('../../fastify') +const helper = require('../helper') +const { FST_ERR_LOG_INVALID_LOGGER } = require('../../lib/errors') +const { on } = stream + +function createDeferredPromise () { + const promise = {} + promise.promise = new Promise(function (resolve) { + promise.resolve = resolve + }) + return promise +} + +function request (url, cleanup = () => { }) { + const promise = createDeferredPromise() + http.get(url, (res) => { + const chunks = [] + // we consume the response + res.on('data', function (chunk) { + chunks.push(chunk) + }) + res.once('end', function () { + cleanup(res, Buffer.concat(chunks).toString()) + promise.resolve() + }) + }) + return promise.promise +} + +t.test('test log stream', (t) => { + t.setTimeout(60000) + + let localhost + let localhostForURL + + t.plan(22) + + t.before(async function () { + [localhost, localhostForURL] = await helper.getLoopbackHost() + }) + + t.test('defaults to info level', async (t) => { + const lines = [ + { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, + { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length * 2 + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (req, reply) { + t.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0 }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + let id + for await (const [line] of on(stream, 'data')) { + // we skip the non-request log + if (typeof line.reqId !== 'string') continue + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.equal(line.reqId, id) + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('test log stream', async (t) => { + const lines = [ + { msg: /^Server listening at / }, + { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, + { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (req, reply) { + t.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + let id + for await (const [line] of on(stream, 'data')) { + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.equal(line.reqId, id) + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('test error log stream', async (t) => { + const lines = [ + { msg: /^Server listening at / }, + { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, + { reqId: /req-/, res: { statusCode: 500 }, msg: 'kaboom' }, + { reqId: /req-/, res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.send(new Error('kaboom')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + let id + for await (const [line] of on(stream, 'data')) { + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.equal(line.reqId, id) + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('can use external logger instance', async (t) => { + const lines = [/^Server listening at /, /^incoming request$/, /^log success$/, /^request completed$/] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = require('pino')(stream) + + const fastify = Fastify({ logger }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/foo', function (req, reply) { + t.ok(req.log) + req.log.info('log success') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo') + + for await (const [line] of on(stream, 'data')) { + const regex = lines.shift() + t.ok(regex.test(line.msg), '"' + line.msg + '" dont match "' + regex + '"') + if (lines.length === 0) break + } + }) + + t.test('can use external logger instance with custom serializer', async (t) => { + const lines = [['level', 30], ['req', { url: '/foo' }], ['level', 30], ['res', { statusCode: 200 }]] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const logger = require('pino')({ + level: 'info', + serializers: { + req: function (req) { + return { + url: req.url + } + } + } + }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/foo', function (req, reply) { + t.ok(req.log) + req.log.info('log success') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo') + + for await (const [line] of on(stream, 'data')) { + const check = lines.shift() + const key = check[0] + const value = check[1] + t.same(line[key], value) + if (lines.length === 0) break + } + }) + + t.test('should throw in case the external logger provided does not have a child method', async (t) => { + t.plan(1) + const loggerInstance = { + info: console.info, + error: console.error, + debug: console.debug, + fatal: console.error, + warn: console.warn, + trace: console.trace + } + + try { + const fastify = Fastify({ logger: loggerInstance }) + await fastify.ready() + } catch (err) { + t.equal( + err instanceof FST_ERR_LOG_INVALID_LOGGER, + true, + "Invalid logger object provided. The logger instance should have these functions(s): 'child'." + ) + } + }) + + t.test('should throw in case a partially matching logger is provided', async (t) => { + t.plan(1) + + try { + const fastify = Fastify({ logger: console }) + await fastify.ready() + } catch (err) { + t.equal( + err instanceof FST_ERR_LOG_INVALID_LOGGER, + true, + "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'." + ) + } + }) + + t.test('expose the logger', async (t) => { + t.plan(2) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + + t.ok(fastify.log) + t.same(typeof fastify.log, 'object') + }) + + t.test('The request id header key can be customized', async (t) => { + const lines = ['incoming request', 'some log message', 'request completed'] + t.plan(lines.length * 2 + 2) + const REQUEST_ID = '42' + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id' + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + t.equal(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, REQUEST_ID) + + for await (const [line] of on(stream, 'data')) { + t.equal(line.reqId, REQUEST_ID) + t.equal(line.msg, lines.shift(), 'message is set') + if (lines.length === 0) break + } + }) + + t.test('The request id header key can be ignored', async (t) => { + const lines = ['incoming request', 'some log message', 'request completed'] + t.plan(lines.length * 2 + 2) + const REQUEST_ID = 'ignore-me' + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: false + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + t.equal(req.id, 'req-1') + req.log.info('some log message') + reply.send({ id: req.id }) + }) + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, 'req-1') + + for await (const [line] of on(stream, 'data')) { + t.equal(line.reqId, 'req-1') + t.equal(line.msg, lines.shift(), 'message is set') + if (lines.length === 0) break + } + }) + + t.test('The request id header key can be customized along with a custom id generator', async (t) => { + const REQUEST_ID = '42' + const matches = [ + { reqId: REQUEST_ID, msg: /incoming request/ }, + { reqId: REQUEST_ID, msg: /some log message/ }, + { reqId: REQUEST_ID, msg: /request completed/ }, + { reqId: 'foo', msg: /incoming request/ }, + { reqId: 'foo', msg: /some log message 2/ }, + { reqId: 'foo', msg: /request completed/ } + ] + t.plan(matches.length + 4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id', + genReqId (req) { + return 'foo' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/one', (req, reply) => { + t.equal(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + fastify.get('/two', (req, reply) => { + t.equal(req.id, 'foo') + req.log.info('some log message 2') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, REQUEST_ID) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/two' }) + const body = await response.json() + t.equal(body.id, 'foo') + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, matches.shift()) + if (matches.length === 0) break + } + }) + + t.test('The request id header key can be ignored along with a custom id generator', async (t) => { + const REQUEST_ID = 'ignore-me' + const matches = [ + { reqId: 'foo', msg: /incoming request/ }, + { reqId: 'foo', msg: /some log message/ }, + { reqId: 'foo', msg: /request completed/ }, + { reqId: 'foo', msg: /incoming request/ }, + { reqId: 'foo', msg: /some log message 2/ }, + { reqId: 'foo', msg: /request completed/ } + ] + t.plan(matches.length + 4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: false, + genReqId (req) { + return 'foo' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/one', (req, reply) => { + t.equal(req.id, 'foo') + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + fastify.get('/two', (req, reply) => { + t.equal(req.id, 'foo') + req.log.info('some log message 2') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, 'foo') + } + + { + const response = await fastify.inject({ method: 'GET', url: '/two' }) + const body = await response.json() + t.equal(body.id, 'foo') + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, matches.shift()) + if (matches.length === 0) break + } + }) + + t.test('The request id log label can be changed', async (t) => { + const REQUEST_ID = '42' + const matches = [ + { traceId: REQUEST_ID, msg: /incoming request/ }, + { traceId: REQUEST_ID, msg: /some log message/ }, + { traceId: REQUEST_ID, msg: /request completed/ } + ] + t.plan(matches.length + 2) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id', + requestIdLogLabel: 'traceId' + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/one', (req, reply) => { + t.equal(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, REQUEST_ID) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, matches.shift()) + if (matches.length === 0) break + } + }) + + t.test('The logger should accept custom serializer', async (t) => { + const lines = [ + { msg: /^Server listening at / }, + { req: { url: '/custom' }, msg: 'incoming request' }, + { res: { statusCode: 500 }, msg: 'kaboom' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info', + serializers: { + req: function (req) { + return { + url: req.url + } + } + } + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/custom', function (req, reply) { + t.ok(req.log) + reply.send(new Error('kaboom')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/custom') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('reply.send logs an error if called twice in a row', async (t) => { + const lines = ['incoming request', 'request completed', 'Reply already sent', 'Reply already sent'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const logger = pino(stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + reply.send({ hello: 'world2' }) + reply.send({ hello: 'world3' }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + + for await (const [line] of on(stream, 'data')) { + t.same(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('logger can be silented', (t) => { + t.plan(17) + const fastify = Fastify({ + logger: false + }) + t.teardown(fastify.close.bind(fastify)) + t.ok(fastify.log) + t.equal(typeof fastify.log, 'object') + t.equal(typeof fastify.log.fatal, 'function') + t.equal(typeof fastify.log.error, 'function') + t.equal(typeof fastify.log.warn, 'function') + t.equal(typeof fastify.log.info, 'function') + t.equal(typeof fastify.log.debug, 'function') + t.equal(typeof fastify.log.trace, 'function') + t.equal(typeof fastify.log.child, 'function') + + const childLog = fastify.log.child() + + t.equal(typeof childLog, 'object') + t.equal(typeof childLog.fatal, 'function') + t.equal(typeof childLog.error, 'function') + t.equal(typeof childLog.warn, 'function') + t.equal(typeof childLog.info, 'function') + t.equal(typeof childLog.debug, 'function') + t.equal(typeof childLog.trace, 'function') + t.equal(typeof childLog.child, 'function') + }) + + t.test('Should set a custom logLevel for a plugin', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed'] + t.plan(lines.length + 2) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + req.log.info('Not Exist') // we should not see this log + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/plugin', (req, reply) => { + req.log.info('Hello') // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/plugin' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.same(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set a custom logSerializers for a plugin', async (t) => { + const lines = ['incoming request', 'XHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.get('/plugin', (req, reply) => { + req.log.info({ test: 'Hello' }) // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info', logSerializers: { test: value => 'X' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/plugin' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + // either test or msg + t.equal(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set a custom logLevel for every plugin', async (t) => { + const lines = ['incoming request', 'info', 'request completed', 'incoming request', 'debug', 'request completed'] + t.plan(lines.length * 2 + 3) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + req.log.warn('Hello') // we should not see this log + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/info', (req, reply) => { + req.log.info('info') // we should see this log + req.log.debug('hidden log') + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info' }) + + fastify.register(function (instance, opts, done) { + instance.get('/debug', (req, reply) => { + req.log.debug('debug') // we should see this log + req.log.trace('hidden log') + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'debug' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/info' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/debug' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.ok(line.level === 30 || line.level === 20) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set a custom logSerializers for every plugin', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed', 'incoming request', 'XHello', 'request completed', 'incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 3) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + req.log.warn({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/test1', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + fastify.register(function (instance, opts, done) { + instance.get('/test2', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'Z' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/test1' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/test2' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should override serializers from route', async (t) => { + const lines = ['incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.get('/', { + logSerializers: { + test: value => 'Z' + value // should override + } + }, (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should override serializers from plugin', async (t) => { + const lines = ['incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.register(context1, { + logSerializers: { + test: value => 'Z' + value // should override + } + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + function context1 (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) +}) diff --git a/test/serial/logger.1.test.js b/test/serial/logger.1.test.js new file mode 100644 index 00000000000..206de71fb86 --- /dev/null +++ b/test/serial/logger.1.test.js @@ -0,0 +1,862 @@ +'use strict' + +const http = require('http') +const stream = require('stream') +const os = require('os') +const fs = require('fs') + +const t = require('tap') +const split = require('split2') +const pino = require('pino') +const path = require('path') +const { streamSym } = require('pino/lib/symbols') + +const Fastify = require('../../fastify') +const helper = require('../helper') +const { once, on } = stream + +function createDeferredPromise () { + const promise = {} + promise.promise = new Promise(function (resolve) { + promise.resolve = resolve + }) + return promise +} + +let count = 0 +function createTempFile () { + const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${process.hrtime().toString()}-${count++}`) + function cleanup () { + try { + fs.unlinkSync(file) + } catch { } + } + return { file, cleanup } +} + +function request (url, cleanup = () => { }) { + const promise = createDeferredPromise() + http.get(url, (res) => { + const chunks = [] + // we consume the response + res.on('data', function (chunk) { + chunks.push(chunk) + }) + res.once('end', function () { + cleanup(res, Buffer.concat(chunks).toString()) + promise.resolve() + }) + }) + return promise.promise +} + +t.test('test log stream', (t) => { + t.setTimeout(60000) + + let localhost + let localhostForURL + + t.plan(24) + + t.before(async function () { + [localhost, localhostForURL] = await helper.getLoopbackHost() + }) + + t.test('Should use serializers from plugin and route', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(context1, { + logSerializers: { test: value => 'X' + value } + }) + + function context1 (instance, opts, done) { + instance.get('/', { + logSerializers: { + test2: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should use serializers from instance fastify and route', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ + level: 'info', + serializers: { + test: value => 'X' + value, + test2: value => 'This should be override - ' + value + } + }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', { + logSerializers: { + test2: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should use serializers inherit from contexts', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'YHello', test3: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ + level: 'info', + serializers: { + test: value => 'X' + value + } + }, stream) + + const fastify = Fastify({ logger }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } }) + + function context1 (instance, opts, done) { + instance.get('/', { + logSerializers: { + test3: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello', test3: 'Hello' }) // { test: 'XHello', test2: 'YHello', test3: 'ZHello' } + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should increase the log level for a specific plugin', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.error('Hello') // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'error' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.level, 50) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set the log level for the customized 404 handler', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'warn' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.setNotFoundHandler(function (req, reply) { + req.log.error('Hello') + reply.code(404).send() + }) + done() + }, { logLevel: 'error' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + t.equal(response.statusCode, 404) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.level, 50) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set the log level for the customized 500 handler', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'warn' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.error('kaboom') + reply.send(new Error('kaboom')) + }) + + instance.setErrorHandler(function (e, request, reply) { + reply.log.fatal('Hello') + reply.code(500).send() + }) + done() + }, { logLevel: 'fatal' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + t.equal(response.statusCode, 500) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.level, 60) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set a custom log level for a specific route', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed'] + t.plan(lines.length + 2) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/log', { logLevel: 'info' }, (req, reply) => { + req.log.info('Hello') + reply.send({ hello: 'world' }) + }) + + fastify.get('/no-log', (req, reply) => { + req.log.info('Hello') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/log' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/no-log' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('The default 404 handler logs the incoming request', async (t) => { + const lines = ['incoming request', 'Route GET:/not-found not found', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'trace' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/not-found' }) + t.equal(response.statusCode, 404) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should serialize request and response', async (t) => { + const lines = [ + { req: { method: 'GET', url: '/500' }, msg: 'incoming request' }, + { req: { method: 'GET', url: '/500' }, msg: '500 error' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const fastify = Fastify({ logger: { level: 'info', stream } }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/500', (req, reply) => { + reply.code(500).send(Error('500 error')) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/500' }) + t.equal(response.statusCode, 500) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Wrap IPv6 address in listening log message', async (t) => { + t.plan(1) + + const interfaces = os.networkInterfaces() + const ipv6 = Object.keys(interfaces) + .filter(name => name.substr(0, 2) === 'lo') + .map(name => interfaces[name]) + .reduce((list, set) => list.concat(set), []) + .filter(info => info.family === 'IPv6') + .map(info => info.address) + .shift() + + if (ipv6 === undefined) { + t.pass('No IPv6 loopback interface') + } else { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + await fastify.listen({ port: 0, host: ipv6 }) + + { + const [line] = await once(stream, 'data') + t.same(line.msg, `Server listening at http://[${ipv6}]:${fastify.server.address().port}`) + } + } + }) + + t.test('Do not wrap IPv4 address', async (t) => { + t.plan(1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + await fastify.listen({ port: 0, host: '127.0.0.1' }) + + { + const [line] = await once(stream, 'data') + t.same(line.msg, `Server listening at http://127.0.0.1:${fastify.server.address().port}`) + } + }) + + t.test('file option', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { reqId: /req-/, req: { method: 'GET', url: '/' }, msg: 'incoming request' }, + { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) + const { file, cleanup } = createTempFile(t) + + const fastify = Fastify({ + logger: { file } + }) + t.teardown(() => { + // cleanup the file after sonic-boom closed + // otherwise we may face racing condition + fastify.log[streamSym].once('close', cleanup) + // we must flush the stream ourself + // otherwise buffer may whole sonic-boom + fastify.log[streamSym].flushSync() + // end after flushing to actually close file + fastify.log[streamSym].end() + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (req, reply) { + t.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + // we already own the full log + const stream = fs.createReadStream(file).pipe(split(JSON.parse)) + t.teardown(stream.resume.bind(stream)) + + let id + for await (const [line] of on(stream, 'data')) { + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.equal(line.reqId, id) + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should log the error if no error handler is defined', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { msg: 'incoming request' }, + { level: 50, msg: 'a generic error' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.send(new Error('a generic error')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should log as info if error status code >= 400 and < 500 if no error handler is defined', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { msg: 'incoming request' }, + { level: 30, msg: 'a 400 error' }, + { res: { statusCode: 400 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/400', function (req, reply) { + t.ok(req.log) + reply.send(Object.assign(new Error('a 400 error'), { statusCode: 400 })) + }) + fastify.get('/503', function (req, reply) { + t.ok(req.log) + reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/400') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should log as error if error status code >= 500 if no error handler is defined', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { msg: 'incoming request' }, + { level: 50, msg: 'a 503 error' }, + { res: { statusCode: 503 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + fastify.get('/503', function (req, reply) { + t.ok(req.log) + reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/503') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should not log the error if error handler is defined and it does not error', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { level: 30, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 2) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.send(new Error('something happened')) + }) + fastify.setErrorHandler((err, req, reply) => { + t.ok(err) + reply.send('something bad happened') + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should not rely on raw request to log errors', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { level: 30, msg: 'incoming request' }, + { res: { statusCode: 415 }, msg: 'something happened' }, + { res: { statusCode: 415 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.status(415).send(new Error('something happened')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should redact the authorization header if so specified', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + redact: ['req.headers.authorization'], + level: 'info', + serializers: { + req (req) { + return { + method: req.method, + url: req.url, + headers: req.headers, + hostname: req.hostname, + remoteAddress: req.ip, + remotePort: req.socket.remotePort + } + } + } + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (req, reply) { + t.same(req.headers.authorization, 'Bearer abcde') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request({ + method: 'GET', + path: '/', + host: localhost, + port: fastify.server.address().port, + headers: { + authorization: 'Bearer abcde' + } + }, function (response, body) { + t.equal(response.statusCode, 200) + t.same(body, JSON.stringify({ hello: 'world' })) + }) + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should not log incoming request and outgoing response when disabled', async (t) => { + t.plan(3) + const stream = split(JSON.parse) + const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/500', (req, reply) => { + reply.code(500).send(Error('500 error')) + }) + + await fastify.ready() + + await fastify.inject({ method: 'GET', url: '/500' }) + + { + const [line] = await once(stream, 'data') + t.ok(line.reqId, 'reqId is defined') + t.equal(line.msg, '500 error', 'message is set') + } + + // no more readable data + t.equal(stream.readableLength, 0) + }) + + t.test('should not log incoming request and outgoing response for 404 onBadUrl when disabled', async (t) => { + t.plan(3) + const stream = split(JSON.parse) + const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + + await fastify.inject({ method: 'GET', url: '/%c0' }) + + { + const [line] = await once(stream, 'data') + t.ok(line.reqId, 'reqId is defined') + t.equal(line.msg, 'Route GET:/%c0 not found', 'message is set') + } + + // no more readable data + t.equal(stream.readableLength, 0) + }) + + t.test('should pass when using unWritable props in the logger option', (t) => { + t.plan(8) + const fastify = Fastify({ + logger: Object.defineProperty({}, 'level', { value: 'info' }) + }) + t.teardown(fastify.close.bind(fastify)) + + t.equal(typeof fastify.log, 'object') + t.equal(typeof fastify.log.fatal, 'function') + t.equal(typeof fastify.log.error, 'function') + t.equal(typeof fastify.log.warn, 'function') + t.equal(typeof fastify.log.info, 'function') + t.equal(typeof fastify.log.debug, 'function') + t.equal(typeof fastify.log.trace, 'function') + t.equal(typeof fastify.log.child, 'function') + }) + + t.test('should be able to use a custom logger', (t) => { + t.plan(7) + + const logger = { + fatal: (msg) => { t.equal(msg, 'fatal') }, + error: (msg) => { t.equal(msg, 'error') }, + warn: (msg) => { t.equal(msg, 'warn') }, + info: (msg) => { t.equal(msg, 'info') }, + debug: (msg) => { t.equal(msg, 'debug') }, + trace: (msg) => { t.equal(msg, 'trace') }, + child: () => logger + } + + const fastify = Fastify({ logger }) + t.teardown(fastify.close.bind(fastify)) + + fastify.log.fatal('fatal') + fastify.log.error('error') + fastify.log.warn('warn') + fastify.log.info('info') + fastify.log.debug('debug') + fastify.log.trace('trace') + const child = fastify.log.child() + t.equal(child, logger) + }) + + t.test('should create a default logger if provided one is invalid', (t) => { + t.plan(8) + + const logger = new Date() + + const fastify = Fastify({ logger }) + t.teardown(fastify.close.bind(fastify)) + + t.equal(typeof fastify.log, 'object') + t.equal(typeof fastify.log.fatal, 'function') + t.equal(typeof fastify.log.error, 'function') + t.equal(typeof fastify.log.warn, 'function') + t.equal(typeof fastify.log.info, 'function') + t.equal(typeof fastify.log.debug, 'function') + t.equal(typeof fastify.log.trace, 'function') + t.equal(typeof fastify.log.child, 'function') + }) + + t.test('should not throw error when serializing custom req', (t) => { + t.plan(1) + + const lines = [] + const dest = new stream.Writable({ + write: function (chunk, enc, cb) { + lines.push(JSON.parse(chunk)) + cb() + } + }) + const fastify = Fastify({ logger: { level: 'info', stream: dest } }) + t.teardown(fastify.close.bind(fastify)) + + fastify.log.info({ req: {} }) + + t.same(lines[0].req, {}) + }) +}) diff --git a/test/serial/tap-parallel-not-ok b/test/serial/tap-parallel-not-ok new file mode 100644 index 00000000000..e69de29bb2d From 114d0d01fc962827cf25e9fff807015af90b2bd9 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 25 Apr 2023 14:14:23 +0200 Subject: [PATCH 0269/1295] chore: fix badge link (#4699) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6dc822cc8df..3808292d5ca 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@
-[![CI](https://github.com/fastify/fastify/workflows/ci/badge.svg)](https://github.com/fastify/fastify/actions/workflows/ci.yml) +[![CI](https://github.com/fastify/fastify/workflows/ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/ci.yml) [![Package Manager -CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml) +CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml) [![Web -SIte](https://github.com/fastify/fastify/workflows/website/badge.svg)](https://github.com/fastify/fastify/actions/workflows/website.yml) +SIte](https://github.com/fastify/fastify/workflows/website/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
From 28424ee9c2d83122f0d2cf188ee0c48c414327d3 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 25 Apr 2023 14:15:31 +0200 Subject: [PATCH 0270/1295] docs: add fastify-log-controller to ecosystem (#4696) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index e03f2c62b15..c888043ed3f 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -405,6 +405,8 @@ middlewares into Fastify plugins A simple plugin for Fastify to list all available routes. - [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from a directory and inject the Fastify instance in each file. +- [`fastify-log-controller`](https://github.com/Eomm/fastify-log-controller/) + changes the log level of your Fastify server at runtime. - [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua scripts with [fastify-redis](https://github.com/fastify/fastify-redis) and [lured](https://github.com/enobufs/lured). From 68eebf29de7ed5712c50349a9bfdd779234e0aec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 12:56:43 +0000 Subject: [PATCH 0271/1295] build(deps-dev): Bump tsd from 0.26.1 to 0.28.1 (#4659) Bumps [tsd](https://github.com/SamVerschueren/tsd) from 0.26.1 to 0.28.1. - [Release notes](https://github.com/SamVerschueren/tsd/releases) - [Commits](https://github.com/SamVerschueren/tsd/compare/v0.26.1...v0.28.1) --- updated-dependencies: - dependency-name: tsd dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 794f7e9eb6a..cb77863d158 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "split2": "^4.1.0", "standard": "^17.0.0", "tap": "^16.3.0", - "tsd": "^0.26.0", + "tsd": "^0.28.1", "typescript": "^5.0.3", "undici": "^5.10.0", "vary": "^1.1.2", From a92a40cd5e706ce6382c84ac82725f5ac0f0214f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 13:11:12 +0000 Subject: [PATCH 0272/1295] build(deps-dev): Bump @sinclair/typebox from 0.26.8 to 0.28.6 (#4700) Bumps [@sinclair/typebox](https://github.com/sinclairzx81/typebox) from 0.26.8 to 0.28.6. - [Release notes](https://github.com/sinclairzx81/typebox/releases) - [Commits](https://github.com/sinclairzx81/typebox/compare/0.26.8...0.28.6) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb77863d158..29c9c883621 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "homepage": "https://www.fastify.io/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.26.8", + "@sinclair/typebox": "^0.28.6", "@sinonjs/fake-timers": "^10.0.0", "@types/node": "^18.7.18", "@typescript-eslint/eslint-plugin": "^5.37.0", From b36fd2698363bc3bd7a468a0148acc1ea9284a61 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 25 Apr 2023 15:25:58 +0200 Subject: [PATCH 0273/1295] chore: use Object.prototype.hasOwnProperty in compileSchemasForValidation (#4692) Co-authored-by: Frazer Smith --- lib/validation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/validation.js b/lib/validation.js index 9ab0839991a..2d43892ad53 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -82,25 +82,25 @@ function compileSchemasForValidation (context, compile, isCustom) { }) } context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' }) - } else if (Object.hasOwnProperty.call(schema, 'headers')) { + } else if (Object.prototype.hasOwnProperty.call(schema, 'headers')) { warning.emit('FSTWRN001', 'headers', method, url) } if (schema.body) { context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) - } else if (Object.hasOwnProperty.call(schema, 'body')) { + } else if (Object.prototype.hasOwnProperty.call(schema, 'body')) { warning.emit('FSTWRN001', 'body', method, url) } if (schema.querystring) { context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' }) - } else if (Object.hasOwnProperty.call(schema, 'querystring')) { + } else if (Object.prototype.hasOwnProperty.call(schema, 'querystring')) { warning.emit('FSTWRN001', 'querystring', method, url) } if (schema.params) { context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' }) - } else if (Object.hasOwnProperty.call(schema, 'params')) { + } else if (Object.prototype.hasOwnProperty.call(schema, 'params')) { warning.emit('FSTWRN001', 'params', method, url) } } From c035a185d1f146dfa01e1246850d7a74f0ca672c Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Tue, 25 Apr 2023 17:53:38 +0300 Subject: [PATCH 0274/1295] =?UTF-8?q?Implement=20support=20for=20passing?= =?UTF-8?q?=20custom=20text=20resolution=20for=20starting=20log=E2=80=A6?= =?UTF-8?q?=20(#4698)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Frazer Smith --- docs/Reference/Server.md | 22 +++++++++++++++++++--- lib/server.js | 13 +++++++++---- test/server.test.js | 10 ++++++++++ test/types/instance.test-d.ts | 2 ++ types/instance.d.ts | 6 ++++++ 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 2b62a4321cb..706ef323dd9 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -916,9 +916,25 @@ fastify.ready().then(() => { Starts the server and internally waits for the `.ready()` event. The signature is `.listen([options][, callback])`. Both the `options` object and the -`callback` parameters follow the [Node.js -core](https://nodejs.org/api/net.html#serverlistenoptions-callback) parameter -definitions. +`callback` parameters extend the [Node.js +core](https://nodejs.org/api/net.html#serverlistenoptions-callback) options +object. Thus, all core options are available with the following additional +Fastify specific options: + +### `listenTextResolver` + + +Set an optional resolver for the text to log after server has been successfully +started. +It is possible to override the default `Server listening at [address]` log +entry using this option. + +```js +server.listen({ + port: 9080, + listenTextResolver: (address) => { return `Prometheus metrics server is listening at ${address}` } +}) +``` By default, the server will listen on the address(es) resolved by `localhost` when no specific host is provided. If listening on any available interface is diff --git a/lib/server.js b/lib/server.js index 3e84bbeb250..66821cd154b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -15,6 +15,10 @@ const { module.exports.createServer = createServer module.exports.compileValidateHTTPVersion = compileValidateHTTPVersion +function defaultResolveServerListeningText (address) { + return `Server listening at ${address}` +} + function createServer (options, httpHandler) { const server = getServerInstance(options, httpHandler) @@ -166,7 +170,7 @@ function listenCallback (server, listenOptions) { const wrap = (err) => { server.removeListener('error', wrap) if (!err) { - const address = logServerAddress.call(this, server) + const address = logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText) listenOptions.cb(null, address) } else { this[kState].listening = false @@ -209,7 +213,7 @@ function listenPromise (server, listenOptions) { const listen = new Promise((resolve, reject) => { server.listen(listenOptions, () => { server.removeListener('error', errEventHandler) - resolve(logServerAddress.call(this, server)) + resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)) }) // we set it afterwards because listen can throw this[kState].listening = true @@ -339,7 +343,7 @@ function normalizePort (firstArg) { return port >= 0 && !Number.isNaN(port) && Number.isInteger(port) ? port : 0 } -function logServerAddress (server) { +function logServerAddress (server, listenTextResolver) { let address = server.address() const isUnixSocket = typeof address === 'string' /* istanbul ignore next */ @@ -353,7 +357,8 @@ function logServerAddress (server) { /* istanbul ignore next */ address = (isUnixSocket ? '' : ('http' + (this[kOptions].https ? 's' : '') + '://')) + address - this.log.info('Server listening at ' + address) + const serverListeningText = listenTextResolver(address) + this.log.info(serverListeningText) return address } diff --git a/test/server.test.js b/test/server.test.js index 70dd5a915b1..c946bbd6771 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -34,6 +34,16 @@ test('listen should accept stringified number port', t => { }) }) +test('listen should accept log text resolution function', t => { + t.plan(1) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: '1234', listenTextResolver: () => 'hardcoded text' }, (err) => { + t.error(err) + }) +}) + test('listen should reject string port', async (t) => { t.plan(2) const fastify = Fastify() diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 1ca98c2fb51..2852adb0f88 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -217,6 +217,7 @@ expectDeprecated(server.listen('3000', '')) // test listen opts objects expectAssignable>(server.listen()) expectAssignable>(server.listen({ port: 3000 })) +expectAssignable>(server.listen({ port: 3000, listenTextResolver: (address) => { return `address: ${address}` } })) expectAssignable>(server.listen({ port: 3000, host: '0.0.0.0' })) expectAssignable>(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 })) expectAssignable>(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true })) @@ -224,6 +225,7 @@ expectAssignable>(server.listen({ port: 3000, host: '::/0', expectAssignable(server.listen(() => {})) expectAssignable(server.listen({ port: 3000 }, () => {})) +expectAssignable(server.listen({ port: 3000, listenTextResolver: (address) => { return `address: ${address}` } }, () => {})) expectAssignable(server.listen({ port: 3000, host: '0.0.0.0' }, () => {})) expectAssignable(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 }, () => {})) expectAssignable(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true }, () => {})) diff --git a/types/instance.d.ts b/types/instance.d.ts index 173ffcbb496..3359af5312a 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -74,6 +74,12 @@ export interface FastifyListenOptions { * @since This option is available only in Node.js v15.6.0 and greater */ signal?: AbortSignal; + + /** + * Function that resolves text to log after server has been successfully started + * @param address + */ + listenTextResolver?: (address: string) => string; } type NotInInterface = Key extends keyof _Interface ? never : Key From 3ef63afbb71b2ebda1bc1c2080bc1130e31dbaa7 Mon Sep 17 00:00:00 2001 From: Tzafrir Ben Ami Date: Tue, 25 Apr 2023 18:50:10 +0300 Subject: [PATCH 0275/1295] fix: Export FastifyBaseLogger as interface instead of type alias (#4681) Co-authored-by: Frazer Smith --- types/logger.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/logger.d.ts b/types/logger.d.ts index 5ae6393e36c..012a3e49d3a 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -19,7 +19,7 @@ export type Bindings = pino.Bindings export type ChildLoggerOptions = pino.ChildLoggerOptions -export type FastifyBaseLogger = pino.BaseLogger & { +export interface FastifyBaseLogger extends pino.BaseLogger { child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger } From 2e9b31952e727cca78b06f65af4a8cc7f56bc609 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 16:29:30 +0000 Subject: [PATCH 0276/1295] build(deps): Bump tiny-lru from 10.4.1 to 11.0.1 (#4694) Bumps [tiny-lru](https://github.com/avoidwork/tiny-lru) from 10.4.1 to 11.0.1. - [Release notes](https://github.com/avoidwork/tiny-lru/releases) - [Changelog](https://github.com/avoidwork/tiny-lru/blob/master/CHANGELOG.md) - [Commits](https://github.com/avoidwork/tiny-lru/compare/10.4.1...11.0.1) --- updated-dependencies: - dependency-name: tiny-lru dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 29c9c883621..4498249527e 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,7 @@ "rfdc": "^1.3.0", "secure-json-parse": "^2.5.0", "semver": "^7.3.7", - "tiny-lru": "^10.0.0" + "tiny-lru": "^11.0.1" }, "standard": { "ignore": [ From cefdb3a676c75b8b37655360933fdc4df62a6270 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Apr 2023 00:08:08 +0200 Subject: [PATCH 0277/1295] Added preClose hook (#4697) * Added preClose hook Signed-off-by: Matteo Collina * Update docs/Reference/Hooks.md Co-authored-by: Frazer Smith * Update docs/Reference/Hooks.md Co-authored-by: James Sumners * Update docs/Reference/Hooks.md Co-authored-by: James Sumners * add execution order test Signed-off-by: Matteo Collina * added types Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Co-authored-by: Manuel Spigolon Co-authored-by: Frazer Smith Co-authored-by: James Sumners --- docs/Reference/Hooks.md | 33 ++++++++++++-- fastify.js | 47 +++++++++++--------- lib/hooks.js | 3 ++ test/close.test.js | 91 ++++++++++++++++++++++++++++++++++++++ test/types/hooks.test-d.ts | 11 +++++ types/hooks.d.ts | 28 ++++++++++++ types/instance.d.ts | 15 ++++++- 7 files changed, 202 insertions(+), 26 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index e4076e5c489..36952818cbb 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -25,6 +25,7 @@ are Request/Reply hooks and application hooks: - [Application Hooks](#application-hooks) - [onReady](#onready) - [onClose](#onclose) + - [preClose](#preclose) - [onRoute](#onroute) - [onRegister](#onregister) - [Scope](#scope) @@ -385,6 +386,7 @@ You can hook into the application-lifecycle as well. - [onReady](#onready) - [onClose](#onclose) +- [preClose](#preclose) - [onRoute](#onroute) - [onRegister](#onregister) @@ -414,9 +416,10 @@ fastify.addHook('onReady', async function () { ### onClose -Triggered when `fastify.close()` is invoked to stop the server. It is useful -when [plugins](./Plugins.md) need a "shutdown" event, for example, to close an -open connection to a database. +Triggered when `fastify.close()` is invoked to stop the server, after all in-flight +HTTP requests have been completed. +It is useful when [plugins](./Plugins.md) need a "shutdown" event, for example, +to close an open connection to a database. The hook function takes the Fastify instance as a first argument, and a `done` callback for synchronous hook functions. @@ -434,6 +437,30 @@ fastify.addHook('onClose', async (instance) => { }) ``` +### preClose + + +Triggered when `fastify.close()` is invoked to stop the server, before all in-flight +HTTP requests have been completed. +It is useful when [plugins](./Plugins.md) have set up some state attached +to the HTTP server that would prevent the server to close. +_It is unlikely you will need to use this hook_, +use the [`onClose`](#onclose) for the most common case. + +```js +// callback style +fastify.addHook('preClose', (done) => { + // Some code + done() +}) + +// or async/await style +fastify.addHook('preClose', async () => { + // Some async code + await removeSomeServerState() +}) +``` + ### onRoute diff --git a/fastify.js b/fastify.js index 63dae72ce7f..89555a71932 100644 --- a/fastify.js +++ b/fastify.js @@ -414,30 +414,33 @@ function fastify (options) { fastify.onClose((instance, done) => { fastify[kState].closing = true router.closeRoutes() - if (fastify[kState].listening) { - // No new TCP connections are accepted - instance.server.close(done) - - /* istanbul ignore next: Cannot test this without Node.js core support */ - if (forceCloseConnections === 'idle') { - // Not needed in Node 19 - instance.server.closeIdleConnections() - /* istanbul ignore next: Cannot test this without Node.js core support */ - } else if (serverHasCloseAllConnections && forceCloseConnections) { - instance.server.closeAllConnections() - } else if (forceCloseConnections === true) { - for (const conn of fastify[kKeepAliveConnections]) { - // We must invoke the destroy method instead of merely unreffing - // the sockets. If we only unref, then the callback passed to - // `fastify.close` will never be invoked; nor will any of the - // registered `onClose` hooks. - conn.destroy() - fastify[kKeepAliveConnections].delete(conn) + + hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () { + if (fastify[kState].listening) { + // No new TCP connections are accepted + instance.server.close(done) + + /* istanbul ignore next: Cannot test this without Node.js core support */ + if (forceCloseConnections === 'idle') { + // Not needed in Node 19 + instance.server.closeIdleConnections() + /* istanbul ignore next: Cannot test this without Node.js core support */ + } else if (serverHasCloseAllConnections && forceCloseConnections) { + instance.server.closeAllConnections() + } else if (forceCloseConnections === true) { + for (const conn of fastify[kKeepAliveConnections]) { + // We must invoke the destroy method instead of merely unreffing + // the sockets. If we only unref, then the callback passed to + // `fastify.close` will never be invoked; nor will any of the + // registered `onClose` hooks. + conn.destroy() + fastify[kKeepAliveConnections].delete(conn) + } } + } else { + done(null) } - } else { - done(null) - } + }) }) }) diff --git a/lib/hooks.js b/lib/hooks.js index b6d9643b362..7c0bff02293 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -4,6 +4,7 @@ const applicationHooks = [ 'onRoute', 'onRegister', 'onReady', + 'preClose', 'onClose' ] const lifecycleHooks = [ @@ -48,6 +49,7 @@ function Hooks () { this.onReady = [] this.onTimeout = [] this.onRequestAbort = [] + this.preClose = [] } Hooks.prototype.validate = function (hook, fn) { @@ -78,6 +80,7 @@ function buildHooks (h) { hooks.onTimeout = h.onTimeout.slice() hooks.onRequestAbort = h.onRequestAbort.slice() hooks.onReady = [] + hooks.preClose = [] return hooks } diff --git a/test/close.test.js b/test/close.test.js index d9ac6866c39..fa0b58fc72f 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -495,3 +495,94 @@ test('shutsdown while keep-alive connections are active (non-async, custom)', t }) }) }) + +test('preClose callback', t => { + t.plan(5) + const fastify = Fastify() + fastify.addHook('onClose', onClose) + let preCloseCalled = false + function onClose (instance, done) { + t.equal(preCloseCalled, true) + done() + } + fastify.addHook('preClose', preClose) + + function preClose (done) { + t.type(this, fastify) + preCloseCalled = true + done() + } + + fastify.listen({ port: 0 }, err => { + t.error(err) + + fastify.close((err) => { + t.error(err) + t.ok('close callback') + }) + }) +}) + +test('preClose async', async t => { + t.plan(2) + const fastify = Fastify() + fastify.addHook('onClose', onClose) + let preCloseCalled = false + async function onClose () { + t.equal(preCloseCalled, true) + } + fastify.addHook('preClose', preClose) + + async function preClose () { + preCloseCalled = true + t.type(this, fastify) + } + + await fastify.listen({ port: 0 }) + + await fastify.close() +}) + +test('preClose execution order', t => { + t.plan(4) + async function sleep (ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) + } + const fastify = Fastify() + const order = [] + fastify.addHook('onClose', onClose) + function onClose (instance, done) { + t.same(order, [1, 2, 3]) + done() + } + + fastify.addHook('preClose', (done) => { + setTimeout(function () { + order.push(1) + done() + }, 200) + }) + + fastify.addHook('preClose', async () => { + await sleep(100) + order.push(2) + }) + + fastify.addHook('preClose', (done) => { + setTimeout(function () { + order.push(3) + done() + }, 100) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + fastify.close((err) => { + t.error(err) + t.ok('close callback') + }) + }) +}) diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 45c16819835..4058bb89bc0 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -338,3 +338,14 @@ server.route({ expectType(reply.context.config) } }) + +server.addHook('preClose', function (done) { + expectType(this) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('preClose', async function () { + expectType(this) +}) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 893cbf95af2..4264d2a5402 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -527,3 +527,31 @@ export interface onCloseAsyncHookHandler< instance: FastifyInstance ): Promise; } + +/** + * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need to cancel some state to allow the server to close successfully. + */ +export interface preCloseHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, +> { + ( + this: FastifyInstance, + done: HookHandlerDoneFunction + ): void; +} + +export interface preCloseAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + ): Promise; +} diff --git a/types/instance.d.ts b/types/instance.d.ts index 3359af5312a..d926701713f 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -3,7 +3,7 @@ import { ConstraintStrategy, HTTPVersion } from 'find-my-way' import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' -import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' +import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, preCloseHookHandler, preCloseAsyncHookHandler } from './hooks' import { FastifyBaseLogger } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' @@ -502,6 +502,19 @@ export interface FastifyInstance< hook: onCloseAsyncHookHandler ): FastifyInstance; + /** + * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need to cancel some state to allow the server to close successfully. + */ + addHook( + name: 'preClose', + hook: preCloseHookHandler + ): FastifyInstance; + + addHook( + name: 'preClose', + hook: preCloseAsyncHookHandler + ): FastifyInstance; + /** * Set the 404 handler */ From c2f3ff669a00c2e480d2d6c345b045d225696f9e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Apr 2023 00:10:34 +0200 Subject: [PATCH 0278/1295] Bumped v4.16.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 171 +++------------------------------------- package.json | 2 +- 3 files changed, 11 insertions(+), 164 deletions(-) diff --git a/fastify.js b/fastify.js index 89555a71932..0a2b04734e9 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.15.0' +const VERSION = '4.16.0' const Avvio = require('avvio') const http = require('http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index b539bad6cf2..c723b484369 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -2,171 +2,19 @@ /* istanbul ignore file */ 'use strict' - - -// eslint-disable-next-line -const STR_ESCAPE = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]|[\ud800-\udbff](?![\udc00-\udfff])|(?:[^\ud800-\udbff]|^)[\udc00-\udfff]/ - -class Serializer { - constructor (options) { - switch (options && options.rounding) { - case 'floor': - this.parseInteger = Math.floor - break - case 'ceil': - this.parseInteger = Math.ceil - break - case 'round': - this.parseInteger = Math.round - break - case 'trunc': - default: - this.parseInteger = Math.trunc - break - } - } - - asInteger (i) { - if (typeof i === 'number') { - if (i === Infinity || i === -Infinity) { - throw new Error(`The value "${i}" cannot be converted to an integer.`) - } - if (Number.isInteger(i)) { - return '' + i - } - if (Number.isNaN(i)) { - throw new Error(`The value "${i}" cannot be converted to an integer.`) - } - return this.parseInteger(i) - } else if (i === null) { - return '0' - } else if (typeof i === 'bigint') { - return i.toString() - } else { - /* eslint no-undef: "off" */ - const integer = this.parseInteger(i) - if (Number.isFinite(integer)) { - return '' + integer - } else { - throw new Error(`The value "${i}" cannot be converted to an integer.`) - } - } - } - - asNumber (i) { - const num = Number(i) - if (Number.isNaN(num)) { - throw new Error(`The value "${i}" cannot be converted to a number.`) - } else if (!Number.isFinite(num)) { - return null - } else { - return '' + num - } - } - - asBoolean (bool) { - return bool && 'true' || 'false' // eslint-disable-line - } - - asDateTime (date) { - if (date === null) return '""' - if (date instanceof Date) { - return '"' + date.toISOString() + '"' - } - if (typeof date === 'string') { - return '"' + date + '"' - } - throw new Error(`The value "${date}" cannot be converted to a date-time.`) - } - - asDate (date) { - if (date === null) return '""' - if (date instanceof Date) { - return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"' - } - if (typeof date === 'string') { - return '"' + date + '"' - } - throw new Error(`The value "${date}" cannot be converted to a date.`) - } + const { dependencies } = require('fast-json-stringify/lib/standalone') - asTime (date) { - if (date === null) return '""' - if (date instanceof Date) { - return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"' - } - if (typeof date === 'string') { - return '"' + date + '"' - } - throw new Error(`The value "${date}" cannot be converted to a time.`) - } + const { Serializer, Validator } = dependencies - asString (str) { - if (typeof str !== 'string') { - if (str === null) { - return '""' - } - if (str instanceof Date) { - return '"' + str.toISOString() + '"' - } - if (str instanceof RegExp) { - str = str.source - } else { - str = str.toString() - } - } - - // Fast escape chars check - if (!STR_ESCAPE.test(str)) { - return '"' + str + '"' - } else if (str.length < 42) { - return this.asStringSmall(str) - } else { - return JSON.stringify(str) - } - } + const serializerState = {"mode":"standalone"} + const serializer = Serializer.restoreFromState(serializerState) - // magically escape strings for json - // relying on their charCodeAt - // everything below 32 needs JSON.stringify() - // every string that contain surrogate needs JSON.stringify() - // 34 and 92 happens all the time, so we - // have a fast case for them - asStringSmall (str) { - const l = str.length - let result = '' - let last = 0 - let found = false - let surrogateFound = false - let point = 255 - // eslint-disable-next-line - for (var i = 0; i < l && point >= 32; i++) { - point = str.charCodeAt(i) - if (point >= 0xD800 && point <= 0xDFFF) { - // The current character is a surrogate. - surrogateFound = true - } - if (point === 34 || point === 92) { - result += str.slice(last, i) + '\\' - last = i - found = true - } - } + const validator = null - if (!found) { - result = str - } else { - result += str.slice(last) - } - return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"' - } -} - - const serializer = new Serializer() - + module.exports = function anonymous(validator,serializer +) { - function anonymous0 (input) { // # @@ -206,7 +54,6 @@ class Serializer { } const main = anonymous0 + return main - - - module.exports = main +}(validator, serializer) diff --git a/package.json b/package.json index 4498249527e..514f082ea91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.15.0", + "version": "4.16.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 7c52e8c00cd4c3fb72be2c80ce37e069853f3d4e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Apr 2023 09:14:27 +0200 Subject: [PATCH 0279/1295] bumped to @fastify/fast-json-stringify-compiler v4.3.0 Signed-off-by: Matteo Collina --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 514f082ea91..8298ddfad92 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "dependencies": { "@fastify/ajv-compiler": "^3.5.0", "@fastify/error": "^3.0.0", - "@fastify/fast-json-stringify-compiler": "^4.2.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", "avvio": "^8.2.0", "fast-content-type-parse": "^1.0.0", From 3a5e57d2ffff7bb707da48bcdc2d0e47f861168d Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Apr 2023 09:16:17 +0200 Subject: [PATCH 0280/1295] Bumped v4.16.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 0a2b04734e9..344b7bf88dd 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.16.0' +const VERSION = '4.16.1' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 8298ddfad92..9569fa5de14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.16.0", + "version": "4.16.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From c922d63a4e31cf6e91bf74fa43c3662b496827d0 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Apr 2023 09:42:22 +0200 Subject: [PATCH 0281/1295] Added fast-json-stringify as depedendency Signed-off-by: Matteo Collina --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9569fa5de14..734cdfa70f2 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "eslint-plugin-n": "^15.2.5", "eslint-plugin-promise": "^6.0.1", "fast-json-body": "^1.1.0", - "fast-json-stringify": "^5.3.0", + "fast-json-stringify": "^5.7.0", "fastify-plugin": "^4.2.1", "fluent-json-schema": "^4.0.0", "form-data": "^4.0.0", From ce7a81e95692df2b753da472df8542384acfab85 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Apr 2023 09:44:06 +0200 Subject: [PATCH 0282/1295] Bumped v4.16.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 344b7bf88dd..12530f44de6 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.16.1' +const VERSION = '4.16.2' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 734cdfa70f2..d02c9263b29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.16.1", + "version": "4.16.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From b3ebbdc1ab363233b7728d2bcb4a5b8fe64ccfb3 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Apr 2023 09:59:13 +0200 Subject: [PATCH 0283/1295] Bumped v4.16.3 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index 12530f44de6..492b32fb10b 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.16.2' +const VERSION = '4.16.3' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index d02c9263b29..2ed2fba8b30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.16.2", + "version": "4.16.3", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", @@ -152,7 +152,6 @@ "eslint-plugin-n": "^15.2.5", "eslint-plugin-promise": "^6.0.1", "fast-json-body": "^1.1.0", - "fast-json-stringify": "^5.7.0", "fastify-plugin": "^4.2.1", "fluent-json-schema": "^4.0.0", "form-data": "^4.0.0", @@ -182,6 +181,7 @@ "@fastify/ajv-compiler": "^3.5.0", "@fastify/error": "^3.0.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", + "fast-json-stringify": "^5.7.0", "abstract-logging": "^2.0.1", "avvio": "^8.2.0", "fast-content-type-parse": "^1.0.0", From 08211432b735322b45c670661073b35e2239477f Mon Sep 17 00:00:00 2001 From: lilsweetcaligula <15699226+lilsweetcaligula@users.noreply.github.com> Date: Wed, 26 Apr 2023 20:17:17 +0300 Subject: [PATCH 0284/1295] feat: add codes to framework errors that lack one (#4632) --- fastify.js | 2 +- lib/validation.js | 2 ++ test/404s.test.js | 6 ++++-- test/delete.test.js | 3 +++ test/fluent-schema.test.js | 8 ++++---- test/get.test.js | 3 +++ test/input-validation.js | 15 ++++++++++----- test/schema-examples.test.js | 2 ++ test/schema-serialization.test.js | 12 +++++++----- test/schema-validation.test.js | 12 ++++++++---- test/search.test.js | 3 +++ test/validation-error-handling.test.js | 21 ++++++++++++++++++--- 12 files changed, 65 insertions(+), 24 deletions(-) diff --git a/fastify.js b/fastify.js index 492b32fb10b..7f409603ace 100644 --- a/fastify.js +++ b/fastify.js @@ -689,7 +689,7 @@ function fastify (options) { const reply = new Reply(res, request, childLogger) return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply) } - const body = `{"error":"Bad Request","message":"'${path}' is not a valid url component","statusCode":400}` + const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}` res.writeHead(400, { 'Content-Type': 'application/json', 'Content-Length': body.length diff --git a/lib/validation.js b/lib/validation.js index 2d43892ad53..02540c33ac5 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -138,12 +138,14 @@ function validate (context, request) { function wrapValidationError (result, dataVar, schemaErrorFormatter) { if (result instanceof Error) { result.statusCode = result.statusCode || 400 + result.code = result.code || 'FST_ERR_VALIDATION' result.validationContext = result.validationContext || dataVar return result } const error = schemaErrorFormatter(result, dataVar) error.statusCode = error.statusCode || 400 + error.code = error.code || 'FST_ERR_VALIDATION' error.validation = result error.validationContext = dataVar return error diff --git a/test/404s.test.js b/test/404s.test.js index 94709f6becc..030bb14a4b1 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -1831,7 +1831,8 @@ test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', t => { t.same(JSON.parse(response.payload), { error: 'Bad Request', message: "'/hello/%world' is not a valid url component", - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_BAD_URL' }) }) }) @@ -1849,7 +1850,8 @@ test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', t => { t.same(JSON.parse(response.payload), { error: 'Bad Request', message: "'/hello/%world' is not a valid url component", - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_BAD_URL' }) }) }) diff --git a/test/delete.test.js b/test/delete.test.js index 605b0cb0e8d..74f14d269d1 100644 --- a/test/delete.test.js +++ b/test/delete.test.js @@ -197,6 +197,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 400) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'params/test must be integer', statusCode: 400 }) @@ -232,6 +233,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 400) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'headers/x-test must be number', statusCode: 400 }) @@ -261,6 +263,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 400) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'querystring/hello must be integer', statusCode: 400 }) diff --git a/test/fluent-schema.test.js b/test/fluent-schema.test.js index b6884d6d317..48b95946af2 100644 --- a/test/fluent-schema.test.js +++ b/test/fluent-schema.test.js @@ -34,7 +34,7 @@ test('use fluent-json-schema object', t => { }, (err, res) => { t.error(err) t.equal(res.statusCode, 400) - t.same(res.json(), { statusCode: 400, error: 'Bad Request', message: 'params/id must be >= 42' }) + t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'params/id must be >= 42' }) }) // check header @@ -47,7 +47,7 @@ test('use fluent-json-schema object', t => { }, (err, res) => { t.error(err) t.equal(res.statusCode, 400) - t.same(res.json(), { statusCode: 400, error: 'Bad Request', message: 'headers/x-custom must match format "email"' }) + t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'headers/x-custom must match format "email"' }) }) // check query @@ -60,7 +60,7 @@ test('use fluent-json-schema object', t => { }, (err, res) => { t.error(err) t.equal(res.statusCode, 400) - t.same(res.json(), { statusCode: 400, error: 'Bad Request', message: 'querystring must have required property \'surname\'' }) + t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'querystring must have required property \'surname\'' }) }) // check body @@ -73,7 +73,7 @@ test('use fluent-json-schema object', t => { }, (err, res) => { t.error(err) t.equal(res.statusCode, 400) - t.same(res.json(), { statusCode: 400, error: 'Bad Request', message: 'body/name must be string' }) + t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/name must be string' }) }) // check response diff --git a/test/get.test.js b/test/get.test.js index 7dd6aece733..48270cc0a78 100644 --- a/test/get.test.js +++ b/test/get.test.js @@ -243,6 +243,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 400) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'params/test must be integer', statusCode: 400 }) @@ -280,6 +281,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 400) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'headers/x-test must be number', statusCode: 400 }) @@ -309,6 +311,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 400) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'querystring/hello must be integer', statusCode: 400 }) diff --git a/test/input-validation.js b/test/input-validation.js index 7eab5c927e1..4dbfc8196f8 100644 --- a/test/input-validation.js +++ b/test/input-validation.js @@ -182,7 +182,8 @@ module.exports.payloadMethod = function (method, t) { t.same(body, { error: 'Bad Request', message: 'body/hello must be integer', - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) @@ -251,7 +252,8 @@ module.exports.payloadMethod = function (method, t) { t.same(body, { error: 'Bad Request', message: '"hello" must be a string', - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) @@ -287,7 +289,8 @@ module.exports.payloadMethod = function (method, t) { t.same(body, { error: 'Bad Request', message: 'hello must be a `string` type, but the final value was: `44`.', - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) @@ -305,7 +308,8 @@ module.exports.payloadMethod = function (method, t) { t.same(body, { error: 'Bad Request', message: 'From custom schema compiler!', - statusCode: '400' + statusCode: '400', + code: 'FST_ERR_VALIDATION' }) }) }) @@ -323,7 +327,8 @@ module.exports.payloadMethod = function (method, t) { t.same(body, { error: 'Bad Request', message: 'Always fail!', - statusCode: '400' + statusCode: '400', + code: 'FST_ERR_VALIDATION' }) }) }) diff --git a/test/schema-examples.test.js b/test/schema-examples.test.js index cca8c4af1d9..70b6b16503e 100644 --- a/test/schema-examples.test.js +++ b/test/schema-examples.test.js @@ -502,6 +502,7 @@ test('should return custom error messages with ajv-errors', t => { t.error(err) t.same(JSON.parse(res.payload), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/age bad age - should be num, body name please, body work please' }) @@ -557,6 +558,7 @@ test('should be able to handle formats of ajv-formats when added by plugins opti }, (_err, res) => { t.same(JSON.parse(res.payload), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/id must match format "uuid"' }) diff --git a/test/schema-serialization.test.js b/test/schema-serialization.test.js index ee2578adddb..0910dd4dd51 100644 --- a/test/schema-serialization.test.js +++ b/test/schema-serialization.test.js @@ -394,7 +394,8 @@ test('Use shared schema and $ref with $id in response ($ref to $id)', t => { t.same(res.json(), { error: 'Bad Request', message: "body must have required property 'address'", - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) @@ -503,7 +504,8 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s t.same(res.json(), { error: 'Bad Request', message: 'body/0/location/email must match format "email"', - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) @@ -806,9 +808,9 @@ test('do not crash if status code serializer errors', async t => { const someUserErrorType2 = { type: 'object', properties: { - code: { type: 'number' } + customCode: { type: 'number' } }, - required: ['code'] + required: ['customCode'] } fastify.get( @@ -834,7 +836,7 @@ test('do not crash if status code serializer errors', async t => { t.same(res.json(), { statusCode: 500, code: 'FST_ERR_FAILED_ERROR_SERIALIZATION', - message: 'Failed to serialize an error. Error: "code" is required!. ' + + message: 'Failed to serialize an error. Error: "customCode" is required!. ' + 'Original error: querystring must have required property \'foo\'' }) }) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index e245a17342d..9984b9aae63 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -100,7 +100,7 @@ test('Basic validation test', t => { url: '/' }, (err, res) => { t.error(err) - t.same(res.json(), { statusCode: 400, error: 'Bad Request', message: "body must have required property 'work'" }) + t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) t.equal(res.statusCode, 400) }) }) @@ -537,6 +537,7 @@ test('JSON Schema validation keywords', t => { t.equal(res.statusCode, 400) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'params/ip must match format "ipv4"' }) @@ -593,7 +594,8 @@ test('Nested id calls', t => { t.same(res.json(), { error: 'Bad Request', message: 'body/host/ip must match format "ipv4"', - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) @@ -695,7 +697,8 @@ test('Use shared schema and $ref with $id ($ref to $id)', t => { t.same(res.json(), { error: 'Bad Request', message: "body must have required property 'address'", - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) @@ -811,7 +814,8 @@ test('Use $ref to /definitions', t => { t.same(res.json(), { error: 'Bad Request', message: 'body/test/id must be number', - statusCode: 400 + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) diff --git a/test/search.test.js b/test/search.test.js index c0f1ba2bdf3..67a95c29b4c 100644 --- a/test/search.test.js +++ b/test/search.test.js @@ -167,6 +167,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 400) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'params/test must be integer', statusCode: 400 }) @@ -196,6 +197,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 400) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'querystring/hello must be integer', statusCode: 400 }) @@ -231,6 +233,7 @@ fastify.listen({ port: 0 }, err => { t.equal(response.headers['content-length'], '' + body.length) t.same(JSON.parse(body), { error: 'Bad Request', + code: 'FST_ERR_VALIDATION', message: 'body/test must be integer', statusCode: 400 }) diff --git a/test/validation-error-handling.test.js b/test/validation-error-handling.test.js index ff5a8b7a705..d87793fc654 100644 --- a/test/validation-error-handling.test.js +++ b/test/validation-error-handling.test.js @@ -57,6 +57,7 @@ test('should fail immediately with invalid payload', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'name'" }) @@ -244,6 +245,7 @@ test('should respect when attachValidation is explicitly set to false', t => { t.error(err) t.same(JSON.parse(res.payload), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'name'" }) @@ -368,7 +370,7 @@ test('should return a defined output message parsing AJV errors', t => { url: '/' }, (err, res) => { t.error(err) - t.equal(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body must have required property \'name\'"}') + t.equal(res.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body must have required property \'name\'"}') }) }) @@ -398,7 +400,7 @@ test('should return a defined output message parsing JOI errors', t => { url: '/' }, (err, res) => { t.error(err) - t.equal(res.payload, '{"statusCode":400,"error":"Bad Request","message":"\\"name\\" is required"}') + t.equal(res.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"\\"name\\" is required"}') }) }) @@ -431,7 +433,7 @@ test('should return a defined output message parsing JOI error details', t => { url: '/' }, (err, res) => { t.error(err) - t.equal(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body \\"name\\" is required"}') + t.equal(res.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body \\"name\\" is required"}') }) }) @@ -457,6 +459,7 @@ test('the custom error formatter context must be the server instance', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'my error' }) @@ -486,6 +489,7 @@ test('the custom error formatter context must be the server instance in options' t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'my error' }) @@ -522,6 +526,7 @@ test('should call custom error formatter', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'my error' }) @@ -609,6 +614,7 @@ test('should register a route based schema error formatter', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'abc' }) @@ -652,6 +658,7 @@ test('prefer route based error formatter over global one', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: '123' }) @@ -668,6 +675,7 @@ test('prefer route based error formatter over global one', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'abc' }) @@ -684,6 +692,7 @@ test('prefer route based error formatter over global one', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'abc123' }) @@ -712,6 +721,7 @@ test('adding schemaErrorFormatter', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'abc' }) @@ -772,6 +782,7 @@ test('plugin override', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'A' }) @@ -788,6 +799,7 @@ test('plugin override', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'B' }) @@ -804,6 +816,7 @@ test('plugin override', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'C' }) @@ -820,6 +833,7 @@ test('plugin override', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'D' }) @@ -836,6 +850,7 @@ test('plugin override', t => { t.error(err) t.same(res.json(), { statusCode: 400, + code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'C' }) From 09e71b01d8faf4ca33317e73d7215b782e967e2c Mon Sep 17 00:00:00 2001 From: Matthew Ailes Date: Thu, 27 Apr 2023 01:35:29 -0400 Subject: [PATCH 0285/1295] fix: revert changes made to route hook types (#4708) --- test/types/hooks.test-d.ts | 42 ++++++++++++++++++++++++++++++++++++++ types/route.d.ts | 42 ++++++++++---------------------------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 4058bb89bc0..b6d654090ae 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -295,6 +295,48 @@ server.route({ } }) +server.get('/', { + onRequest: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + preParsing: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + preValidation: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + preHandler: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + preSerialization: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + onSend: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + onResponse: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + onTimeout: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + }, + onError: async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) + } +}, async (request, reply) => { + expectType(request.context.config) + expectType(reply.context.config) +}) + type CustomContextRequest = FastifyRequest type CustomContextReply = FastifyReply server.route({ diff --git a/types/route.d.ts b/types/route.d.ts index 937516340f5..9f33a246e18 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -3,7 +3,7 @@ import { FastifyRequest, RequestGenericInterface } from './request' import { FastifyReply, ReplyGenericInterface } from './reply' import { FastifySchema, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorFormatter } from './schema' import { HTTPMethods, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' -import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, preParsingAsyncHookHandler, preValidationAsyncHookHandler, preHandlerAsyncHookHandler, preSerializationAsyncHookHandler, onSendAsyncHookHandler, onResponseAsyncHookHandler, onTimeoutAsyncHookHandler, onErrorAsyncHookHandler, onRequestAbortAsyncHookHandler } from './hooks' +import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler, onRequestAbortHookHandler } from './hooks' import { FastifyError } from '@fastify/error' import { FastifyContext } from './context' import { @@ -45,45 +45,25 @@ export interface RouteShorthandOptions< // hooks onRequest?: onRequestHookHandler - | onRequestHookHandler[] - | onRequestAsyncHookHandler - | onRequestAsyncHookHandler[]; + | onRequestHookHandler[]; preParsing?: preParsingHookHandler - | preParsingHookHandler[] - | preParsingAsyncHookHandler - | preParsingAsyncHookHandler[]; + | preParsingHookHandler[]; preValidation?: preValidationHookHandler - | preValidationHookHandler[] - | preValidationAsyncHookHandler - | preValidationAsyncHookHandler[]; + | preValidationHookHandler[]; preHandler?: preHandlerHookHandler - | preHandlerHookHandler[] - | preHandlerAsyncHookHandler - | preHandlerAsyncHookHandler[]; + | preHandlerHookHandler[]; preSerialization?: preSerializationHookHandler - | preSerializationHookHandler[] - | preSerializationAsyncHookHandler - | preSerializationAsyncHookHandler[]; + | preSerializationHookHandler[]; onSend?: onSendHookHandler - | onSendHookHandler[] - | onSendAsyncHookHandler - | onSendAsyncHookHandler[]; + | onSendHookHandler[]; onResponse?: onResponseHookHandler - | onResponseHookHandler[] - | onResponseAsyncHookHandler - | onResponseAsyncHookHandler[]; + | onResponseHookHandler[]; onTimeout?: onTimeoutHookHandler - | onTimeoutHookHandler[] - | onTimeoutAsyncHookHandler - | onTimeoutAsyncHookHandler[]; + | onTimeoutHookHandler[]; onError?: onErrorHookHandler - | onErrorHookHandler[] - | onErrorAsyncHookHandler - | onErrorAsyncHookHandler[]; + | onErrorHookHandler[]; onRequestAbort?: onRequestAbortHookHandler - | onRequestAbortHookHandler[] - | onRequestAbortAsyncHookHandler - | onRequestAbortAsyncHookHandler[]; + | onRequestAbortHookHandler[]; } /** From 0dd31b71eeb79da4a43738473eaebcf5844fc8d4 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 27 Apr 2023 10:26:36 +0200 Subject: [PATCH 0286/1295] Bumped v4.17.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 7f409603ace..9d25dbd8998 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.16.3' +const VERSION = '4.17.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 2ed2fba8b30..17d564113fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.16.3", + "version": "4.17.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From e1643860a9df8d40c9f0c370126a336d00caf2ff Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 27 Apr 2023 12:05:55 +0200 Subject: [PATCH 0287/1295] Update GOVERNANCE.md (#4709) --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index c21c1bf15b2..a4e63562227 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -14,8 +14,8 @@ ## Lead Maintainers -Fastify Lead Maintainers are the founder of the project and the organization -owners. They are the only members of the `@fastify/leads` team. The Lead +Fastify Lead Maintainers are the organization owners. +They are the only members of the `@fastify/leads` team. The Lead Maintainers are the curator of the Fastify project and their key responsibility is to issue releases of Fastify and its dependencies. From 7431a2325a2d45c104af24fdfdd021a5e8be710f Mon Sep 17 00:00:00 2001 From: Cynthia Date: Fri, 28 Apr 2023 14:38:49 +0200 Subject: [PATCH 0288/1295] docs: add fastify-opaque-apake to Ecosystem.md (#4712) Signed-off-by: Cynthia --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index c888043ed3f..86dbd55645f 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -465,6 +465,9 @@ middlewares into Fastify plugins ORM. - [`fastify-objectionjs-classes`](https://github.com/kamikazechaser/fastify-objectionjs-classes) Plugin to cherry-pick classes from objectionjs ORM. +- [`fastify-opaque-apake`](https://github.com/squirrelchat/fastify-opaque-apake) + A Fastify plugin to implement the OPAQUE aPAKE protocol. Uses + [@squirrelchat/opaque-wasm-server](https://github.com/squirrelchat/opaque-wasm). - [`fastify-openapi-docs`](https://github.com/ShogunPanda/fastify-openapi-docs) A Fastify plugin that generates OpenAPI spec automatically. - [`fastify-openapi-glue`](https://github.com/seriousme/fastify-openapi-glue) From a201010d46a500b6a569ebc32b648feb99b28499 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 30 Apr 2023 09:29:25 +0100 Subject: [PATCH 0289/1295] ci: only trigger on pushes to main branches (#4714) --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef8ec39567b..2abd0206d06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,11 @@ name: ci on: push: + branches: + - main + - master + - next + - 'v*' paths-ignore: - 'docs/**' - '*.md' From dd9414d569fb91f8cfe7df2b7d9f972d4b5d15ec Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 30 Apr 2023 12:24:37 +0100 Subject: [PATCH 0290/1295] ci: test using pnpm 8 (#4720) --- .github/workflows/integration.yml | 11 ++++++++--- .github/workflows/package-manager-ci.yml | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 2571439fa7a..8fa8c541d5f 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -18,8 +18,14 @@ jobs: strategy: matrix: - node-version: [14, 16, 18] + node-version: [16, 18] os: [ubuntu-latest] + pnpm-version: [8] + # pnpm@8 does not support Node.js 14 so include it separately + include: + - node-version: 14 + os: ubuntu-latest + pnpm-version: 7 steps: - uses: actions/checkout@v3 @@ -34,8 +40,7 @@ jobs: - name: Install Pnpm uses: pnpm/action-setup@v2 with: - # pnpm@8 does not support Node.js 14 - version: 7 + version: ${{ matrix.pnpm-version }} - name: Install Production run: | diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 23e9d7fb1e9..6abc2d6ee43 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -15,8 +15,14 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [14, 16, 18] + node-version: [16, 18] os: [ubuntu-latest] + pnpm-version: [8] + # pnpm@8 does not support Node.js 14 so include it separately + include: + - node-version: 14 + os: ubuntu-latest + pnpm-version: 7 steps: - uses: actions/checkout@v3 @@ -31,8 +37,7 @@ jobs: - name: Install with pnpm uses: pnpm/action-setup@v2 with: - # pnpm@8 does not support Node.js 14 - version: 7 + version: ${{ matrix.pnpm-version }} - run: pnpm install From fc09e73e0286fd21ecfdd731092e3d5ec57123cf Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 30 Apr 2023 15:25:57 +0100 Subject: [PATCH 0291/1295] ci(integration): only trigger on pushes to main branches (#4721) --- .github/workflows/integration.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 8fa8c541d5f..7bfeb05ba62 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -2,6 +2,11 @@ name: integration on: push: + branches: + - main + - master + - next + - 'v*' paths-ignore: - 'docs/**' - '*.md' From af1e0422b5632b847217188b360c970d872351a6 Mon Sep 17 00:00:00 2001 From: Jascha Ephraim Date: Mon, 1 May 2023 02:34:17 -0700 Subject: [PATCH 0292/1295] fix: keep custom response schema class (#4718) * add test and fix to maintain custom response schema * ensure mixed schema types are individually skipped or normalized --- lib/schemas.js | 39 ++++++++++----------- test/internals/validation.test.js | 58 +++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/lib/schemas.js b/lib/schemas.js index 154248cf0b9..ac3caabaff2 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -44,6 +44,16 @@ Schemas.prototype.getSchema = function (schemaId) { return this.store[schemaId] } +/** + * Checks whether a schema is a non-plain object. + * + * @param {*} schema the schema to check + * @returns {boolean} true if schema has a custom prototype + */ +function isCustomSchemaPrototype (schema) { + return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype +} + function normalizeSchema (routeSchemas, serverOptions) { if (routeSchemas[kSchemaVisited]) { return routeSchemas @@ -60,33 +70,20 @@ function normalizeSchema (routeSchemas, serverOptions) { generateFluentSchema(routeSchemas) - // let's check if our schemas have a custom prototype - for (const key of ['headers', 'querystring', 'params', 'body']) { - if (typeof routeSchemas[key] === 'object' && Object.getPrototypeOf(routeSchemas[key]) !== Object.prototype) { - routeSchemas[kSchemaVisited] = true - return routeSchemas + for (const key of SCHEMAS_SOURCE) { + const schema = routeSchemas[key] + if (schema && !isCustomSchemaPrototype(schema)) { + routeSchemas[key] = getSchemaAnyway(schema, serverOptions.jsonShorthand) } } - if (routeSchemas.body) { - routeSchemas.body = getSchemaAnyway(routeSchemas.body, serverOptions.jsonShorthand) - } - - if (routeSchemas.headers) { - routeSchemas.headers = getSchemaAnyway(routeSchemas.headers, serverOptions.jsonShorthand) - } - - if (routeSchemas.querystring) { - routeSchemas.querystring = getSchemaAnyway(routeSchemas.querystring, serverOptions.jsonShorthand) - } - - if (routeSchemas.params) { - routeSchemas.params = getSchemaAnyway(routeSchemas.params, serverOptions.jsonShorthand) - } - if (routeSchemas.response) { const httpCodes = Object.keys(routeSchemas.response) for (const code of httpCodes) { + if (isCustomSchemaPrototype(routeSchemas.response[code])) { + continue + } + const contentProperty = routeSchemas.response[code].content let hasContentMultipleContentTypes = false diff --git a/test/internals/validation.test.js b/test/internals/validation.test.js index 75e99597ea3..3e1ce6b74d6 100644 --- a/test/internals/validation.test.js +++ b/test/internals/validation.test.js @@ -300,3 +300,61 @@ test('build schema - uppercased headers are not included', t => { return () => {} }) }) + +test('build schema - mixed schema types are individually skipped or normalized', t => { + t.plan(6) + + class CustomSchemaClass {} + const nonNormalizedSchema = { + hello: { type: 'string' } + } + const normalizedSchema = { + type: 'object', + properties: nonNormalizedSchema + } + + const testCases = [{ + schema: { + body: new CustomSchemaClass() + }, + assertions: (schema) => { + t.type(schema.body, CustomSchemaClass) + } + }, { + schema: { + response: { + 200: new CustomSchemaClass() + } + }, + assertions: (schema) => { + t.type(schema.response[200], CustomSchemaClass) + } + }, { + schema: { + body: nonNormalizedSchema, + response: { + 200: new CustomSchemaClass() + } + }, + assertions: (schema) => { + t.same(schema.body, normalizedSchema) + t.type(schema.response[200], CustomSchemaClass) + } + }, { + schema: { + body: new CustomSchemaClass(), + response: { + 200: nonNormalizedSchema + } + }, + assertions: (schema) => { + t.type(schema.body, CustomSchemaClass) + t.same(schema.response[200], normalizedSchema) + } + }] + + testCases.forEach((testCase) => { + const result = normalizeSchema(testCase.schema, { jsonShorthand: true }) + testCase.assertions(result) + }) +}) From f1737b32f0a3229df3fcf7dd50738032d8a4a36c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 15:06:56 +0000 Subject: [PATCH 0293/1295] build(deps): Bump lycheeverse/lychee-action from 1.6.1 to 1.7.0 (#4723) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.6.1 to 1.7.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/9ace499fe66cee282a29eaa628fdac2c72fa087f...97189f2c0a3c8b0cb0e704fd4e878af6e5e2b2c5) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index f8cf6298c7a..f8924319a65 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@9ace499fe66cee282a29eaa628fdac2c72fa087f + uses: lycheeverse/lychee-action@97189f2c0a3c8b0cb0e704fd4e878af6e5e2b2c5 with: fail: true # As external links behaviour is not predictable, we check only internal links From 72e1a7c28147d1a540fd7510f1beef02681dc4ce Mon Sep 17 00:00:00 2001 From: ileighAube <79858612+ileighAube@users.noreply.github.com> Date: Tue, 2 May 2023 06:09:48 -0400 Subject: [PATCH 0294/1295] docs(readme): move table of contents (#4722) * Update README.md * Update README.md Co-authored-by: Manuel Spigolon --------- Co-authored-by: Frazer Smith Co-authored-by: Manuel Spigolon --- README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3808292d5ca..ddc2d03fe6e 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,18 @@ resources of your server, knowing that you are serving the highest number of requests as possible, without sacrificing security validations and handy development? +Enter Fastify. Fastify is a web framework highly focused on providing the best +developer experience with the least overhead and a powerful plugin architecture. +It is inspired by Hapi and Express and as far as we know, it is one of the +fastest web frameworks in town. + +The `main` branch refers to the Fastify `v4` release. Check out the +[`v3.x` branch](https://github.com/fastify/fastify/tree/3.x) for `v3`. + + + +### Table of Contents + - [Quick start](#quick-start) - [Install](#install) - [Example](#example) @@ -51,13 +63,6 @@ development? - [Hosted by](#hosted-by) - [License](#license) -Enter Fastify. Fastify is a web framework highly focused on providing the best -developer experience with the least overhead and a powerful plugin architecture. -It is inspired by Hapi and Express and as far as we know, it is one of the -fastest web frameworks in town. - -This branch refers to the Fastify v4 release. Check out the -[v3.x](https://github.com/fastify/fastify/tree/v3.x) branch for v3. ### Quick start From be137713136ba5f7bd4c22805ebc501b3412565f Mon Sep 17 00:00:00 2001 From: AJ Bienz Date: Tue, 2 May 2023 10:11:45 -0400 Subject: [PATCH 0295/1295] Updates to docs and types regarding the `res` serializer (#4716) * docs: clarify docs on `res` serializer * types: update `res` serializer type to better reflect all scenarios * docs: remove extra whitespace * fixup! types: update `res` serializer type to better reflect all scenarios --- docs/Reference/Logging.md | 35 ++++++++++++++++++++++++++++++++++- test/types/logger.test-d.ts | 14 ++++++++++---- types/logger.d.ts | 11 ++++++++++- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 486b62485a8..59fee063102 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -22,7 +22,7 @@ const fastify = require('fastify')({ ``` Enabling the logger with appropriate configuration for both local development -and production and test environment requires bit more configuration: +and production and test environment requires a bit more configuration: ```js const envToLogger = { @@ -108,6 +108,7 @@ serialize objects with `req`, `res`, and `err` properties. The object received by `req` is the Fastify [`Request`](./Request.md) object, while the object received by `res` is the Fastify [`Reply`](./Reply.md) object. This behaviour can be customized by specifying custom serializers. + ```js const fastify = require('fastify')({ logger: { @@ -152,6 +153,34 @@ const fastify = require('fastify')({ } }); ``` + +**Note**: In certain cases, the [`Reply`](./Reply.md) object passed to the `res` +serializer cannot be fully constructed. When writing a custom `res` serializer, +it is necessary to check for the existence of any properties on `reply` aside +from `statusCode`, which is always present. For example, the existence of +`getHeaders` must be verified before it can be called: + +```js +const fastify = require('fastify')({ + logger: { + transport: { + target: 'pino-pretty' + }, + serializers: { + res (reply) { + // The default + return { + statusCode: reply.statusCode + headers: typeof reply.getHeaders === 'function' + ? reply.getHeaders() + : {} + } + }, + } + } +}); +``` + **Note**: The body cannot be serialized inside a `req` method because the request is serialized when we create the child logger. At that time, the body is not yet parsed. @@ -167,6 +196,10 @@ app.addHook('preHandler', function (req, reply, done) { }) ``` +**Note**: Care should be take to ensure serializers never throw, as an error +thrown from a serializer has the potential to cause the Node process to exit. +See the [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) +on serializers for more information. *Any logger other than Pino will ignore this option.* diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index 10a570a1ba3..dfbefdf3a1e 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -1,15 +1,17 @@ -import { expectDeprecated, expectError, expectType } from 'tsd' +import { expectAssignable, expectDeprecated, expectError, expectNotAssignable, expectType } from 'tsd' import fastify, { FastifyLogFn, LogLevel, FastifyLoggerInstance, FastifyRequest, FastifyReply, - FastifyBaseLogger + FastifyBaseLogger, + FastifyInstance } from '../../fastify' import { Server, IncomingMessage, ServerResponse } from 'http' import * as fs from 'fs' import P from 'pino' +import { ResSerializerReply } from '../../types/logger' expectType(fastify().log) @@ -127,7 +129,9 @@ const serverAutoInferredSerializerResponseObjectOption = fastify({ logger: { serializers: { res (ServerResponse) { - expectType(ServerResponse) + expectType>(ServerResponse) + expectAssignable & Pick>(ServerResponse) + expectNotAssignable(ServerResponse) return { status: '200' } @@ -154,7 +158,9 @@ const serverAutoInferredSerializerObjectOption = fastify({ } }, res (ServerResponse) { - expectType(ServerResponse) + expectType>(ServerResponse) + expectAssignable & Pick>(ServerResponse) + expectNotAssignable(ServerResponse) return { statusCode: 'statusCode' } diff --git a/types/logger.d.ts b/types/logger.d.ts index 012a3e49d3a..6b698b31aaf 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -35,6 +35,15 @@ export interface FastifyLoggerStreamDestination { export type PinoLoggerOptions = pino.LoggerOptions +// TODO: once node 18 is EOL, this type can be replaced with plain FastifyReply. +/** + * Specialized reply type used for the `res` log serializer, since only `statusCode` is passed in certain cases. + */ +export type ResSerializerReply< + RawServer extends RawServerBase, + RawReply extends FastifyReply +> = Partial & Pick; + /** * Fastify Custom Logger options. */ @@ -59,7 +68,7 @@ export interface FastifyLoggerOptions< stack: string; [key: string]: unknown; }; - res?: (res: RawReply) => { + res?: (res: ResSerializerReply) => { statusCode?: string | number; [key: string]: unknown; }; From 8e7c7a4ab36da58e7079a4f9f6aaafa6b91dffa2 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Wed, 3 May 2023 14:30:47 +0800 Subject: [PATCH 0296/1295] chore(deps): bump process-warning to ^2.2.0 (#4726) Signed-off-by: KaKa --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17d564113fe..a17587539e6 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,7 @@ "find-my-way": "^7.6.0", "light-my-request": "^5.6.1", "pino": "^8.5.0", - "process-warning": "^2.0.0", + "process-warning": "^2.2.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", "secure-json-parse": "^2.5.0", From dc87e008652f2b2bd1c0e55dab40a29904da2f15 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 3 May 2023 08:26:43 +0100 Subject: [PATCH 0297/1295] build(deps): bump dependencies (#4727) --- package.json | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index a17587539e6..b4fda03ede0 100644 --- a/package.json +++ b/package.json @@ -134,65 +134,65 @@ "homepage": "https://www.fastify.io/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.28.6", - "@sinonjs/fake-timers": "^10.0.0", - "@types/node": "^18.7.18", - "@typescript-eslint/eslint-plugin": "^5.37.0", - "@typescript-eslint/parser": "^5.37.0", - "ajv": "^8.11.0", + "@sinclair/typebox": "^0.28.9", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "^18.16.3", + "@typescript-eslint/eslint-plugin": "^5.59.2", + "@typescript-eslint/parser": "^5.59.2", + "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "branch-comparer": "^1.1.0", - "eslint": "^8.23.1", + "eslint": "^8.39.0", "eslint-config-standard": "^17.0.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-n": "^15.2.5", - "eslint-plugin-promise": "^6.0.1", + "eslint-import-resolver-node": "^0.3.7", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-promise": "^6.1.1", "fast-json-body": "^1.1.0", - "fastify-plugin": "^4.2.1", - "fluent-json-schema": "^4.0.0", + "fastify-plugin": "^4.5.0", + "fluent-json-schema": "^4.1.0", "form-data": "^4.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", - "joi": "^17.6.0", - "json-schema-to-ts": "^2.5.5", + "joi": "^17.9.2", + "json-schema-to-ts": "^2.8.0", "JSONStream": "^1.3.5", "license-checker": "^25.0.1", - "markdownlint-cli2": "^0.7.0", + "markdownlint-cli2": "^0.7.1", "proxyquire": "^2.1.3", "pump": "^3.0.0", "self-cert": "^2.0.0", "send": "^0.18.0", "simple-get": "^4.0.1", "snazzy": "^9.0.0", - "split2": "^4.1.0", + "split2": "^4.2.0", "standard": "^17.0.0", - "tap": "^16.3.0", + "tap": "^16.3.4", "tsd": "^0.28.1", - "typescript": "^5.0.3", - "undici": "^5.10.0", + "typescript": "^5.0.4", + "undici": "^5.22.0", "vary": "^1.1.2", - "yup": "^1.0.0" + "yup": "^1.1.1" }, "dependencies": { "@fastify/ajv-compiler": "^3.5.0", - "@fastify/error": "^3.0.0", + "@fastify/error": "^3.2.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", "fast-json-stringify": "^5.7.0", "abstract-logging": "^2.0.1", - "avvio": "^8.2.0", + "avvio": "^8.2.1", "fast-content-type-parse": "^1.0.0", "find-my-way": "^7.6.0", - "light-my-request": "^5.6.1", - "pino": "^8.5.0", + "light-my-request": "^5.9.1", + "pino": "^8.12.0", "process-warning": "^2.2.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", "secure-json-parse": "^2.5.0", - "semver": "^7.3.7", + "semver": "^7.5.0", "tiny-lru": "^11.0.1" }, "standard": { From fa400a22570bbaf30670f385d407d771ceb39d5d Mon Sep 17 00:00:00 2001 From: Paul Fayoux <78373471+paul-jolimoi@users.noreply.github.com> Date: Thu, 4 May 2023 14:47:49 +0200 Subject: [PATCH 0298/1295] Add mention to default error handler on reply send (#4713) * doc(#4606): Add mention to default error handler on reply send * doc(#4606): Reword the sentence * doc(fastify#4606): Fixing linting * doc(fastify#4606): Fixing linting --- docs/Reference/Lifecycle.md | 3 +++ docs/Reference/Reply.md | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/docs/Reference/Lifecycle.md b/docs/Reference/Lifecycle.md index b12a1f22f62..d8c29495f7d 100644 --- a/docs/Reference/Lifecycle.md +++ b/docs/Reference/Lifecycle.md @@ -1,6 +1,8 @@

Fastify

## Lifecycle + + Following the schema of the internal lifecycle of Fastify. 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` hooks will still be executed ## Reply Lifecycle + Whenever the user handles the request, the result may be: diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 9a7e78b11f2..7a1779c7eb7 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -789,6 +789,11 @@ fastify.setErrorHandler(function (error, request, reply) { }) ``` +Beware that calling `reply.send(error)` in your custom error handler will send +the error to the default error handler. +Check out the [Reply Lifecycle](./Reference/Lifecycle#reply-lifecycle) +for more information. + The not found errors generated by the router will use the [`setNotFoundHandler`](./Server.md#setnotfoundhandler) From 97b5ba7f2ed8ff8f69806bfaee1f689efef8fb8a Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 5 May 2023 14:47:21 +0800 Subject: [PATCH 0299/1295] chore: fix Reply.md internal link (#4731) * chore: fix Reply.md internal link * chore: update by comment --- docs/Reference/Reply.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 7a1779c7eb7..3ad8b942b33 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -791,7 +791,7 @@ fastify.setErrorHandler(function (error, request, reply) { Beware that calling `reply.send(error)` in your custom error handler will send the error to the default error handler. -Check out the [Reply Lifecycle](./Reference/Lifecycle#reply-lifecycle) +Check out the [Reply Lifecycle](./Lifecycle.md#reply-lifecycle) for more information. The not found errors generated by the router will use the From a6b17213a4f7a53ff451c64f2433c9798d0e3378 Mon Sep 17 00:00:00 2001 From: Luis Orbaiceta <44276180+luisorbaiceta@users.noreply.github.com> Date: Sat, 6 May 2023 15:52:21 +0200 Subject: [PATCH 0300/1295] Update website.yml (#4736) Update to v2 of the CircleCI api --- .github/workflows/website.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 1ff589d3ba6..54699b65fe0 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -19,4 +19,8 @@ jobs: steps: - name: Build website run: | - curl -X POST --header 'Content-Type: application/json' "https://circleci.com/api/v1.1/project/github/fastify/website?circle-token=${{ secrets.circleci_token }}" + curl -X POST \ + -H 'Content-Type: application/json' \ + -H 'Circle-Token: ${{ secrets.circleci_token }}' \ + -d '{"branch":"master"}' \ + "https://circleci.com/api/v2/project/gh/fastify/website/pipeline" From aa8ae56368790d26b8fcfd5444d4b6490d6d0a3a Mon Sep 17 00:00:00 2001 From: Simone Sacchi Date: Sun, 7 May 2023 17:29:45 +0200 Subject: [PATCH 0301/1295] feat: add listeningOrigin fastify immutable instance property (#4586) (#4674) --- docs/Reference/Server.md | 10 +++++++++ fastify.js | 10 +++++++++ test/fastify-instance.test.js | 39 +++++++++++++++++++++++++++++++++++ test/types/instance.test-d.ts | 1 + types/instance.d.ts | 2 +- 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 706ef323dd9..4b465244fc3 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -63,6 +63,7 @@ describes the properties available in that options object. - [prefix](#prefix) - [pluginName](#pluginname) - [hasPlugin](#hasplugin) + - [listeningOrigin](#listeningOrigin) - [log](#log) - [version](#version) - [inject](#inject) @@ -1231,6 +1232,15 @@ fastify.ready(() => { }) ``` +### listeningOrigin + + +The current origin the server is listening to. +For example, a TCP socket based server returns +a base address like `http://127.0.0.1:3000`, +and a Unix socket server will return the socket +path, e.g. `fastify.temp.sock`. + #### log diff --git a/fastify.js b/fastify.js index 9d25dbd8998..ae71511bb23 100644 --- a/fastify.js +++ b/fastify.js @@ -348,6 +348,16 @@ function fastify (options) { } Object.defineProperties(fastify, { + listeningOrigin: { + get () { + const address = this.addresses().slice(-1).pop() + /* istanbul ignore if windows: unix socket is not testable on Windows platform */ + if (typeof address === 'string') { + return address + } + return `${this[kOptions].https ? 'https' : 'http'}://${address.address}:${address.port}` + } + }, pluginName: { configurable: true, get () { diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index f013358c320..3b347ac8ca3 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -3,6 +3,8 @@ const t = require('tap') const test = t.test const Fastify = require('..') +const os = require('os') + const { kOptions, kErrorHandler @@ -97,3 +99,40 @@ test('errorHandler in plugin should be separate from the external one', async t t.ok(fastify[kErrorHandler].func instanceof Function) t.same(fastify.errorHandler, fastify[kErrorHandler].func) }) + +test('fastify instance should contains listeningOrigin property (with port and host)', async t => { + t.plan(1) + const port = 3000 + const host = '127.0.0.1' + const fastify = Fastify() + await fastify.listen({ port, host }) + t.same(fastify.listeningOrigin, `http://${host}:${port}`) + await fastify.close() +}) + +test('fastify instance should contains listeningOrigin property (with port and https)', async t => { + t.plan(1) + const port = 3000 + const host = '127.0.0.1' + const fastify = Fastify({ https: {} }) + await fastify.listen({ port, host }) + t.same(fastify.listeningOrigin, `https://${host}:${port}`) + await fastify.close() +}) + +test('fastify instance should contains listeningOrigin property (no options)', async t => { + t.plan(1) + const fastify = Fastify() + await fastify.listen() + const address = fastify.server.address() + t.same(fastify.listeningOrigin, `http://${address.address}:${address.port}`) + await fastify.close() +}) + +test('fastify instance should contains listeningOrigin property (unix socket)', { skip: os.platform() === 'win32' }, async t => { + const fastify = Fastify() + const path = `fastify.${Date.now()}.sock` + await fastify.listen({ path }) + t.same(fastify.listeningOrigin, path) + await fastify.close() +}) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 2852adb0f88..a44fe041ad4 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -31,6 +31,7 @@ expectType(server.addresses()) expectType(server.getSchema('SchemaId')) expectType(server.printRoutes()) expectType(server.printPlugins()) +expectType(server.listeningOrigin) expectAssignable( server.setErrorHandler(function (error, request, reply) { diff --git a/types/instance.d.ts b/types/instance.d.ts index d926701713f..f7b2ecd7ba3 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -99,7 +99,7 @@ export interface FastifyInstance< prefix: string; version: string; log: Logger; - + listeningOrigin: string; addresses(): AddressInfo[] withTypeProvider(): FastifyInstance; From 8a42b8c8c846427eb984d862c532d80430eb1d5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 May 2023 15:11:33 +0000 Subject: [PATCH 0302/1295] build(deps-dev): Bump @types/node from 18.16.5 to 20.1.0 (#4738) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.16.5 to 20.1.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b4fda03ede0..cd97399ef68 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "@fastify/pre-commit": "^2.0.2", "@sinclair/typebox": "^0.28.9", "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "^18.16.3", + "@types/node": "^20.1.0", "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", "ajv": "^8.12.0", From 07267dab14c1b2e1899dcdac6f27646b6e7578a5 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Wed, 10 May 2023 03:18:43 -0300 Subject: [PATCH 0303/1295] chore: add test-compare action (#4737) --- .github/workflows/test-compare.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/test-compare.yml diff --git a/.github/workflows/test-compare.yml b/.github/workflows/test-compare.yml new file mode 100644 index 00000000000..f3342246198 --- /dev/null +++ b/.github/workflows/test-compare.yml @@ -0,0 +1,18 @@ +name: Test compare +on: + pull_request: + types: [opened, reopened, synchronize, labeled] + +jobs: + run: + if: contains(github.event.pull_request.labels.*.name, 'test-compare') + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Test compare + uses: nearform-actions/github-action-test-compare@v1 + with: + label: test-compare + testCommand: 'npm run test:ci' From a2981d727821c76c63bf2445332b04af17d52780 Mon Sep 17 00:00:00 2001 From: Riccardo Romoli <26823400+Ceereals@users.noreply.github.com> Date: Wed, 10 May 2023 08:20:41 +0200 Subject: [PATCH 0304/1295] feat: support TypedArray objects (#4715) (#4735) * add support for TypedArray in Reply.send * add TypedArray section to Reply.send doc * fix typedarray test to use sget * fix TypedArray doc section * fix code duplication * fix test to use fastify inject --- docs/Reference/Reply.md | 14 ++++++++ lib/reply.js | 5 +-- test/internals/reply.test.js | 69 ++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 3ad8b942b33..7ce8274e38d 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -708,6 +708,20 @@ fastify.get('/streams', async function (request, reply) { return reply }) ``` + +#### TypedArrays + + +`send` manages TypedArray and sets the `'Content-Type'=application/octet-stream'` +header if not already set. +```js +const fs = require('fs') +fastify.get('/streams', function (request, reply) { + const typedArray = new Uint16Array(10) + reply.send(typedArray) +}) +``` + #### Errors diff --git a/lib/reply.js b/lib/reply.js index 814cfb38347..2068957bff5 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -148,11 +148,12 @@ Reply.prototype.send = function (payload) { return this } - if (Buffer.isBuffer(payload)) { + if (payload?.buffer instanceof ArrayBuffer) { if (hasContentType === false) { this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET } - onSendHook(this, payload) + const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer) + onSendHook(this, payloadToSend) return this } diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index d1754b63d4a..a1378c5703e 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -454,7 +454,76 @@ test('buffer without content type should send a application/octet-stream and raw }) }) }) +test('Uint8Array without content type should send a application/octet-stream and raw buffer', t => { + t.plan(4) + + const fastify = require('../..')() + fastify.get('/', function (req, reply) { + reply.send(new Uint8Array(1024).fill(0xff)) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(fastify.close.bind(fastify)) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, response) => { + t.error(err) + t.equal(response.headers['content-type'], 'application/octet-stream') + t.same(new Uint8Array(response.rawPayload), new Uint8Array(1024).fill(0xff)) + }) + }) +}) +test('Uint16Array without content type should send a application/octet-stream and raw buffer', t => { + t.plan(4) + + const fastify = require('../..')() + + fastify.get('/', function (req, reply) { + reply.send(new Uint16Array(50).fill(0xffffffff)) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(fastify.close.bind(fastify)) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + t.equal(res.headers['content-type'], 'application/octet-stream') + t.same(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(50).fill(0xffffffff)) + }) + }) +}) +test('TypedArray with content type should not send application/octet-stream', t => { + t.plan(4) + + const fastify = require('../..')() + + fastify.get('/', function (req, reply) { + reply.header('Content-Type', 'text/plain') + reply.send(new Uint16Array(1024).fill(0xffffffff)) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(fastify.close.bind(fastify)) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + t.equal(res.headers['content-type'], 'text/plain') + t.same(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(1024).fill(0xffffffff)) + }) + }) +}) test('buffer with content type should not send application/octet-stream', t => { t.plan(4) From a44e4dc60ecbff3b432fef2d15a6c1e40dab6913 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Fri, 12 May 2023 12:51:40 -0300 Subject: [PATCH 0305/1295] doc: remove RafaelGSS from plugins team (#4746) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index ddc2d03fe6e..47e47982778 100644 --- a/README.md +++ b/README.md @@ -344,8 +344,6 @@ listed in alphabetical order. * [__Frazer Smith__](https://github.com/Fdawgs), * [__Manuel Spigolon__](https://github.com/eomm), , -* [__Rafael Gonzaga__](https://github.com/rafaelgss), - , * [__Simone Busoli__](https://github.com/simoneb), , From 42685f4796a4decb51952059d0fc01b5990e7280 Mon Sep 17 00:00:00 2001 From: "Habibur Rahman (HR)" <113028607+devhabib429@users.noreply.github.com> Date: Fri, 12 May 2023 22:37:26 +0530 Subject: [PATCH 0306/1295] Typo correction from extendible to extensible (#4745) Co-authored-by: Habibur --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47e47982778..228126800fb 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ changes should be based on **`branch 1.x`**. In a similar way, all Fastify - **Highly performant:** as far as we know, Fastify is one of the fastest web frameworks in town, depending on the code complexity we can serve up to 76+ thousand requests per second. -- **Extendible:** Fastify is fully extensible via its hooks, plugins and +- **Extensible:** Fastify is fully extensible via its hooks, plugins and decorators. - **Schema based:** even if it is not mandatory we recommend to use [JSON Schema](https://json-schema.org/) to validate your routes and serialize your From c4cb3a87cdbd33524556314341aa2b9bea61422f Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sun, 14 May 2023 14:27:10 +0200 Subject: [PATCH 0307/1295] feat: allow lowercase methods when registering routes (#4750) * allow lowercase methods * fix test --- docs/Reference/Errors.md | 5 + lib/errors.js | 5 + lib/route.js | 17 ++- test/route.test.js | 266 ++++++++++++++++++++++++++++----------- 4 files changed, 216 insertions(+), 77 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 0aadb14058e..a18b441628c 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -429,6 +429,11 @@ Handler for the route must be a function. Missing handler function for the route. +#### FST_ERR_ROUTE_METHOD_INVALID + + +Method is not a valid value. + #### FST_ERR_ROUTE_METHOD_NOT_SUPPORTED diff --git a/lib/errors.js b/lib/errors.js index cf303751a34..9b78792b3b5 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -336,6 +336,11 @@ const codes = { 'Missing handler function for "%s:%s" route.', 500 ), + FST_ERR_ROUTE_METHOD_INVALID: createError( + 'FST_ERR_ROUTE_METHOD_INVALID', + 'Provided method is invalid!', + 500 + ), FST_ERR_ROUTE_METHOD_NOT_SUPPORTED: createError( 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', '%s method is not supported.', diff --git a/lib/route.js b/lib/route.js index 8f6069093d8..a67c75657c4 100644 --- a/lib/route.js +++ b/lib/route.js @@ -27,6 +27,7 @@ const { FST_ERR_ROUTE_HANDLER_NOT_FN, FST_ERR_ROUTE_MISSING_HANDLER, FST_ERR_ROUTE_METHOD_NOT_SUPPORTED, + FST_ERR_ROUTE_METHOD_INVALID, FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED, FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT } = require('./errors') @@ -185,10 +186,12 @@ function buildRouting (options) { if (Array.isArray(opts.method)) { // eslint-disable-next-line no-var for (var i = 0; i < opts.method.length; ++i) { - validateMethodAndSchemaBodyOption(opts.method[i], path, opts.schema) + opts.method[i] = normalizeAndValidateMethod(opts.method[i]) + validateSchemaBodyOption(opts.method[i], path, opts.schema) } } else { - validateMethodAndSchemaBodyOption(opts.method, path, opts.schema) + opts.method = normalizeAndValidateMethod(opts.method) + validateSchemaBodyOption(opts.method, path, opts.schema) } if (!opts.handler) { @@ -526,11 +529,19 @@ function handleTimeout () { ) } -function validateMethodAndSchemaBodyOption (method, path, schema) { +function normalizeAndValidateMethod (method) { + if (typeof method !== 'string') { + throw new FST_ERR_ROUTE_METHOD_INVALID() + } + method = method.toUpperCase() if (supportedMethods.indexOf(method) === -1) { throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method) } + return method +} + +function validateSchemaBodyOption (method, path, schema) { if ((method === 'GET' || method === 'HEAD') && schema && schema.body) { throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path) } diff --git a/test/route.test.js b/test/route.test.js index 2e44fb511d0..6f403593b17 100644 --- a/test/route.test.js +++ b/test/route.test.js @@ -8,7 +8,11 @@ const sget = require('simple-get').concat const joi = require('joi') const Fastify = require('..') const proxyquire = require('proxyquire') -const { FST_ERR_INVALID_URL } = require('../lib/errors') +const { + FST_ERR_INVALID_URL, + FST_ERR_INSTANCE_ALREADY_LISTENING, + FST_ERR_ROUTE_METHOD_INVALID +} = require('../lib/errors') function getUrl (app) { const { address, port } = app.server.address() @@ -20,13 +24,14 @@ function getUrl (app) { } test('route', t => { - t.plan(9) + t.plan(10) const test = t.test - const fastify = Fastify() test('route - get', t => { - t.plan(1) - try { + t.plan(4) + + const fastify = Fastify() + t.doesNotThrow(() => fastify.route({ method: 'GET', url: '/', @@ -46,15 +51,27 @@ test('route', t => { reply.send({ hello: 'world' }) } }) - t.pass() - } catch (e) { - t.fail() - } + ) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getUrl(fastify) + '/' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) }) test('missing schema - route', t => { - t.plan(1) - try { + t.plan(4) + + const fastify = Fastify() + t.doesNotThrow(() => fastify.route({ method: 'GET', url: '/missing', @@ -62,106 +79,188 @@ test('route', t => { reply.send({ hello: 'world' }) } }) - t.pass() - } catch (e) { - t.fail() - } + ) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getUrl(fastify) + '/missing' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) }) test('invalid handler attribute - route', t => { t.plan(1) - try { - fastify.get('/', { handler: 'not a function' }, () => {}) - t.fail() - } catch (e) { - t.pass() - } + + const fastify = Fastify() + t.throws(() => fastify.get('/', { handler: 'not a function' }, () => { })) }) - test('Multiple methods', t => { - t.plan(1) - try { + test('Add Multiple methods per route all uppercase', t => { + t.plan(7) + + const fastify = Fastify() + t.doesNotThrow(() => fastify.route({ method: ['GET', 'DELETE'], url: '/multiple', handler: function (req, reply) { reply.send({ hello: 'world' }) } + })) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) }) - t.pass() - } catch (e) { - t.fail() - } - }) - test('Add multiple methods', t => { - t.plan(1) - try { - fastify.get('/add-multiple', function (req, reply) { - reply.send({ hello: 'Bob!' }) + sget({ + method: 'DELETE', + url: getUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) }) + }) + }) + + test('Add Multiple methods per route all lowercase', t => { + t.plan(7) + + const fastify = Fastify() + t.doesNotThrow(() => fastify.route({ - method: ['PUT', 'DELETE'], - url: '/add-multiple', + method: ['get', 'delete'], + url: '/multiple', handler: function (req, reply) { reply.send({ hello: 'world' }) } + })) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) }) - t.pass() - } catch (e) { - t.fail() - } + + sget({ + method: 'DELETE', + url: getUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + test('Add Multiple methods per route mixed uppercase and lowercase', t => { + t.plan(7) - test('cannot add another route after binding', t => { - t.plan(1) - try { - fastify.route({ - method: 'GET', - url: '/another-get-route', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }) - t.fail() - } catch (e) { - t.pass() - } - }) + const fastify = Fastify() + t.doesNotThrow(() => + fastify.route({ + method: ['GET', 'delete'], + url: '/multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + })) - test('route - get', t => { - t.plan(3) + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: getUrl(fastify) + '/multiple' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) t.same(JSON.parse(body), { hello: 'world' }) }) - }) - test('route - missing schema', t => { - t.plan(3) sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/missing' + method: 'DELETE', + url: getUrl(fastify) + '/multiple' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) t.same(JSON.parse(body), { hello: 'world' }) }) }) + }) + + test('Add invalid Multiple methods per route', t => { + t.plan(1) + + const fastify = Fastify() + t.throws(() => + fastify.route({ + method: ['GET', 1], + url: '/invalid-method', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_ROUTE_METHOD_INVALID()) + }) + + test('Add method', t => { + t.plan(1) - test('route - multiple methods', t => { - t.plan(6) + const fastify = Fastify() + t.throws(() => + fastify.route({ + method: 1, + url: '/invalid-method', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_ROUTE_METHOD_INVALID()) + }) + + test('Add additional multiple methods to existing route', t => { + t.plan(7) + + const fastify = Fastify() + t.doesNotThrow(() => { + fastify.get('/add-multiple', function (req, reply) { + reply.send({ hello: 'Bob!' }) + }) + fastify.route({ + method: ['PUT', 'DELETE'], + url: '/add-multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/multiple' + method: 'PUT', + url: getUrl(fastify) + '/add-multiple' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -170,7 +269,7 @@ test('route', t => { sget({ method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + '/multiple' + url: getUrl(fastify) + '/add-multiple' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -178,6 +277,25 @@ test('route', t => { }) }) }) + + test('cannot add another route after binding', t => { + t.plan(1) + + const fastify = Fastify() + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + + t.throws(() => fastify.route({ + method: 'GET', + url: '/another-get-route', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_INSTANCE_ALREADY_LISTENING('Cannot add route!')) + }) + }) }) test('invalid schema - route', t => { @@ -185,7 +303,7 @@ test('invalid schema - route', t => { const fastify = Fastify() fastify.route({ - handler: () => {}, + handler: () => { }, method: 'GET', url: '/invalid', schema: { @@ -207,7 +325,7 @@ test('same route definition object on multiple prefixes', async t => { t.plan(2) const routeObject = { - handler: () => {}, + handler: () => { }, method: 'GET', url: '/simple' } @@ -1459,7 +1577,7 @@ test('invalid url attribute - non string URL', t => { const fastify = Fastify() try { - fastify.get(/^\/(donations|skills|blogs)/, () => {}) + fastify.get(/^\/(donations|skills|blogs)/, () => { }) } catch (error) { t.equal(error.code, FST_ERR_INVALID_URL().code) } From e70b632fbe8d3a81399334ef9699a794928b2c26 Mon Sep 17 00:00:00 2001 From: Matthias Keckl <53833818+matthyk@users.noreply.github.com> Date: Wed, 17 May 2023 15:11:01 +0200 Subject: [PATCH 0308/1295] Fix lowercase HTTP methods type (#4757) * fix identation * fix lowercase http method types --- test/types/route.test-d.ts | 12 ++++++++++++ types/utils.d.ts | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 53bb0bb4d0f..569341d15fd 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -342,3 +342,15 @@ expectType(fastify().hasRoute({ method: 'GET', constraints: { version: '1.2.0' } })) + +expectType(fastify().route({ + url: '/', + method: 'get', + handler: routeHandlerWithReturnValue +})) + +expectType(fastify().route({ + url: '/', + method: ['put', 'patch'], + handler: routeHandlerWithReturnValue +})) diff --git a/types/utils.d.ts b/types/utils.d.ts index cb0740c1267..425d933152c 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -5,9 +5,11 @@ import * as https from 'https' /** * Standard HTTP method strings */ -export type HTTPMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS' | +type _HTTPMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS' | 'PROPFIND' | 'PROPPATCH' | 'MKCOL' | 'COPY' | 'MOVE' | 'LOCK' | 'UNLOCK' | 'TRACE' | 'SEARCH' +export type HTTPMethods = Uppercase<_HTTPMethods> | Lowercase<_HTTPMethods> + /** * A union type of the Node.js server types from the http, https, and http2 modules. */ From e82f4a7d463e49c5b9b33011a590979e872c4571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ate=C5=9F=20G=C3=B6ral?= Date: Thu, 18 May 2023 07:48:43 -0400 Subject: [PATCH 0309/1295] feat(ts): add trailer method definitions (#4759) --- test/types/reply.test-d.ts | 3 +++ types/reply.d.ts | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 6d2dfb3dcd1..9d6702c7fe2 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -33,6 +33,9 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<(fn: (payload: any) => string) => FastifyReply>(reply.serializer) expectType<(payload: any) => string | ArrayBuffer | Buffer>(reply.serialize) expectType<(fulfilled: () => void, rejected: (err: Error) => void) => void>(reply.then) + expectType<(key: string, fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise) | ((reply: FastifyReply, payload: string | Buffer | null, done: (err: Error | null, value?: string) => void) => void)) => FastifyReply>(reply.trailer) + expectType<(key: string) => boolean>(reply.hasTrailer) + expectType<(key: string) => FastifyReply>(reply.removeTrailer) expectType(reply.server) expectAssignable<((httpStatus: string) => DefaultSerializationFunction)>(reply.getSerializationFunction) expectAssignable<((schema: {[key: string]: unknown}) => DefaultSerializationFunction)>(reply.getSerializationFunction) diff --git a/types/reply.d.ts b/types/reply.d.ts index 11ea23d0b9a..2df025b36d7 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -61,4 +61,10 @@ export interface FastifyReply< serializeInput(input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string, contentType?: string): string; serializeInput(input: {[key: string]: unknown}, httpStatus: string, contentType?: string): unknown; then(fulfilled: () => void, rejected: (err: Error) => void): void; + trailer: ( + key: string, + fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise) | ((reply: FastifyReply, payload: string | Buffer | null, done: (err: Error | null, value?: string) => void) => void) + ) => FastifyReply; + hasTrailer(key: string): boolean; + removeTrailer(key: string): FastifyReply; } From ca75d4c866c3140f49f768c4bb0cc3b3ece56cac Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 21 May 2023 16:17:42 +0200 Subject: [PATCH 0310/1295] fix: benchmark:parser script (#4765) --- examples/benchmark/body.json | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 examples/benchmark/body.json diff --git a/examples/benchmark/body.json b/examples/benchmark/body.json new file mode 100644 index 00000000000..eeedd400762 --- /dev/null +++ b/examples/benchmark/body.json @@ -0,0 +1,3 @@ +{ + "hello": "world" +} \ No newline at end of file diff --git a/package.json b/package.json index cd97399ef68..a8d36d72d90 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"", "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"", - "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -b \"{\"hello\":\"world\"}\" -H \"content-type=application/jsoff\" -m POST localhost:3000/\"", + "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "coverage": "npm run unit -- --cov --coverage-report=html", "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage -R terse", From 410957f7e6018d4d3b07989c985df8b5af68ca3a Mon Sep 17 00:00:00 2001 From: Brett Willis Date: Tue, 23 May 2023 23:48:35 +1200 Subject: [PATCH 0311/1295] Remove debug logging for URL rewrite (#4754) * Remove debug logging for URL rewrite * Use url rewrite params object for future compatibility Amend test to ensure logger param is present * Change rewriteUrl params to 'this' * Call rewriteUrl with this bound to the Fastify instance * Improve docs for rewriteUrl Co-authored-by: James Sumners --------- Co-authored-by: James Sumners --- docs/Reference/Server.md | 12 ++++++++++-- fastify.d.ts | 7 ++++++- fastify.js | 16 ++++++---------- test/types/fastify.test-d.ts | 5 ++++- test/url-rewriting.test.js | 3 +++ 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 4b465244fc3..20c3375abe7 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -829,8 +829,16 @@ URLs. > Rewriting a URL will modify the `url` property of the `req` object ```js -function rewriteUrl (req) { // req is the Node.js HTTP request - return req.url === '/hi' ? '/hello' : req.url; +// @param {object} req The raw Node.js HTTP request, not the `FastifyRequest` object. +// @this Fastify The root Fastify instance (not an encapsulated instance). +// @returns {string} The path that the request should be mapped to. +function rewriteUrl (req) { + if (req.url === '/hi') { + this.log.debug({ originalUrl: req.url, url: '/hello' }, 'rewrite url'); + return '/hello' + } else { + return req.url; + } } ``` diff --git a/fastify.d.ts b/fastify.d.ts index 497350dfd57..5f261620d15 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -148,7 +148,12 @@ declare namespace fastify { req: FastifyRequest, FastifySchema, TypeProvider>, res: FastifyReply, RawReplyDefaultExpression, RequestGeneric, FastifyContextConfig, SchemaCompiler, TypeProvider> ) => void, - rewriteUrl?: (req: RawRequestDefaultExpression) => string, + rewriteUrl?: ( + // The RawRequestDefaultExpression, RawReplyDefaultExpression, and FastifyTypeProviderDefault parameters + // should be narrowed further but those generic parameters are not passed to this FastifyServerOptions type + this: FastifyInstance, RawReplyDefaultExpression, Logger, FastifyTypeProviderDefault>, + req: RawRequestDefaultExpression + ) => string, schemaErrorFormatter?: SchemaErrorFormatter, /** * listener to error events emitted by client connections diff --git a/fastify.js b/fastify.js index ae71511bb23..0b96ea11c01 100644 --- a/fastify.js +++ b/fastify.js @@ -794,16 +794,12 @@ function fastify (options) { // only call isAsyncConstraint once if (isAsync === undefined) isAsync = router.isAsyncConstraint() if (rewriteUrl) { - const originalUrl = req.url - const url = rewriteUrl(req) - if (originalUrl !== url) { - logger.debug({ originalUrl, url }, 'rewrite url') - if (typeof url === 'string') { - req.url = url - } else { - const err = new FST_ERR_ROUTE_REWRITE_NOT_STR(req.url, typeof url) - req.destroy(err) - } + const url = rewriteUrl.call(fastify, req) + if (typeof url === 'string') { + req.url = url + } else { + const err = new FST_ERR_ROUTE_REWRITE_NOT_STR(req.url, typeof url) + req.destroy(err) } } router.routing(req, res, buildAsyncConstraintCallback(isAsync, req, res)) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 813e77f5175..ca99d902c8c 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -198,7 +198,10 @@ expectAssignable(fastify({ })) expectAssignable(fastify({ frameworkErrors: () => { } })) expectAssignable(fastify({ - rewriteUrl: (req) => req.url === '/hi' ? '/hello' : req.url! + rewriteUrl: function (req) { + this.log.debug('rewrite url') + return req.url === '/hi' ? '/hello' : req.url! + } })) expectAssignable(fastify({ schemaErrorFormatter: (errors, dataVar) => { diff --git a/test/url-rewriting.test.js b/test/url-rewriting.test.js index f786364d515..075ae953983 100644 --- a/test/url-rewriting.test.js +++ b/test/url-rewriting.test.js @@ -10,6 +10,7 @@ test('Should rewrite url', t => { const fastify = Fastify({ rewriteUrl (req) { t.equal(req.url, '/this-would-404-without-url-rewrite') + this.log.info('rewriting url') return '/' } }) @@ -43,6 +44,7 @@ test('Should not rewrite if the url is the same', t => { const fastify = Fastify({ rewriteUrl (req) { t.equal(req.url, '/this-would-404-without-url-rewrite') + this.log.info('rewriting url') return req.url } }) @@ -74,6 +76,7 @@ test('Should throw an error', t => { const fastify = Fastify({ rewriteUrl (req) { t.equal(req.url, '/this-would-404-without-url-rewrite') + this.log.info('rewriting url') return undefined } }) From 4f367eabcdd70be5e969883c44290a83e8d5f18e Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 25 May 2023 20:37:57 +0200 Subject: [PATCH 0312/1295] chore: pin json-schema-to-ts (#4770) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8d36d72d90..971f104c7a4 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "h2url": "^0.2.0", "http-errors": "^2.0.0", "joi": "^17.9.2", - "json-schema-to-ts": "^2.8.0", + "json-schema-to-ts": "2.8.2", "JSONStream": "^1.3.5", "license-checker": "^25.0.1", "markdownlint-cli2": "^0.7.1", From a649133ebd60c59b284dcd92834aa20fa5d2a8e1 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 26 May 2023 08:33:00 +0200 Subject: [PATCH 0313/1295] feat: async validation support (#4752) --- .../Reference/Validation-and-Serialization.md | 5 +- lib/handleRequest.js | 19 +- lib/validation.js | 114 +++- test/schema-special-usage.test.js | 569 ++++++++++++++++++ 4 files changed, 685 insertions(+), 22 deletions(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index ab1f5c28803..17c2ed2d7a9 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -19,8 +19,9 @@ All the examples in this section are using the [JSON Schema Draft > use with user-provided schemas. See [Ajv](https://npm.im/ajv) and > [fast-json-stringify](https://npm.im/fast-json-stringify) for more details. > -> Moreover, the [`$async` Ajv -> feature](https://ajv.js.org/guide/async-validation.html) should not be used as +> Regardless the [`$async` Ajv +> feature](https://ajv.js.org/guide/async-validation.html) is supported +> by Fastify, it should not be used as > part of the first validation strategy. This option is used to access Databases > and reading them during the validation process may lead to Denial of Service > Attacks to your application. If you need to run `async` tasks, use [Fastify's diff --git a/lib/handleRequest.js b/lib/handleRequest.js index e74c3ae53f6..84c9f640aa0 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -89,14 +89,25 @@ function preValidationCallback (err, request, reply) { return } - const result = validateSchema(reply[kRouteContext], request) - if (result) { + const validationErr = validateSchema(reply[kRouteContext], request) + const isAsync = (validationErr && typeof validationErr.then === 'function') || false + + if (isAsync) { + const cb = validationCompleted.bind(null, request, reply) + validationErr.then(cb, cb) + } else { + validationCompleted(request, reply, validationErr) + } +} + +function validationCompleted (request, reply, validationErr) { + if (validationErr) { if (reply[kRouteContext].attachValidation === false) { - reply.send(result) + reply.send(validationErr) return } - reply.request.validationError = result + reply.request.validationError = validationErr } // preHandler hook diff --git a/lib/validation.js b/lib/validation.js index 02540c33ac5..d5b12efc4f1 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -108,31 +108,113 @@ function compileSchemasForValidation (context, compile, isCustom) { function validateParam (validatorFunction, request, paramName) { const isUndefined = request[paramName] === undefined const ret = validatorFunction && validatorFunction(isUndefined ? null : request[paramName]) - if (ret === false) return validatorFunction.errors - if (ret && ret.error) return ret.error - if (ret && ret.value) request[paramName] = ret.value - return false + + if (ret?.then) { + return ret + .then((res) => { return answer(res) }) + .catch(err => { return err }) // return as simple error (not throw) + } + + return answer(ret) + + function answer (ret) { + if (ret === false) return validatorFunction.errors + if (ret && ret.error) return ret.error + if (ret && ret.value) request[paramName] = ret.value + return false + } } -function validate (context, request) { - const params = validateParam(context[paramsSchema], request, 'params') +function validate (context, request, execution) { + const runExecution = execution === undefined - if (params) { - return wrapValidationError(params, 'params', context.schemaErrorFormatter) + if (runExecution || !execution.skipParams) { + const params = validateParam(context[paramsSchema], request, 'params') + if (params) { + if (typeof params.then !== 'function') { + return wrapValidationError(params, 'params', context.schemaErrorFormatter) + } else { + return validateAsyncParams(params, context, request) + } + } } - const body = validateParam(context[bodySchema], request, 'body') - if (body) { - return wrapValidationError(body, 'body', context.schemaErrorFormatter) + + if (runExecution || !execution.skipBody) { + const body = validateParam(context[bodySchema], request, 'body') + if (body) { + if (typeof body.then !== 'function') { + return wrapValidationError(body, 'body', context.schemaErrorFormatter) + } else { + return validateAsyncBody(body, context, request) + } + } } - const query = validateParam(context[querystringSchema], request, 'query') - if (query) { - return wrapValidationError(query, 'querystring', context.schemaErrorFormatter) + + if (runExecution || !execution.skipQuery) { + const query = validateParam(context[querystringSchema], request, 'query') + if (query) { + if (typeof query.then !== 'function') { + return wrapValidationError(query, 'querystring', context.schemaErrorFormatter) + } else { + return validateAsyncQuery(query, context, request) + } + } } + const headers = validateParam(context[headersSchema], request, 'headers') if (headers) { - return wrapValidationError(headers, 'headers', context.schemaErrorFormatter) + if (typeof headers.then !== 'function') { + return wrapValidationError(headers, 'headers', context.schemaErrorFormatter) + } else { + return validateAsyncHeaders(headers, context, request) + } } - return null + + return false +} + +function validateAsyncParams (validatePromise, context, request) { + return validatePromise + .then((paramsResult) => { + if (paramsResult) { + return wrapValidationError(paramsResult, 'params', context.schemaErrorFormatter) + } + + return validate(context, request, { skipParams: true }) + }) +} + +function validateAsyncBody (validatePromise, context, request) { + return validatePromise + .then((bodyResult) => { + if (bodyResult) { + return wrapValidationError(bodyResult, 'body', context.schemaErrorFormatter) + } + + return validate(context, request, { skipParams: true, skipBody: true }) + }) +} + +function validateAsyncQuery (validatePromise, context, request) { + return validatePromise + .then((queryResult) => { + if (queryResult) { + return wrapValidationError(queryResult, 'querystring', context.schemaErrorFormatter) + } + + return validate(context, request, { skipParams: true, skipBody: true, skipQuery: true }) + }) +} + +function validateAsyncHeaders (validatePromise, context, request) { + return validatePromise + .then((headersResult) => { + if (headersResult) { + return wrapValidationError(headersResult, 'headers', context.schemaErrorFormatter) + } + + return false + }) } function wrapValidationError (result, dataVar, schemaErrorFormatter) { diff --git a/test/schema-special-usage.test.js b/test/schema-special-usage.test.js index 1fc99dc6184..e71dc053d6c 100644 --- a/test/schema-special-usage.test.js +++ b/test/schema-special-usage.test.js @@ -744,3 +744,572 @@ test('The default schema compilers should not be called when overwritte by the u await fastify.ready() }) + +test('Supports async JOI validation', t => { + t.plan(7) + + const schemaValidator = ({ schema }) => async data => { + const validationResult = await schema.validateAsync(data) + return validationResult + } + + const fastify = Fastify({ + exposeHeadRoutes: false + }) + fastify.setValidatorCompiler(schemaValidator) + + fastify.get('/', { + schema: { + headers: Joi.object({ + 'user-agent': Joi.string().external(async (val) => { + if (val !== 'lightMyRequest') { + throw new Error('Invalid user-agent') + } + + t.equal(val, 'lightMyRequest') + return val + }), + host: Joi.string().required() + }) + } + }, (request, reply) => { + reply.send(request.headers) + }) + + fastify.inject('/', (err, res) => { + t.error(err) + t.equal(res.statusCode, 200) + t.same(res.json(), { + 'user-agent': 'lightMyRequest', + host: 'localhost:80' + }) + }) + + fastify.inject({ + url: '/', + headers: { + 'user-agent': 'invalid' + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 400) + t.same(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'Invalid user-agent (user-agent)' + }) + }) +}) + +test('Supports async AJV validation', t => { + t.plan(12) + + const fastify = Fastify({ + exposeHeadRoutes: false, + ajv: { + customOptions: { + allErrors: true, + keywords: [ + { + keyword: 'idExists', + async: true, + type: 'number', + validate: checkIdExists + } + ] + }, + plugins: [ + [ajvErrors, { singleError: '@@@@' }] + ] + } + }) + + async function checkIdExists (schema, data) { + const res = await Promise.resolve(data) + switch (res) { + case 42: + return true + + case 500: + throw new Error('custom error') + + default: + return false + } + } + + const schema = { + $async: true, + type: 'object', + properties: { + userId: { + type: 'integer', + idExists: { table: 'users' } + }, + postId: { + type: 'integer', + idExists: { table: 'posts' } + } + } + } + + fastify.post('/', { + schema: { + body: schema + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { userId: 99 } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 400) + t.same(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'validation failed' + }) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { userId: 500 } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 400) + t.same(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'custom error' + }) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { userId: 42 } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 200) + t.same(res.json(), { userId: 42 }) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { userId: 42, postId: 19 } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 400) + t.same(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'validation failed' + }) + }) +}) + +test('Check all the async AJV validation paths', t => { + const fastify = Fastify({ + exposeHeadRoutes: false, + ajv: { + customOptions: { + allErrors: true, + keywords: [ + { + keyword: 'idExists', + async: true, + type: 'number', + validate: checkIdExists + } + ] + } + } + }) + + async function checkIdExists (schema, data) { + const res = await Promise.resolve(data) + switch (res) { + case 200: + return true + + default: + return false + } + } + + const schema = { + $async: true, + type: 'object', + properties: { + id: { + type: 'integer', + idExists: { table: 'posts' } + } + } + } + + fastify.post('/:id', { + schema: { + params: schema, + body: schema, + query: schema, + headers: schema + }, + handler (req, reply) { reply.send(req.body) } + }) + + const testCases = [ + { + params: 400, + body: 200, + querystring: 200, + headers: 200, + response: 400 + }, + { + params: 200, + body: 400, + querystring: 200, + headers: 200, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 400, + headers: 200, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 200, + headers: 400, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 200, + headers: 200, + response: 200 + } + ] + t.plan(testCases.length * 2) + testCases.forEach(validate) + + function validate ({ + params, + body, + querystring, + headers, + response + }) { + fastify.inject({ + method: 'POST', + url: `/${params}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, response) + }) + } +}) + +test('Check mixed sync and async AJV validations', t => { + const fastify = Fastify({ + exposeHeadRoutes: false, + ajv: { + customOptions: { + allErrors: true, + keywords: [ + { + keyword: 'idExists', + async: true, + type: 'number', + validate: checkIdExists + } + ] + } + } + }) + + async function checkIdExists (schema, data) { + const res = await Promise.resolve(data) + switch (res) { + case 200: + return true + + default: + return false + } + } + + const schemaSync = { + type: 'object', + properties: { + id: { type: 'integer' } + } + } + + const schemaAsync = { + $async: true, + type: 'object', + properties: { + id: { + type: 'integer', + idExists: { table: 'posts' } + } + } + } + + fastify.post('/queryAsync/:id', { + schema: { + params: schemaSync, + body: schemaSync, + query: schemaAsync, + headers: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/paramsAsync/:id', { + schema: { + params: schemaAsync, + body: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/bodyAsync/:id', { + schema: { + params: schemaAsync, + body: schemaAsync, + query: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/headersSync/:id', { + schema: { + params: schemaSync, + body: schemaSync, + query: schemaAsync, + headers: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/noHeader/:id', { + schema: { + params: schemaSync, + body: schemaSync, + query: schemaAsync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/noBody/:id', { + schema: { + params: schemaSync, + query: schemaAsync, + headers: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + const testCases = [ + { + url: '/queryAsync', + params: 200, + body: 200, + querystring: 200, + headers: 'not a number sync', + response: 400 + }, + { + url: '/paramsAsync', + params: 200, + body: 'not a number sync', + querystring: 200, + headers: 200, + response: 400 + }, + { + url: '/bodyAsync', + params: 200, + body: 200, + querystring: 'not a number sync', + headers: 200, + response: 400 + }, + { + url: '/headersSync', + params: 200, + body: 200, + querystring: 200, + headers: 'not a number sync', + response: 400 + }, + { + url: '/noHeader', + params: 200, + body: 200, + querystring: 200, + headers: 'not a number sync, but not validated', + response: 200 + }, + { + url: '/noBody', + params: 200, + body: 'not a number sync, but not validated', + querystring: 200, + headers: 'not a number sync', + response: 400 + } + ] + t.plan(testCases.length * 2) + testCases.forEach(validate) + + function validate ({ + url, + params, + body, + querystring, + headers, + response + }) { + fastify.inject({ + method: 'POST', + url: `${url}/${params || ''}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, response) + }) + } +}) + +test('Check if hooks and attachValidation work with AJV validations', t => { + const fastify = Fastify({ + exposeHeadRoutes: false, + ajv: { + customOptions: { + allErrors: true, + keywords: [ + { + keyword: 'idExists', + async: true, + type: 'number', + validate: checkIdExists + } + ] + } + } + }) + + async function checkIdExists (schema, data) { + const res = await Promise.resolve(data) + switch (res) { + case 200: + return true + + default: + return false + } + } + + const schemaAsync = { + $async: true, + type: 'object', + properties: { + id: { + type: 'integer', + idExists: { table: 'posts' } + } + } + } + + fastify.post('/:id', { + preHandler: function hook (request, reply, done) { + t.equal(request.validationError.message, 'validation failed') + t.pass('preHandler called') + + reply.code(400).send(request.body) + }, + attachValidation: true, + schema: { + params: schemaAsync, + body: schemaAsync, + query: schemaAsync, + headers: schemaAsync + }, + handler (req, reply) { reply.send(req.body) } + }) + + const testCases = [ + { + params: 200, + body: 200, + querystring: 200, + headers: 400, + response: 400 + }, + { + params: 200, + body: 400, + querystring: 200, + headers: 200, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 400, + headers: 200, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 200, + headers: 400, + response: 400 + } + ] + t.plan(testCases.length * 4) + testCases.forEach(validate) + + function validate ({ + url, + params, + body, + querystring, + headers, + response + }) { + fastify.inject({ + method: 'POST', + url: `/${params}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, response) + }) + } +}) From e352340894f64bfb1a2241176e1ff2c4d99e02dd Mon Sep 17 00:00:00 2001 From: Briscoooe Date: Sat, 27 May 2023 22:30:09 +0100 Subject: [PATCH 0314/1295] docs(ecosystem): add fastify-evervault plugin (#4771) --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 86dbd55645f..2b57c65e130 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -310,6 +310,9 @@ section. - [`fastify-esso`](https://github.com/patrickpissurno/fastify-esso) The easiest authentication plugin for Fastify, with built-in support for Single sign-on (and great documentation). +- [`fastify-evervault`](https://github.com/Briscoooe/fastify-evervault/) Fastify + plugin for instantiating and encapsulating the + [Evervault](https://evervault.com/) client. - [`fastify-explorer`](https://github.com/Eomm/fastify-explorer) Get control of your decorators across all the encapsulated contexts. - [`fastify-favicon`](https://github.com/smartiniOnGitHub/fastify-favicon) From 8540fb48762961ec3bd2ff2d2dfd9cd1e9cc225a Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 28 May 2023 12:09:49 +0200 Subject: [PATCH 0315/1295] revert: hotfix dev dep (#4775) * Revert "chore: pin json-schema-to-ts (#4770)" This reverts commit 4f367eabcdd70be5e969883c44290a83e8d5f18e. * Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 971f104c7a4..2eeeb4b8c62 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "h2url": "^0.2.0", "http-errors": "^2.0.0", "joi": "^17.9.2", - "json-schema-to-ts": "2.8.2", + "json-schema-to-ts": "^2.9.1", "JSONStream": "^1.3.5", "license-checker": "^25.0.1", "markdownlint-cli2": "^0.7.1", From 71e419b6907c7017bf2e04df33fa0a628178908d Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Thu, 1 Jun 2023 16:59:15 +0200 Subject: [PATCH 0316/1295] docs(Request.md): fixed typo in compileValidationSchema function name --- docs/Reference/Request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 1472fd2d0d0..df197fa6762 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -145,7 +145,7 @@ console.log(validate({ foo: 0.5 })) // false console.log(validate.errors) // validation errors ``` -See [.compilaValidationSchema(schema, [httpStatus])](#compilevalidationschema) +See [.compileValidationSchema(schema, [httpStatus])](#compilevalidationschema) for more information on how to compile validation function. ### .compileValidationSchema(schema, [httpPart]) From bccac51fea557ea7f43ed9a4a8b96cb4f4aaf457 Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Fri, 2 Jun 2023 17:43:39 -0500 Subject: [PATCH 0317/1295] Rename types/tsconfig.json to types/tsconfig.eslint.json to avoid hoisting by vscode (#4773) * build: ignore types/tsconfig.json * rename tsconfig.eslint.json * remove blank line --------- Co-authored-by: Uzlopak --- types/.eslintrc.json | 2 +- types/{tsconfig.json => tsconfig.eslint.json} | 0 types/type-provider.d.ts | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) rename types/{tsconfig.json => tsconfig.eslint.json} (100%) diff --git a/types/.eslintrc.json b/types/.eslintrc.json index ad7fba78019..e0f08e65932 100644 --- a/types/.eslintrc.json +++ b/types/.eslintrc.json @@ -11,7 +11,7 @@ "parserOptions": { "ecmaVersion": 6, "sourceType": "module", - "project": "./types/tsconfig.json", + "project": "./types/tsconfig.eslint.json", "createDefaultProgram": true }, "rules": { diff --git a/types/tsconfig.json b/types/tsconfig.eslint.json similarity index 100% rename from types/tsconfig.json rename to types/tsconfig.eslint.json diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 0af41899d30..2d22543e398 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -1,4 +1,3 @@ - import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' From 36e095db673876db96cad6c11d26fe661dadefcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 23:03:35 +0000 Subject: [PATCH 0318/1295] build(deps): Bump lycheeverse/lychee-action from 1.7.0 to 1.8.0 (#4779) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/97189f2c0a3c8b0cb0e704fd4e878af6e5e2b2c5...ec3ed119d4f44ad2673a7232460dc7dff59d2421) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index f8924319a65..6b678589812 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@97189f2c0a3c8b0cb0e704fd4e878af6e5e2b2c5 + uses: lycheeverse/lychee-action@ec3ed119d4f44ad2673a7232460dc7dff59d2421 with: fail: true # As external links behaviour is not predictable, we check only internal links From 13c358dc89da0662b08d77f6fd10a4cee9d3efdc Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Sat, 3 Jun 2023 04:47:32 -0400 Subject: [PATCH 0319/1295] fix: update removeHeader type to be FastifyReply (#4781) Co-authored-by: Uzlopak --- test/types/reply.test-d.ts | 2 +- types/reply.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 9d6702c7fe2..006a8bea8d1 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -23,7 +23,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<(values: {[key: string]: any}) => FastifyReply>(reply.headers) expectType<(key: string) => number | string | string[] | undefined>(reply.getHeader) expectType<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders) - expectType<(key: string) => void>(reply.removeHeader) + expectType<(key: string) => FastifyReply>(reply.removeHeader) expectType<(key: string) => boolean>(reply.hasHeader) expectType<{(statusCode: number, url: string): FastifyReply; (url: string): FastifyReply }>(reply.redirect) expectType<() => FastifyReply>(reply.hijack) diff --git a/types/reply.d.ts b/types/reply.d.ts index 2df025b36d7..c3ab401d70a 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -43,7 +43,7 @@ export interface FastifyReply< // Node's `getHeaders()` can return numbers and arrays, so they're included here as possible types. [key: string]: number | string | string[] | undefined; }; - removeHeader(key: string): void; + removeHeader(key: string): FastifyReply; hasHeader(key: string): boolean; // Note: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param redirect(statusCode: number, url: string): FastifyReply; From d7301f71755d55cf07f8f50c058fd38e01d58191 Mon Sep 17 00:00:00 2001 From: Ekott2006 <84778161+Ekott2006@users.noreply.github.com> Date: Wed, 7 Jun 2023 17:34:58 +0100 Subject: [PATCH 0320/1295] Making Vercel Serverless docs easier to use (#4793) * Making Vercel Serverless easier to use * Markdown lint fixed * Markdown lint and Proper Formatting --- docs/Guides/Serverless.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 1c7b7863161..7f0e996edfc 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -489,10 +489,21 @@ const app = Fastify({ }); // Register your application as a normal plugin. -app.register(import("../src/app")); +app.register(import("../src/app.js")); export default async (req, res) => { await app.ready(); app.server.emit('request', req, res); } ``` + +In `src/app.js` define the plugin. +```js +async function routes (fastify, options) { + fastify.get('/', async (request, reply) => { + return { hello: 'world' } + }) +} + +export default routes; +``` \ No newline at end of file From 1c45ccfaa99674fa5cabafa9e143456e62d31973 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 8 Jun 2023 09:00:31 +0200 Subject: [PATCH 0321/1295] ci: ignore node-14 on windows (#4800) --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2abd0206d06..a5e95a38822 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,10 @@ jobs: matrix: node-version: [14, 16, 18, 19] os: [macos-latest, ubuntu-latest, windows-latest] + exclude: + # excludes node 14 on Windows + - os: windows-latest + node-version: 14 steps: - uses: actions/checkout@v3 From 4031acbe0c65f436ba689e34ec72c633fbde037a Mon Sep 17 00:00:00 2001 From: Sergey Burnevsky Date: Thu, 8 Jun 2023 14:47:56 +0300 Subject: [PATCH 0322/1295] fix: body reader to use statusCode from stream error, if available (#4787) * body reader to use statusCode from stream error, if available (#4785) * another test for non-number statusCode --------- Co-authored-by: Manuel Spigolon --- lib/contentTypeParser.js | 4 +- test/hooks-async.test.js | 154 +++++++++++++++++++++++++++++++++++++++ test/route-hooks.test.js | 38 ++++++++++ 3 files changed, 195 insertions(+), 1 deletion(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 59d46afa3f4..c53214e5ab6 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -248,7 +248,9 @@ function rawBody (request, reply, options, parser, done) { payload.removeListener('error', onEnd) if (err !== undefined) { - err.statusCode = 400 + if (!(typeof err.statusCode === 'number' && err.statusCode >= 400)) { + err.statusCode = 400 + } reply[kReplyIsError] = true reply.code(err.statusCode).send(err) return diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index 343fd5a169f..a992024e155 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -194,6 +194,160 @@ test('preParsing hooks should be able to modify the payload', t => { }) }) +test('preParsing hooks should be able to supply statusCode', t => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable({ + read () { + const error = new Error('kaboom') + error.statusCode = 408 + this.destroy(error) + } + }) + stream.receivedEncodedLength = 20 + return stream + }) + + fastify.addHook('onError', async (req, res, err) => { + t.equal(err.statusCode, 408) + }) + + fastify.post('/', function (request, reply) { + t.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 408) + t.same(JSON.parse(res.payload), { + statusCode: 408, + error: 'Request Timeout', + message: 'kaboom' + }) + }) +}) + +test('preParsing hooks should ignore statusCode 200 in stream error', t => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable({ + read () { + const error = new Error('kaboom') + error.statusCode = 200 + this.destroy(error) + } + }) + stream.receivedEncodedLength = 20 + return stream + }) + + fastify.addHook('onError', async (req, res, err) => { + t.equal(err.statusCode, 400) + }) + + fastify.post('/', function (request, reply) { + t.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 400) + t.same(JSON.parse(res.payload), { + statusCode: 400, + error: 'Bad Request', + message: 'kaboom' + }) + }) +}) + +test('preParsing hooks should ignore non-number statusCode in stream error', t => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable({ + read () { + const error = new Error('kaboom') + error.statusCode = '418' + this.destroy(error) + } + }) + stream.receivedEncodedLength = 20 + return stream + }) + + fastify.addHook('onError', async (req, res, err) => { + t.equal(err.statusCode, 400) + }) + + fastify.post('/', function (request, reply) { + t.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 400) + t.same(JSON.parse(res.payload), { + statusCode: 400, + error: 'Bad Request', + message: 'kaboom' + }) + }) +}) + +test('preParsing hooks should default to statusCode 400 if stream error', t => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable({ + read () { + this.destroy(new Error('kaboom')) + } + }) + stream.receivedEncodedLength = 20 + return stream + }) + + fastify.addHook('onError', async (req, res, err) => { + t.equal(err.statusCode, 400) + }) + + fastify.post('/', function (request, reply) { + t.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 400) + t.same(JSON.parse(res.payload), { + statusCode: 400, + error: 'Bad Request', + message: 'kaboom' + }) + }) +}) + test('preParsing hooks should handle errors', t => { t.plan(3) const fastify = Fastify() diff --git a/test/route-hooks.test.js b/test/route-hooks.test.js index 3b9e7e979ce..aa90f1d8d57 100644 --- a/test/route-hooks.test.js +++ b/test/route-hooks.test.js @@ -498,6 +498,44 @@ test('preParsing option should be able to modify the payload', t => { }) }) +test('preParsing option should be able to supply statusCode', t => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/only', { + preParsing: async (req, reply, payload) => { + const stream = new Readable({ + read () { + const error = new Error('kaboom') + error.statusCode = 408 + this.destroy(error) + } + }) + stream.receivedEncodedLength = 20 + return stream + }, + onError: async (req, res, err) => { + t.equal(err.statusCode, 408) + } + }, (req, reply) => { + t.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/only', + payload: { hello: 'world' } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 408) + t.same(JSON.parse(res.payload), { + statusCode: 408, + error: 'Request Timeout', + message: 'kaboom' + }) + }) +}) + test('onRequest option should be called before preParsing', t => { t.plan(3) const fastify = Fastify() From 27ac8d9ff00de814beeb91b9a007c606eb912d82 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Thu, 8 Jun 2023 10:32:28 -0300 Subject: [PATCH 0323/1295] workflow(benchmark): update node version (#4786) --- .github/workflows/benchmark.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b1ff0c2563b..12006bbdece 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -11,15 +11,15 @@ jobs: permissions: contents: read outputs: - PR-BENCH-14: ${{ steps.benchmark-pr.outputs.BENCH_RESULT14 }} PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }} PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} - MAIN-BENCH-14: ${{ steps.benchmark-main.outputs.BENCH_RESULT14 }} + PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }} MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} + MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} strategy: matrix: - node-version: [14, 16, 18] + node-version: [16, 18, 20] steps: - uses: actions/checkout@v3 with: @@ -70,12 +70,6 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | - **Node**: 14 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-14 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-14 }} - - --- - **Node**: 16 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }} @@ -86,6 +80,12 @@ jobs: **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} + --- + + **Node**: 20 + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} + - uses: actions-ecosystem/action-remove-labels@v1 with: labels: | From f6a0ff3d8b94a118cd9cc92b44e7bfdff7fdf1b9 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Fri, 9 Jun 2023 08:57:39 +0200 Subject: [PATCH 0324/1295] add sync-next workflow (#4801) --- .github/workflows/sync-next.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/sync-next.yml diff --git a/.github/workflows/sync-next.yml b/.github/workflows/sync-next.yml new file mode 100644 index 00000000000..d5be7663bf3 --- /dev/null +++ b/.github/workflows/sync-next.yml @@ -0,0 +1,25 @@ +name: Sync next-branch +on: + workflow_dispatch: + schedule: + - cron: '0 8 * * 1' # every monday at 8 am + +permissions: + pull-requests: write + +jobs: + create-pull-request: + runs-on: ubuntu-latest + steps: + - uses: octokit/request-action@v2.x + id: create-pull-request + with: + route: POST /repos/{owner}/{repo}/pulls + owner: fastify + repo: fastify + title: Sync next-branch + body: Sync next-branch with latest changes of the main-branch + head: main + base: next + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 9f87feaf1819854198c16fc0bfce5ce49013c4c7 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Fri, 9 Jun 2023 12:27:58 +0200 Subject: [PATCH 0325/1295] remove double validation of onReady-hook (#4799) --- fastify.js | 1 - 1 file changed, 1 deletion(-) diff --git a/fastify.js b/fastify.js index 0b96ea11c01..5a1d9a9cb93 100644 --- a/fastify.js +++ b/fastify.js @@ -616,7 +616,6 @@ function fastify (options) { } else if (name === 'onReady') { this[kHooks].add(name, fn) } else if (name === 'onRoute') { - this[kHooks].validate(name, fn) this[kHooks].add(name, fn) } else { this.after((err, done) => { From 196c89dc67b760bbcf73bce6aecfde46e17d9d83 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Fri, 9 Jun 2023 13:53:13 +0100 Subject: [PATCH 0326/1295] ci(ci): replace node 19 with node 20 in test matrices (#4691) * ci(ci): replace node 19 with node 20 in test matrix * fix: options.https do not accept boolean * test: fix missing host header * test: split custom-parser.test.js * ci(package-manager-ci): add node 20 to test matrix * docs(reference/lts): add node 20 to runtime lists * ci(integration): add node 20 to test matrix * docs(reference/lts): add node 20 to package manager runtimes * fix: tests * Skip Node.js v14 on Windows Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * remove terse reporter Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Co-authored-by: KaKa Co-authored-by: Manuel Spigolon Co-authored-by: Manuel Spigolon Co-authored-by: Matteo Collina --- .github/workflows/ci.yml | 4 +- .github/workflows/coverage-nix.yml | 2 + .github/workflows/coverage-win.yml | 2 + .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 +- docs/Reference/LTS.md | 10 +- lib/server.js | 11 +- package.json | 10 +- test/custom-parser.0.test.js | 777 ++++++++++++++++++ ...parser.test.js => custom-parser.1.test.js} | 743 +---------------- test/hooks.test.js | 58 +- test/internals/reply.test.js | 90 +- test/reply-error.test.js | 14 +- test/request-error.test.js | 3 + 14 files changed, 889 insertions(+), 841 deletions(-) create mode 100644 test/custom-parser.0.test.js rename test/{custom-parser.test.js => custom-parser.1.test.js} (57%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5e95a38822..5df9a9c63cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: strategy: matrix: - node-version: [14, 16, 18, 19] + node-version: [14, 16, 18, 20] os: [macos-latest, ubuntu-latest, windows-latest] exclude: # excludes node 14 on Windows @@ -113,6 +113,8 @@ jobs: - name: Run tests run: | npm run test:ci + env: + NODE_OPTIONS=no-network-family-autoselection automerge: if: > diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 9945372d71d..f8245b10c22 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -34,6 +34,8 @@ jobs: - name: Generate coverage report run: | npm run coverage:ci + env: + NODE_OPTIONS=no-network-family-autoselection - uses: actions/upload-artifact@v3 if: ${{ success() }} diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 176b8792aa5..f49e1a3aee7 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -34,6 +34,8 @@ jobs: - name: Generate coverage report run: | npm run coverage:ci + env: + NODE_OPTIONS=no-network-family-autoselection - uses: actions/upload-artifact@v3 if: ${{ success() }} diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 7bfeb05ba62..9fa53622435 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: - node-version: [16, 18] + node-version: [16, 18, 20] os: [ubuntu-latest] pnpm-version: [8] # pnpm@8 does not support Node.js 14 so include it separately diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 6abc2d6ee43..36b97c2c3c8 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [16, 18] + node-version: [16, 18, 20] os: [ubuntu-latest] pnpm-version: [8] # pnpm@8 does not support Node.js 14 so include it separately @@ -51,7 +51,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [14, 16, 18] + node-version: [14, 16, 18, 20] os: [ubuntu-latest] steps: diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 851d6529aca..880b63cd0c6 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -48,7 +48,7 @@ A "month" is defined as 30 consecutive days. | 1.0.0 | 2018-03-06 | 2019-09-01 | 6, 8, 9, 10, 11 | | 2.0.0 | 2019-02-25 | 2021-01-31 | 6, 8, 10, 12, 14 | | 3.0.0 | 2020-07-07 | 2023-06-30 | 10, 12, 14, 16, 18 | -| 4.0.0 | 2022-06-08 | TBD | 14, 16, 18 | +| 4.0.0 | 2022-06-08 | TBD | 14, 16, 18, 20 | ### CI tested operating systems @@ -61,10 +61,10 @@ YAML workflow labels below: | OS | YAML Workflow Label | Package Manager | Node.js | |---------|------------------------|---------------------------|--------------| -| Linux | `ubuntu-latest` | npm | 14,16,18 | -| Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18 | -| Windows | `windows-latest` | npm | 14,16,18 | -| MacOS | `macos-latest` | npm | 14,16,18 | +| Linux | `ubuntu-latest` | npm | 14,16,18,20 | +| Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18,20 | +| Windows | `windows-latest` | npm | 14,16,18,20 | +| MacOS | `macos-latest` | npm | 14,16,18,20 | Using [yarn](https://yarnpkg.com/) might require passing the `--ignore-engines` flag. diff --git a/lib/server.js b/lib/server.js index 66821cd154b..f3cff0a7b2c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -278,19 +278,22 @@ function compileValidateHTTPVersion (options) { function getServerInstance (options, httpHandler) { let server = null + // node@20 do not accepts options as boolean + // we need to provide proper https option + const httpsOptions = options.https === true ? {} : options.https if (options.serverFactory) { server = options.serverFactory(httpHandler, options) } else if (options.http2) { - if (options.https) { - server = http2().createSecureServer(options.https, httpHandler) + if (typeof httpsOptions === 'object') { + server = http2().createSecureServer(httpsOptions, httpHandler) } else { server = http2().createServer(httpHandler) } server.on('session', sessionTimeout(options.http2SessionTimeout)) } else { // this is http1 - if (options.https) { - server = https.createServer(options.https, httpHandler) + if (httpsOptions) { + server = https.createServer(httpsOptions, httpHandler) } else { server = http.createServer(options.http, httpHandler) } diff --git a/package.json b/package.json index 2eeeb4b8c62..100493f8fe0 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"", "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", - "coverage": "npm run unit -- --cov --coverage-report=html", - "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage -R terse", + "coverage": "NODE_OPTIONS=no-network-family-autoselection npm run unit -- --cov --coverage-report=html", + "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage", "coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100", "license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"", "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown", @@ -20,13 +20,13 @@ "lint:standard": "standard | snazzy", "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", - "test": "npm run lint && npm run unit && npm run test:typescript", - "test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript", + "test": "npm run lint && NODE_OPTIONS=no-network-family-autoselection npm run unit && npm run test:typescript", + "test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", "test:typescript": "tsc test/types/import.ts && tsd", "test:watch": "npm run unit -- -w --no-coverage-report -R terse", - "unit": "tap", + "unit": "NODE_OPTIONS=no-network-family-autoselection tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap" }, diff --git a/test/custom-parser.0.test.js b/test/custom-parser.0.test.js new file mode 100644 index 00000000000..d8b10835491 --- /dev/null +++ b/test/custom-parser.0.test.js @@ -0,0 +1,777 @@ +'use strict' + +const fs = require('fs') +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../fastify') + +const jsonParser = require('fast-json-body') + +function plainTextParser (request, callback) { + let body = '' + request.setEncoding('utf8') + request.on('error', onError) + request.on('data', onData) + request.on('end', onEnd) + function onError (err) { + callback(err, null) + } + function onData (chunk) { + body += chunk + } + function onEnd () { + callback(null, body) + } +} + +function getUrl (app) { + const { address, port } = app.server.address() + if (address === '::1') { + return `http://[${address}]:${port}` + } else { + return `http://${address}:${port}` + } +} + +process.removeAllListeners('warning') + +test('contentTypeParser method should exist', t => { + t.plan(1) + const fastify = Fastify() + t.ok(fastify.addContentTypeParser) +}) + +test('contentTypeParser should add a custom parser', t => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.options('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + t.teardown(() => fastify.close()) + + t.test('in POST', t => { + t.plan(3) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + }) + }) + + t.test('in OPTIONS', t => { + t.plan(3) + + sget({ + method: 'OPTIONS', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + }) + }) + }) +}) + +test('contentTypeParser should handle multiple custom parsers', t => { + t.plan(7) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.post('/hello', (req, reply) => { + reply.send(req.body) + }) + + function customParser (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + } + + fastify.addContentTypeParser('application/jsoff', customParser) + fastify.addContentTypeParser('application/ffosj', customParser) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + }) + + sget({ + method: 'POST', + url: getUrl(fastify) + '/hello', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/ffosj' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + }) + }) +}) + +test('contentTypeParser should handle an array of custom contentTypes', t => { + t.plan(7) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.post('/hello', (req, reply) => { + reply.send(req.body) + }) + + function customParser (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + } + + fastify.addContentTypeParser(['application/jsoff', 'application/ffosj'], customParser) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + }) + + sget({ + method: 'POST', + url: getUrl(fastify) + '/hello', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/ffosj' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + }) + }) +}) + +test('contentTypeParser should handle errors', t => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + done(new Error('kaboom!'), {}) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 500) + fastify.close() + }) + }) +}) + +test('contentTypeParser should support encapsulation', t => { + t.plan(6) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addContentTypeParser('application/jsoff', () => {}) + t.ok(instance.hasContentTypeParser('application/jsoff')) + + instance.register((instance, opts, done) => { + instance.addContentTypeParser('application/ffosj', () => {}) + t.ok(instance.hasContentTypeParser('application/jsoff')) + t.ok(instance.hasContentTypeParser('application/ffosj')) + done() + }) + + done() + }) + + fastify.ready(err => { + t.error(err) + t.notOk(fastify.hasContentTypeParser('application/jsoff')) + t.notOk(fastify.hasContentTypeParser('application/ffosj')) + }) +}) + +test('contentTypeParser should support encapsulation, second try', t => { + t.plan(4) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.post('/', (req, reply) => { + reply.send(req.body) + }) + + instance.addContentTypeParser('application/jsoff', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + fastify.close() + }) + }) +}) + +test('contentTypeParser shouldn\'t support request with undefined "Content-Type"', t => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: 'unknown content type!', + headers: { + // 'Content-Type': undefined + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + fastify.close() + }) + }) +}) + +test('the content type should be a string or RegExp', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addContentTypeParser(null, () => {}) + t.fail() + } catch (err) { + t.equal(err.code, 'FST_ERR_CTP_INVALID_TYPE') + t.equal(err.message, 'The content type should be a string or a RegExp') + } +}) + +test('the content type cannot be an empty string', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addContentTypeParser('', () => {}) + t.fail() + } catch (err) { + t.equal(err.code, 'FST_ERR_CTP_EMPTY_TYPE') + t.equal(err.message, 'The content type cannot be an empty string') + } +}) + +test('the content type handler should be a function', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addContentTypeParser('aaa', null) + t.fail() + } catch (err) { + t.equal(err.code, 'FST_ERR_CTP_INVALID_HANDLER') + t.equal(err.message, 'The content type handler should be a function') + } +}) + +test('catch all content type parser', t => { + t.plan(7) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('*', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: 'hello', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'hello') + + sget({ + method: 'POST', + url: getUrl(fastify), + body: 'hello', + headers: { + 'Content-Type': 'very-weird-content-type' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'hello') + fastify.close() + }) + }) + }) +}) + +test('catch all content type parser should not interfere with other conte type parsers', t => { + t.plan(7) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('*', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: 'hello', + headers: { + 'Content-Type': 'very-weird-content-type' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'hello') + fastify.close() + }) + }) + }) +}) + +// Issue 492 https://github.com/fastify/fastify/issues/492 +test('\'*\' catch undefined Content-Type requests', t => { + t.plan(4) + + const fastify = Fastify() + + t.teardown(fastify.close.bind(fastify)) + + fastify.addContentTypeParser('*', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.post('/', (req, res) => { + // Needed to avoid json stringify + res.type('text/plain').send(req.body) + }) + + fastify.listen({ port: 0 }, function (err) { + t.error(err) + + const fileStream = fs.createReadStream(__filename) + + sget({ + method: 'POST', + url: getUrl(fastify) + '/', + body: fileStream + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body + '', fs.readFileSync(__filename).toString()) + }) + }) +}) + +test('cannot add custom parser after binding', t => { + t.plan(2) + + const fastify = Fastify() + + t.teardown(fastify.close.bind(fastify)) + + fastify.post('/', (req, res) => { + res.type('text/plain').send(req.body) + }) + + fastify.listen({ port: 0 }, function (err) { + t.error(err) + + try { + fastify.addContentTypeParser('*', () => {}) + t.fail() + } catch (e) { + t.pass() + } + }) +}) + +test('Can override the default json parser', t => { + t.plan(5) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + t.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.toString(), '{"hello":"world"}') + fastify.close() + }) + }) +}) + +test('Can override the default plain text parser', t => { + t.plan(5) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('text/plain', function (req, payload, done) { + t.ok('called') + plainTextParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.toString(), 'hello world') + fastify.close() + }) + }) +}) + +test('Can override the default json parser in a plugin', t => { + t.plan(5) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addContentTypeParser('application/json', function (req, payload, done) { + t.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + instance.post('/', (req, reply) => { + reply.send(req.body) + }) + + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.toString(), '{"hello":"world"}') + fastify.close() + }) + }) +}) + +test('Can\'t override the json parser multiple times', t => { + t.plan(2) + const fastify = Fastify() + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + try { + fastify.addContentTypeParser('application/json', function (req, payload, done) { + t.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + } catch (err) { + t.equal(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.equal(err.message, 'Content type parser \'application/json\' already present.') + } +}) + +test('Can\'t override the plain text parser multiple times', t => { + t.plan(2) + const fastify = Fastify() + + fastify.addContentTypeParser('text/plain', function (req, payload, done) { + plainTextParser(payload, function (err, body) { + done(err, body) + }) + }) + + try { + fastify.addContentTypeParser('text/plain', function (req, payload, done) { + t.ok('called') + plainTextParser(payload, function (err, body) { + done(err, body) + }) + }) + } catch (err) { + t.equal(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.equal(err.message, 'Content type parser \'text/plain\' already present.') + } +}) + +test('Should get the body as string', t => { + t.plan(6) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { + t.ok('called') + t.ok(typeof body === 'string') + try { + const json = JSON.parse(body) + done(null, json) + } catch (err) { + err.statusCode = 400 + done(err, undefined) + } + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.toString(), '{"hello":"world"}') + fastify.close() + }) + }) +}) + +test('Should return defined body with no custom parser defined and content type = \'text/plain\'', t => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.toString(), 'hello world') + fastify.close() + }) + }) +}) + +test('Should have typeof body object with no custom parser defined, no body defined and content type = \'text/plain\'', t => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getUrl(fastify), + headers: { + 'Content-Type': 'text/plain' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(typeof body, 'object') + fastify.close() + }) + }) +}) diff --git a/test/custom-parser.test.js b/test/custom-parser.1.test.js similarity index 57% rename from test/custom-parser.test.js rename to test/custom-parser.1.test.js index a669c32189e..c1c7935cfeb 100644 --- a/test/custom-parser.test.js +++ b/test/custom-parser.1.test.js @@ -1,10 +1,9 @@ 'use strict' -const fs = require('fs') const t = require('tap') const test = t.test const sget = require('simple-get').concat -const Fastify = require('..') +const Fastify = require('../fastify') const jsonParser = require('fast-json-body') @@ -36,746 +35,6 @@ function getUrl (app) { process.removeAllListeners('warning') -test('contentTypeParser method should exist', t => { - t.plan(1) - const fastify = Fastify() - t.ok(fastify.addContentTypeParser) -}) - -test('contentTypeParser should add a custom parser', t => { - t.plan(3) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.options('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - t.teardown(() => fastify.close()) - - t.test('in POST', t => { - t.plan(3) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - }) - - t.test('in OPTIONS', t => { - t.plan(3) - - sget({ - method: 'OPTIONS', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - }) - }) -}) - -test('contentTypeParser should handle multiple custom parsers', t => { - t.plan(7) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.post('/hello', (req, reply) => { - reply.send(req.body) - }) - - function customParser (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - } - - fastify.addContentTypeParser('application/jsoff', customParser) - fastify.addContentTypeParser('application/ffosj', customParser) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - - sget({ - method: 'POST', - url: getUrl(fastify) + '/hello', - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/ffosj' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - }) -}) - -test('contentTypeParser should handle an array of custom contentTypes', t => { - t.plan(7) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.post('/hello', (req, reply) => { - reply.send(req.body) - }) - - function customParser (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - } - - fastify.addContentTypeParser(['application/jsoff', 'application/ffosj'], customParser) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - - sget({ - method: 'POST', - url: getUrl(fastify) + '/hello', - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/ffosj' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - }) -}) - -test('contentTypeParser should handle errors', t => { - t.plan(3) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { - done(new Error('kaboom!'), {}) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) - fastify.close() - }) - }) -}) - -test('contentTypeParser should support encapsulation', t => { - t.plan(6) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - instance.addContentTypeParser('application/jsoff', () => {}) - t.ok(instance.hasContentTypeParser('application/jsoff')) - - instance.register((instance, opts, done) => { - instance.addContentTypeParser('application/ffosj', () => {}) - t.ok(instance.hasContentTypeParser('application/jsoff')) - t.ok(instance.hasContentTypeParser('application/ffosj')) - done() - }) - - done() - }) - - fastify.ready(err => { - t.error(err) - t.notOk(fastify.hasContentTypeParser('application/jsoff')) - t.notOk(fastify.hasContentTypeParser('application/ffosj')) - }) -}) - -test('contentTypeParser should support encapsulation, second try', t => { - t.plan(4) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - instance.post('/', (req, reply) => { - reply.send(req.body) - }) - - instance.addContentTypeParser('application/jsoff', function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - done() - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - fastify.close() - }) - }) -}) - -test('contentTypeParser shouldn\'t support request with undefined "Content-Type"', t => { - t.plan(3) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'unknown content type!', - headers: { - // 'Content-Type': undefined - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - fastify.close() - }) - }) -}) - -test('the content type should be a string or RegExp', t => { - t.plan(2) - const fastify = Fastify() - - try { - fastify.addContentTypeParser(null, () => {}) - t.fail() - } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_INVALID_TYPE') - t.equal(err.message, 'The content type should be a string or a RegExp') - } -}) - -test('the content type cannot be an empty string', t => { - t.plan(2) - const fastify = Fastify() - - try { - fastify.addContentTypeParser('', () => {}) - t.fail() - } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_EMPTY_TYPE') - t.equal(err.message, 'The content type cannot be an empty string') - } -}) - -test('the content type handler should be a function', t => { - t.plan(2) - const fastify = Fastify() - - try { - fastify.addContentTypeParser('aaa', null) - t.fail() - } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_INVALID_HANDLER') - t.equal(err.message, 'The content type handler should be a function') - } -}) - -test('catch all content type parser', t => { - t.plan(7) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('*', function (req, payload, done) { - let data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'hello', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello') - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'hello', - headers: { - 'Content-Type': 'very-weird-content-type' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello') - fastify.close() - }) - }) - }) -}) - -test('catch all content type parser should not interfere with other conte type parsers', t => { - t.plan(7) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('*', function (req, payload, done) { - let data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data) - }) - }) - - fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'hello', - headers: { - 'Content-Type': 'very-weird-content-type' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello') - fastify.close() - }) - }) - }) -}) - -// Issue 492 https://github.com/fastify/fastify/issues/492 -test('\'*\' catch undefined Content-Type requests', t => { - t.plan(4) - - const fastify = Fastify() - - t.teardown(fastify.close.bind(fastify)) - - fastify.addContentTypeParser('*', function (req, payload, done) { - let data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data) - }) - }) - - fastify.post('/', (req, res) => { - // Needed to avoid json stringify - res.type('text/plain').send(req.body) - }) - - fastify.listen({ port: 0 }, function (err) { - t.error(err) - - const fileStream = fs.createReadStream(__filename) - - sget({ - method: 'POST', - url: getUrl(fastify) + '/', - body: fileStream - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body + '', fs.readFileSync(__filename).toString()) - }) - }) -}) - -test('cannot add custom parser after binding', t => { - t.plan(2) - - const fastify = Fastify() - - t.teardown(fastify.close.bind(fastify)) - - fastify.post('/', (req, res) => { - res.type('text/plain').send(req.body) - }) - - fastify.listen({ port: 0 }, function (err) { - t.error(err) - - try { - fastify.addContentTypeParser('*', () => {}) - t.fail() - } catch (e) { - t.pass() - } - }) -}) - -test('Can override the default json parser', t => { - t.plan(5) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '{"hello":"world"}') - fastify.close() - }) - }) -}) - -test('Can override the default plain text parser', t => { - t.plan(5) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('text/plain', function (req, payload, done) { - t.ok('called') - plainTextParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') - fastify.close() - }) - }) -}) - -test('Can override the default json parser in a plugin', t => { - t.plan(5) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - instance.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - instance.post('/', (req, reply) => { - reply.send(req.body) - }) - - done() - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '{"hello":"world"}') - fastify.close() - }) - }) -}) - -test('Can\'t override the json parser multiple times', t => { - t.plan(2) - const fastify = Fastify() - - fastify.addContentTypeParser('application/json', function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - try { - fastify.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') - t.equal(err.message, 'Content type parser \'application/json\' already present.') - } -}) - -test('Can\'t override the plain text parser multiple times', t => { - t.plan(2) - const fastify = Fastify() - - fastify.addContentTypeParser('text/plain', function (req, payload, done) { - plainTextParser(payload, function (err, body) { - done(err, body) - }) - }) - - try { - fastify.addContentTypeParser('text/plain', function (req, payload, done) { - t.ok('called') - plainTextParser(payload, function (err, body) { - done(err, body) - }) - }) - } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') - t.equal(err.message, 'Content type parser \'text/plain\' already present.') - } -}) - -test('Should get the body as string', t => { - t.plan(6) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { - t.ok('called') - t.ok(typeof body === 'string') - try { - const json = JSON.parse(body) - done(null, json) - } catch (err) { - err.statusCode = 400 - done(err, undefined) - } - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '{"hello":"world"}') - fastify.close() - }) - }) -}) - -test('Should return defined body with no custom parser defined and content type = \'text/plain\'', t => { - t.plan(4) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') - fastify.close() - }) - }) -}) - -test('Should have typeof body object with no custom parser defined, no body defined and content type = \'text/plain\'', t => { - t.plan(4) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(typeof body, 'object') - fastify.close() - }) - }) -}) - test('Should have typeof body object with no custom parser defined, null body and content type = \'text/plain\'', t => { t.plan(4) const fastify = Fastify() diff --git a/test/hooks.test.js b/test/hooks.test.js index 4fc98a0a39f..6666bd8a63e 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -165,7 +165,7 @@ test('hooks', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -175,7 +175,7 @@ test('hooks', t => { sget({ method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 500) @@ -183,7 +183,7 @@ test('hooks', t => { sget({ method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 500) @@ -288,7 +288,7 @@ test('onRequest hook should support encapsulation / 3', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -298,7 +298,7 @@ test('onRequest hook should support encapsulation / 3', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -349,7 +349,7 @@ test('preHandler hook should support encapsulation / 5', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -359,7 +359,7 @@ test('preHandler hook should support encapsulation / 5', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -966,7 +966,7 @@ test('onResponse hook should support encapsulation / 3', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -976,7 +976,7 @@ test('onResponse hook should support encapsulation / 3', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -1043,7 +1043,7 @@ test('onSend hook should support encapsulation / 2', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -1053,7 +1053,7 @@ test('onSend hook should support encapsulation / 2', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -1338,7 +1338,7 @@ test('onSend hook throws', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -1347,21 +1347,21 @@ test('onSend hook throws', t => { }) sget({ method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 500) }) sget({ method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 500) }) sget({ method: 'PUT', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 500) @@ -2476,7 +2476,7 @@ test('preValidation hook should support encapsulation / 3', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -2486,7 +2486,7 @@ test('preValidation hook should support encapsulation / 3', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -2623,7 +2623,7 @@ test('preParsing hook should run before parsing and be able to modify the payloa sget({ method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/first', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first', body: { hello: 'world' }, json: true }, (err, response, body) => { @@ -2666,7 +2666,7 @@ test('preParsing hooks should run in the order in which they are defined', t => sget({ method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/first', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first', body: { hello: 'world' }, json: true }, (err, response, body) => { @@ -2716,7 +2716,7 @@ test('preParsing hooks should support encapsulation', t => { sget({ method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/first', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first', body: { hello: 'world' }, json: true }, (err, response, body) => { @@ -2728,7 +2728,7 @@ test('preParsing hooks should support encapsulation', t => { sget({ method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/second', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/second', body: { hello: 'world' }, json: true }, (err, response, body) => { @@ -2837,7 +2837,7 @@ test('preParsing hook should support encapsulation / 3', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -2847,7 +2847,7 @@ test('preParsing hook should support encapsulation / 3', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -2899,7 +2899,7 @@ test('preSerialization hook should run before serialization and be able to modif sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -2950,7 +2950,7 @@ test('preSerialization hook should be able to throw errors which are validated a sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 500) @@ -2984,7 +2984,7 @@ test('preSerialization hook which returned error should still run onError hooks' sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 500) @@ -3018,7 +3018,7 @@ test('preSerialization hooks should run in the order in which they are defined', sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -3062,7 +3062,7 @@ test('preSerialization hooks should support encapsulation', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -3072,7 +3072,7 @@ test('preSerialization hooks should support encapsulation', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index a1378c5703e..00b6e0ff586 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -280,7 +280,7 @@ test('within an instance', t => { t.plan(3) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/custom-serializer' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/custom-serializer' }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'text/plain') @@ -292,7 +292,7 @@ test('within an instance', t => { t.plan(4) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -305,7 +305,7 @@ test('within an instance', t => { t.plan(3) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/auto-status-code' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/auto-status-code' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -317,7 +317,7 @@ test('within an instance', t => { t.plan(3) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/auto-type' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/auto-type' }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'text/plain') @@ -328,7 +328,7 @@ test('within an instance', t => { test('redirect to `/` - 1', t => { t.plan(1) - http.get('http://localhost:' + fastify.server.address().port + '/redirect', function (response) { + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect', function (response) { t.equal(response.statusCode, 302) }) }) @@ -336,7 +336,7 @@ test('within an instance', t => { test('redirect to `/` - 2', t => { t.plan(1) - http.get('http://localhost:' + fastify.server.address().port + '/redirect-code', function (response) { + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code', function (response) { t.equal(response.statusCode, 301) }) }) @@ -345,7 +345,7 @@ test('within an instance', t => { t.plan(4) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/redirect' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -358,7 +358,7 @@ test('within an instance', t => { t.plan(4) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/redirect-code' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -369,7 +369,7 @@ test('within an instance', t => { test('redirect to `/` - 5', t => { t.plan(3) - const url = 'http://localhost:' + fastify.server.address().port + '/redirect-onsend' + const url = 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-onsend' http.get(url, (response) => { t.equal(response.headers['x-onsend'], 'yes') t.equal(response.headers['content-length'], '0') @@ -381,7 +381,7 @@ test('within an instance', t => { t.plan(4) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/redirect-code-before-call' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -394,7 +394,7 @@ test('within an instance', t => { t.plan(4) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/redirect-code-before-call-overwrite' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call-overwrite' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -406,7 +406,7 @@ test('within an instance', t => { test('redirect to `/` - 8', t => { t.plan(1) - http.get('http://localhost:' + fastify.server.address().port + '/redirect-code-before-call', function (response) { + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call', function (response) { t.equal(response.statusCode, 307) }) }) @@ -414,7 +414,7 @@ test('within an instance', t => { test('redirect to `/` - 9', t => { t.plan(1) - http.get('http://localhost:' + fastify.server.address().port + '/redirect-code-before-call-overwrite', function (response) { + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call-overwrite', function (response) { t.equal(response.statusCode, 302) }) }) @@ -422,7 +422,7 @@ test('within an instance', t => { test('redirect with async function to `/` - 10', t => { t.plan(1) - http.get('http://localhost:' + fastify.server.address().port + '/redirect-async', function (response) { + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-async', function (response) { t.equal(response.statusCode, 302) }) }) @@ -446,7 +446,7 @@ test('buffer without content type should send a application/octet-stream and raw sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'application/octet-stream') @@ -540,7 +540,7 @@ test('buffer with content type should not send application/octet-stream', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'text/plain') @@ -567,7 +567,7 @@ test('stream with content type should not send application/octet-stream', t => { t.teardown(fastify.close.bind(fastify)) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'text/plain') @@ -593,7 +593,7 @@ test('stream without content type should not send application/octet-stream', t = t.teardown(fastify.close.bind(fastify)) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], undefined) @@ -628,7 +628,7 @@ test('stream using reply.raw.writeHead should return customize headers', t => { t.teardown(fastify.close.bind(fastify)) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers.location, '/') @@ -653,7 +653,7 @@ test('plain string without content type should send a text/plain', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'text/plain; charset=utf-8') @@ -677,7 +677,7 @@ test('plain string with content type should be sent unmodified', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'text/css') @@ -704,7 +704,7 @@ test('plain string with content type and custom serializer should be serialized' sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'text/css') @@ -728,7 +728,7 @@ test('plain string with content type application/json should NOT be serialized a sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'application/json; charset=utf-8') @@ -782,7 +782,7 @@ test('plain string with custom json content type should NOT be serialized as jso Object.keys(customSamples).forEach((path) => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/' + path + url: 'http://127.0.0.1:' + fastify.server.address().port + '/' + path }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8') @@ -807,7 +807,7 @@ test('non-string with content type application/json SHOULD be serialized as json sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'application/json; charset=utf-8') @@ -831,7 +831,7 @@ test('non-string with custom json\'s content-type SHOULD be serialized as json', sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], 'application/json; version=2; charset=utf-8') @@ -881,7 +881,7 @@ test('non-string with custom json content type SHOULD be serialized as json', t Object.keys(customSamples).forEach((path) => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/' + path + url: 'http://127.0.0.1:' + fastify.server.address().port + '/' + path }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8') @@ -945,7 +945,7 @@ test('undefined payload should be sent as-is', t => { sget({ method: 'GET', - url: `http://localhost:${fastify.server.address().port}` + url: `http://127.0.0.1:${fastify.server.address().port}` }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], undefined) @@ -990,7 +990,7 @@ test('for HEAD method, no body should be sent but content-length should be', t = sget({ method: 'HEAD', - url: `http://localhost:${fastify.server.address().port}` + url: `http://127.0.0.1:${fastify.server.address().port}` }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], contentType) @@ -1000,7 +1000,7 @@ test('for HEAD method, no body should be sent but content-length should be', t = sget({ method: 'HEAD', - url: `http://localhost:${fastify.server.address().port}/with/null` + url: `http://127.0.0.1:${fastify.server.address().port}/with/null` }, (err, response, body) => { t.error(err) t.equal(response.headers['content-type'], contentType) @@ -1038,7 +1038,7 @@ test('reply.send(new NotFound()) should not invoke the 404 handler', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/not-found' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/not-found' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -1052,7 +1052,7 @@ test('reply.send(new NotFound()) should not invoke the 404 handler', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/prefixed/not-found' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/prefixed/not-found' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -1084,7 +1084,7 @@ test('reply can set multiple instances of same header', t => { sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/headers' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, (err, response, body) => { t.error(err) t.ok(response.headers['set-cookie']) @@ -1110,7 +1110,7 @@ test('reply.hasHeader returns correct values', t => { t.teardown(fastify.close.bind(fastify)) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/headers' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, () => {}) }) }) @@ -1142,7 +1142,7 @@ test('reply.getHeader returns correct values', t => { t.teardown(fastify.close.bind(fastify)) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/headers' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, () => {}) }) }) @@ -1212,7 +1212,7 @@ test('reply.removeHeader can remove the value', t => { t.teardown(fastify.close.bind(fastify)) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/headers' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, () => { t.pass() }) @@ -1239,7 +1239,7 @@ test('reply.header can reset the value', t => { t.teardown(fastify.close.bind(fastify)) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/headers' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, () => { t.pass() }) @@ -1268,7 +1268,7 @@ test('reply.hasHeader computes raw and fastify headers', t => { t.teardown(fastify.close.bind(fastify)) sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/headers' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, () => { t.pass() }) @@ -1438,7 +1438,7 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t = sget({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/headers' + url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, (err, response, body) => { t.error(err) t.ok(response.headers['set-cookie']) @@ -1963,7 +1963,7 @@ test('redirect to an invalid URL should not crash the server', async t => { await fastify.listen({ port: 0 }) { - const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=1`) + const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=1`) t.equal(response.statusCode, 500) t.same(JSON.parse(body), { statusCode: 500, @@ -1973,13 +1973,13 @@ test('redirect to an invalid URL should not crash the server', async t => { }) } { - const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=2`) + const { response } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=2`) t.equal(response.statusCode, 302) t.equal(response.headers.location, '/?key=a%E2%80%99b') } { - const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=3`) + const { response } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=3`) t.equal(response.statusCode, 302) t.equal(response.headers.location, '/?key=ab') } @@ -2008,7 +2008,7 @@ test('invalid response headers should not crash the server', async t => { await fastify.listen({ port: 0 }) - const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`) + const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) t.equal(response.statusCode, 500) t.same(JSON.parse(body), { statusCode: 500, @@ -2037,7 +2037,7 @@ test('invalid response headers when sending back an error', async t => { await fastify.listen({ port: 0 }) - const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`) + const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) t.equal(response.statusCode, 500) t.same(JSON.parse(body), { statusCode: 500, @@ -2071,7 +2071,7 @@ test('invalid response headers and custom error handler', async t => { await fastify.listen({ port: 0 }) - const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`) + const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) t.equal(response.statusCode, 500) t.same(JSON.parse(body), { statusCode: 500, diff --git a/test/reply-error.test.js b/test/reply-error.test.js index c4ee4fb3de1..65b10446548 100644 --- a/test/reply-error.test.js +++ b/test/reply-error.test.js @@ -107,10 +107,11 @@ test('Should reply 400 on client error', t => { t.plan(2) const fastify = Fastify() - fastify.listen({ port: 0 }, err => { + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0, host: '127.0.0.1' }, err => { t.error(err) - const client = net.connect(fastify.server.address().port) + const client = net.connect(fastify.server.address().port, '127.0.0.1') client.end('oooops!') let chunks = '' @@ -125,7 +126,6 @@ test('Should reply 400 on client error', t => { statusCode: 400 }) t.equal(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`, chunks) - fastify.close() }) }) }) @@ -156,10 +156,11 @@ test('Should set the response from client error handler', t => { } }) - fastify.listen({ port: 0 }, err => { + fastify.listen({ port: 0, host: '127.0.0.1' }, err => { t.error(err) + t.teardown(fastify.close.bind(fastify)) - const client = net.connect(fastify.server.address().port) + const client = net.connect(fastify.server.address().port, '127.0.0.1') client.end('oooops!') let chunks = '' @@ -169,7 +170,6 @@ test('Should set the response from client error handler', t => { client.once('end', () => { t.equal(response, chunks) - fastify.close() }) }) @@ -705,7 +705,7 @@ test('setting content-type on reply object should not hang the server case 2', a }) try { - await fastify.listen({ port: 0 }) + await fastify.ready() const res = await fastify.inject({ url: '/', method: 'GET' diff --git a/test/request-error.test.js b/test/request-error.test.js index 9108f9aa4ec..b64b5f59546 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -123,6 +123,7 @@ test('default clientError handler ignores ECONNRESET', t => { client.resume() client.write('GET / HTTP/1.1\r\n') + client.write('Host: example.com\r\n') client.write('Connection: close\r\n') client.write('\r\n\r\n') }) @@ -308,8 +309,10 @@ test('default clientError replies with bad request on reused keep-alive connecti client.resume() client.write('GET / HTTP/1.1\r\n') + client.write('Host: example.com\r\n') client.write('\r\n\r\n') client.write('GET /?a b HTTP/1.1\r\n') + client.write('Host: example.com\r\n') client.write('Connection: close\r\n') client.write('\r\n\r\n') }) From 6ca905af8a2a6da6c648648e1b9fac5206a5f836 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 9 Jun 2023 15:16:21 +0200 Subject: [PATCH 0327/1295] Fix broken CI (#4809) * Fix broken CI Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina --- .github/workflows/ci.yml | 2 +- .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5df9a9c63cf..21cbae700b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: run: | npm run test:ci env: - NODE_OPTIONS=no-network-family-autoselection + NODE_OPTIONS: no-network-family-autoselection automerge: if: > diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index f8245b10c22..e356768049d 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -35,7 +35,7 @@ jobs: run: | npm run coverage:ci env: - NODE_OPTIONS=no-network-family-autoselection + NODE_OPTIONS: no-network-family-autoselection - uses: actions/upload-artifact@v3 if: ${{ success() }} diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index f49e1a3aee7..402ab292568 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -35,7 +35,7 @@ jobs: run: | npm run coverage:ci env: - NODE_OPTIONS=no-network-family-autoselection + NODE_OPTIONS: no-network-family-autoselection - uses: actions/upload-artifact@v3 if: ${{ success() }} diff --git a/package.json b/package.json index 100493f8fe0..c6b94434bbb 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "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", "test:typescript": "tsc test/types/import.ts && tsd", "test:watch": "npm run unit -- -w --no-coverage-report -R terse", - "unit": "NODE_OPTIONS=no-network-family-autoselection tap", + "unit": "tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap" }, From 48ae3a7ee52f21e01a8d4e5f9c6a7357e68e3651 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 9 Jun 2023 15:37:16 +0200 Subject: [PATCH 0328/1295] Disable happy eyeblass for yarn and pnpm Signed-off-by: Matteo Collina --- .github/workflows/package-manager-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 36b97c2c3c8..22bd6f0a0f8 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -44,6 +44,8 @@ jobs: - name: Run tests run: | pnpm run test:ci + env: + NODE_OPTIONS: no-network-family-autoselection yarn: runs-on: ${{ matrix.os }} @@ -74,3 +76,5 @@ jobs: - name: Run tests run: | yarn run test:ci + env: + NODE_OPTIONS: no-network-family-autoselection From 96068f9bc358983f62acddd1b7e4bbf6e9aeeea9 Mon Sep 17 00:00:00 2001 From: Colin Ihrig Date: Fri, 9 Jun 2023 10:06:00 -0400 Subject: [PATCH 0329/1295] reset listening state for each secondary server (#4810) The listenCallback() function is called for each secondary address that a server should listen on. However, each call sets the listening state to true, so subsequent addresses fail with FST_ERR_REOPENED_SERVER. This commit resets the state on each call. --- lib/server.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/server.js b/lib/server.js index f3cff0a7b2c..9d351179209 100644 --- a/lib/server.js +++ b/lib/server.js @@ -127,6 +127,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o cb: (_ignoreErr) => { binded++ + /* istanbul ignore next: the else won't be taken unless listening fails */ if (!_ignoreErr) { this[kServerBindings].push(secondaryServer) } @@ -144,6 +145,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o mainServer.on('unref', closeSecondary) mainServer.on('close', closeSecondary) mainServer.on('error', closeSecondary) + this[kState].listening = false listenCallback.call(this, secondaryServer, secondaryOpts)() } } From 5d084fb842f6465340f2bf2b1317b3639fe3962a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 9 Jun 2023 19:07:41 +0200 Subject: [PATCH 0330/1295] Bumped v4.18.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 5a1d9a9cb93..d5549f3ab78 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.17.0' +const VERSION = '4.18.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index c6b94434bbb..f7e88eec17b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.17.0", + "version": "4.18.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 3ec2651fbd6ed7eab635c429abfd1e2675360e81 Mon Sep 17 00:00:00 2001 From: aarontravass <43901677+aarontravass@users.noreply.github.com> Date: Fri, 9 Jun 2023 16:56:47 -0400 Subject: [PATCH 0331/1295] fix: added a check to prevent creation of secondary server when external server is supplied (#4741) * fix: added a check to prevent creation of secondary server when external server is passed to serverfactory #4725 * fix: serverFactoryListening now uses the serverfactory if supplied rather than the options * fix: added warning if secondary server is being created and if supplied server is listening, then the first server throws an error. added test cases for the same * Update lib/server.js Co-authored-by: Carlos Fuentes * fix: rewords FST_ERR_DUPLICATE_SERVER to FST_ERR_SERVER_ALREADY_LISTENING and changed the message as well * fix: changed warning label, error message * fix: serverFactory usage does not throw an error and instead avoids creating secondary server --------- Signed-off-by: Matteo Collina Co-authored-by: Carlos Fuentes Co-authored-by: Matteo Collina --- lib/server.js | 62 +++++++++++++------------- test/custom-http-server.test.js | 15 ++++++- test/https/custom-https-server.test.js | 4 +- 3 files changed, 47 insertions(+), 34 deletions(-) diff --git a/lib/server.js b/lib/server.js index 9d351179209..ff1390b1576 100644 --- a/lib/server.js +++ b/lib/server.js @@ -61,7 +61,6 @@ function createServer (options, httpHandler) { if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false) { listenOptions.host = host } - if (host === 'localhost') { listenOptions.cb = (err, address) => { if (err) { @@ -115,41 +114,44 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o return } + const isMainServerListening = mainServer.listening && serverOpts.serverFactory + let binding = 0 let binded = 0 - const primaryAddress = mainServer.address() - for (const adr of addresses) { - if (adr.address !== primaryAddress.address) { - binding++ - const secondaryOpts = Object.assign({}, listenOptions, { - host: adr.address, - port: primaryAddress.port, - cb: (_ignoreErr) => { - binded++ - - /* istanbul ignore next: the else won't be taken unless listening fails */ - if (!_ignoreErr) { - this[kServerBindings].push(secondaryServer) + if (!isMainServerListening) { + const primaryAddress = mainServer.address() + for (const adr of addresses) { + if (adr.address !== primaryAddress.address) { + binding++ + const secondaryOpts = Object.assign({}, listenOptions, { + host: adr.address, + port: primaryAddress.port, + cb: (_ignoreErr) => { + binded++ + + /* istanbul ignore next: the else won't be taken unless listening fails */ + if (!_ignoreErr) { + this[kServerBindings].push(secondaryServer) + } + + if (binded === binding) { + // regardless of the error, we are done + onListen() + } } + }) - if (binded === binding) { - // regardless of the error, we are done - onListen() - } - } - }) - - const secondaryServer = getServerInstance(serverOpts, httpHandler) - const closeSecondary = () => { secondaryServer.close(() => {}) } - secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade')) - mainServer.on('unref', closeSecondary) - mainServer.on('close', closeSecondary) - mainServer.on('error', closeSecondary) - this[kState].listening = false - listenCallback.call(this, secondaryServer, secondaryOpts)() + const secondaryServer = getServerInstance(serverOpts, httpHandler) + const closeSecondary = () => { secondaryServer.close(() => { }) } + secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade')) + mainServer.on('unref', closeSecondary) + mainServer.on('close', closeSecondary) + mainServer.on('error', closeSecondary) + this[kState].listening = false + listenCallback.call(this, secondaryServer, secondaryOpts)() + } } } - // no extra bindings are necessary if (binding === 0) { onListen() diff --git a/test/custom-http-server.test.js b/test/custom-http-server.test.js index 3ad212f3842..4312973d884 100644 --- a/test/custom-http-server.test.js +++ b/test/custom-http-server.test.js @@ -11,10 +11,10 @@ const dns = require('dns').promises test('Should support a custom http server', async t => { const localAddresses = await dns.lookup('localhost', { all: true }) - t.plan(localAddresses.length + 3) + t.plan((localAddresses.length - 1) + 3) const serverFactory = (handler, opts) => { - t.ok(opts.serverFactory, 'it is called twice for every HOST interface') + t.ok(opts.serverFactory, 'it is called once for localhost') const server = http.createServer((req, res) => { req.custom = true @@ -71,3 +71,14 @@ test('Should not allow forceCloseConnection=idle if the server does not support "Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method" ) }) + +test('Should accept user defined serverFactory and ignore secondary server creation', async t => { + const server = http.createServer(() => {}) + t.teardown(() => new Promise(resolve => server.close(resolve))) + const app = await Fastify({ + serverFactory: () => server + }) + t.resolves(async () => { + await app.listen({ port: 0 }) + }) +}) diff --git a/test/https/custom-https-server.test.js b/test/https/custom-https-server.test.js index ea05e639206..84e5443b9a0 100644 --- a/test/https/custom-https-server.test.js +++ b/test/https/custom-https-server.test.js @@ -13,10 +13,10 @@ t.before(buildCertificate) test('Should support a custom https server', async t => { const localAddresses = await dns.lookup('localhost', { all: true }) - t.plan(localAddresses.length + 3) + t.plan((localAddresses.length - 1) + 3) const serverFactory = (handler, opts) => { - t.ok(opts.serverFactory, 'it is called twice for every HOST interface') + t.ok(opts.serverFactory, 'it is called once for localhost') const options = { key: global.context.key, From f4a8f3624c87ab23bec79280b876ad955ac5fafa Mon Sep 17 00:00:00 2001 From: aarontravass <43901677+aarontravass@users.noreply.github.com> Date: Sat, 10 Jun 2023 00:56:59 -0400 Subject: [PATCH 0332/1295] feat: added originalUrl in request object #4704 (#4758) --- docs/Reference/Request.md | 2 ++ fastify.js | 1 + lib/request.js | 12 +++++++++++- lib/route.js | 1 - lib/symbols.js | 1 + test/internals/request.test.js | 1 + test/types/request.test-d.ts | 1 + test/url-rewriting.test.js | 35 ++++++++++++++++++++++++++++++++++ test/versioned-routes.test.js | 2 +- types/request.d.ts | 1 + 10 files changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index df197fa6762..23ac9a3c77c 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -27,6 +27,8 @@ Request is a core Fastify object containing the following fields: - `protocol` - the protocol of the incoming request (`https` or `http`) - `method` - the method of the incoming request - `url` - the URL of the incoming request +- `originalUrl` - similar to `url`, this allows you to access the + original `url` in case of internal re-routing - `routerMethod` - the method defined for the router that is handling the request - `routerPath` - the path pattern defined for the router that is handling the diff --git a/fastify.js b/fastify.js index d5549f3ab78..86efc3f56ac 100644 --- a/fastify.js +++ b/fastify.js @@ -793,6 +793,7 @@ function fastify (options) { // only call isAsyncConstraint once if (isAsync === undefined) isAsync = router.isAsyncConstraint() if (rewriteUrl) { + req.originalUrl = req.url const url = rewriteUrl.call(fastify, req) if (typeof url === 'string') { req.url = url diff --git a/lib/request.js b/lib/request.js index eb95d3ac263..da0de5ba6c1 100644 --- a/lib/request.js +++ b/lib/request.js @@ -13,7 +13,8 @@ const { kOptions, kRequestCacheValidateFns, kRouteContext, - kPublicRouteContext + kPublicRouteContext, + kRequestOriginalUrl } = require('./symbols') const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors') @@ -149,6 +150,15 @@ Object.defineProperties(Request.prototype, { return this.raw.url } }, + originalUrl: { + get () { + /* istanbul ignore else */ + if (!this[kRequestOriginalUrl]) { + this[kRequestOriginalUrl] = this.raw.originalUrl || this.raw.url + } + return this[kRequestOriginalUrl] + } + }, method: { get () { return this.raw.method diff --git a/lib/route.js b/lib/route.js index a67c75657c4..c2433552fb6 100644 --- a/lib/route.js +++ b/lib/route.js @@ -468,7 +468,6 @@ function buildRouting (options) { const request = new context.Request(id, params, req, query, childLogger, context) const reply = new context.Reply(res, request, childLogger) - if (disableRequestLogging === false) { childLogger.info({ req: request }, 'incoming request') } diff --git a/lib/symbols.js b/lib/symbols.js index cdf4db84a4c..c5e2baab413 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -30,6 +30,7 @@ const keys = { kRequestPayloadStream: Symbol('fastify.RequestPayloadStream'), kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'), kRequestCacheValidateFns: Symbol('fastify.request.cache.validateFns'), + kRequestOriginalUrl: Symbol('fastify.request.originalUrl'), // 404 kFourOhFour: Symbol('fastify.404'), kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'), diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 0395749eff1..7936af87fd6 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -60,6 +60,7 @@ test('Regular request', t => { t.equal(request.body, undefined) t.equal(request.method, 'GET') t.equal(request.url, '/') + t.equal(request.originalUrl, '/') t.equal(request.socket, req.socket) t.equal(request.protocol, 'http') t.equal(request.routerPath, context.config.url) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 5f5cd827e41..783080e7676 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -63,6 +63,7 @@ interface CustomLoggerInterface extends FastifyLoggerInstance { const getHandler: RouteHandler = function (request, _reply) { expectType(request.url) + expectType(request.originalUrl) expectType(request.method) expectType(request.routerPath) expectType(request.routerMethod) diff --git a/test/url-rewriting.test.js b/test/url-rewriting.test.js index 075ae953983..77768cf8bc6 100644 --- a/test/url-rewriting.test.js +++ b/test/url-rewriting.test.js @@ -104,3 +104,38 @@ test('Should throw an error', t => { t.teardown(() => fastify.close()) }) + +test('Should rewrite url but keep originalUrl unchanged', t => { + t.plan(7) + const fastify = Fastify({ + rewriteUrl (req) { + t.equal(req.url, '/this-would-404-without-url-rewrite') + t.equal(req.originalUrl, '/this-would-404-without-url-rewrite') + return '/' + } + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + t.equal(req.originalUrl, '/this-would-404-without-url-rewrite') + reply.send({ hello: 'world' }) + } + }) + + fastify.listen({ port: 0 }, function (err) { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' + }, (err, response, body) => { + t.error(err) + t.same(JSON.parse(body), { hello: 'world' }) + t.equal(response.statusCode, 200) + }) + }) + + t.teardown(() => fastify.close()) +}) diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js index 00a3acb6e37..8d79b6129db 100644 --- a/test/versioned-routes.test.js +++ b/test/versioned-routes.test.js @@ -394,7 +394,7 @@ test('Bad accept version (inject)', t => { }) }) -test('Bas accept version (server)', t => { +test('Bad accept version (server)', t => { t.plan(5) const fastify = Fastify() diff --git a/types/request.d.ts b/types/request.d.ts index a23ba41f398..d0cbbe05ee2 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -73,6 +73,7 @@ export interface FastifyRequest Date: Mon, 12 Jun 2023 15:13:19 +0000 Subject: [PATCH 0333/1295] build(deps-dev): Bump markdownlint-cli2 from 0.7.1 to 0.8.1 (#4819) Bumps [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) from 0.7.1 to 0.8.1. - [Changelog](https://github.com/DavidAnson/markdownlint-cli2/blob/main/CHANGELOG.md) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.7.1...v0.8.1) --- updated-dependencies: - dependency-name: markdownlint-cli2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f7e88eec17b..f4da230f6ae 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "json-schema-to-ts": "^2.9.1", "JSONStream": "^1.3.5", "license-checker": "^25.0.1", - "markdownlint-cli2": "^0.7.1", + "markdownlint-cli2": "^0.8.1", "proxyquire": "^2.1.3", "pump": "^3.0.0", "self-cert": "^2.0.0", From 1128dc096a438aef897b86fa30dddb6e94fe34a2 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 13 Jun 2023 20:33:17 +0200 Subject: [PATCH 0334/1295] docs: add Platformatic to deploy strategies (#4822) * Add Platformatic to deploy strategies Signed-off-by: Matteo Collina * Update docs/Guides/Serverless.md Co-authored-by: James Sumners --------- Signed-off-by: Matteo Collina Co-authored-by: James Sumners --- docs/Guides/Serverless.md | 44 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 7f0e996edfc..685ce50bc03 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -28,6 +28,7 @@ snippet of code. - [Google Cloud Functions](#google-cloud-functions) - [Google Cloud Run](#google-cloud-run) - [Netlify Lambda](#netlify-lambda) +- [Platformatic Cloud](#platformatic) - [Vercel](#vercel) ## AWS @@ -453,6 +454,47 @@ Add this command to your `package.json` *scripts* Then it should work fine +## Platformatic Cloud + +[Platformatic](https://platformatic.dev) provides zero-configuration deployment +for Node.js applications. +To use it now, you should wrap your existing Fastify application inside a +[Platformatic Service](https://oss.platformatic.dev/docs/reference/service/introduction), +by running the following: + + +```bash +npm create platformatic@latest -- service +``` + +The wizard would ask you to fill in a few answers: + +``` +? Where would you like to create your project? . +? Do you want to run npm install? yes +? Do you want to use TypeScript? no +? What port do you want to use? 3042 +[13:04:14] INFO: Configuration file platformatic.service.json successfully created. +[13:04:14] INFO: Environment file .env successfully created. +[13:04:14] INFO: Plugins folder "plugins" successfully created. +[13:04:14] INFO: Routes folder "routes" successfully created. +? Do you want to create the github action to deploy this application to Platformatic Cloud dynamic workspace? no +? Do you want to create the github action to deploy this application to Platformatic Cloud static workspace? no +``` + +Then, head to [Platformatic Cloud](https://platformatic.cloud) and sign in +with your GitHub account. +Create your first application and a static workspace: be careful to download the +API key as an env file, e.g. `yourworkspace.txt`. + +Then, you can easily deploy your application with the following commmand: + +```bash +platformatic deploy --keys `yuourworkspace.txt` +``` + +Check out the [Full Guide](https://blog.platformatic.dev/how-to-migrate-a-fastify-app-to-platformatic-service) +on how to wrap Fastify application in Platformatic. ## Vercel @@ -506,4 +548,4 @@ async function routes (fastify, options) { } export default routes; -``` \ No newline at end of file +``` From 1a6597f9730d77fa924f4a5ca8c51a9cca93a9ae Mon Sep 17 00:00:00 2001 From: Ilya Strelov Date: Wed, 14 Jun 2023 09:48:09 +0300 Subject: [PATCH 0335/1295] fix: deal while sending an empty body as a buffer (#4797) --- lib/contentTypeParser.js | 2 +- test/buffer.test.js | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test/buffer.test.js diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index c53214e5ab6..34a4539befb 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -281,7 +281,7 @@ function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) { return defaultJsonParser function defaultJsonParser (req, body, done) { - if (body === '' || body == null) { + if (body === '' || body == null || (Buffer.isBuffer(body) && body.length === 0)) { return done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined) } let json diff --git a/test/buffer.test.js b/test/buffer.test.js new file mode 100644 index 00000000000..a82540fbeed --- /dev/null +++ b/test/buffer.test.js @@ -0,0 +1,51 @@ +const t = require('tap') +const test = t.test +const Fastify = require('..') + +test('Buffer test', async t => { + const fastify = Fastify() + fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, fastify.getDefaultJsonParser('error', 'ignore')) + + fastify.delete('/', async (request) => { + return request.body + }) + + test('should return 200 if the body is not empty', async t => { + t.plan(3) + + const response = await fastify.inject({ + method: 'DELETE', + url: '/', + payload: Buffer.from('{"hello":"world"}'), + headers: { + 'content-type': 'application/json' + } + }) + + t.error(response.error) + t.equal(response.statusCode, 200) + t.same(response.payload.toString(), '{"hello":"world"}') + }) + + test('should return 400 if the body is empty', async t => { + t.plan(3) + + const response = await fastify.inject({ + method: 'DELETE', + url: '/', + payload: Buffer.alloc(0), + headers: { + 'content-type': 'application/json' + } + }) + + t.error(response.error) + t.equal(response.statusCode, 400) + t.same(JSON.parse(response.payload.toString()), { + error: 'Bad Request', + code: 'FST_ERR_CTP_EMPTY_JSON_BODY', + message: 'Body cannot be empty when content-type is set to \'application/json\'', + statusCode: 400 + }) + }) +}) From d1ad6c17ce9731f1bc28377318b010966ca339cd Mon Sep 17 00:00:00 2001 From: punkish Date: Wed, 14 Jun 2023 08:48:50 +0200 Subject: [PATCH 0336/1295] docs(ecosystem): add fastify-better-sqlite3 (#4812) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 2b57c65e130..af4bdeaa6fe 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -248,6 +248,8 @@ section. development servers that require Babel transformations of JavaScript sources. - [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash generator & checker. +- [`fastify-better-sqlite3`](https://github.com/punkish/fastify-better-sqlite3) + Plugin for better-sqlite3. - [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your routes to the console, so you definitely know which endpoints are available. - [`fastify-bookshelf`](https://github.com/butlerx/fastify-bookshelfjs) Fastify From 109873cf7e63eeffae65de2613d50b21815d3cb3 Mon Sep 17 00:00:00 2001 From: Sergey Burnevsky Date: Fri, 16 Jun 2023 18:39:55 +0300 Subject: [PATCH 0337/1295] fix: correct type definition for genReqId argument (#4784) * Fixes #4748: correct type defiinition for genReqId(), it receives raw request as argument, not FastifyRequest * Clarify this in docs * Update docs/Reference/Server.md Co-authored-by: Frazer Smith * Improved tests * Removed irrelevant tests --------- Co-authored-by: Frazer Smith Co-authored-by: Uzlopak --- docs/Reference/Server.md | 4 ++-- fastify.d.ts | 2 +- test/genReqId.test.js | 42 ++++++++++++++++++++++++++++++++++++ test/types/fastify.test-d.ts | 8 ++++++- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 20c3375abe7..a068676c9d0 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -527,8 +527,8 @@ Defines the label used for the request identifier when logging the request. ### `genReqId` -Function for generating the request-id. It will receive the incoming request as -a parameter. This function is expected to be error-free. +Function for generating the request-id. It will receive the _raw_ incoming +request as a parameter. This function is expected to be error-free. + Default: `value of 'request-id' header if provided or monotonically increasing integers` diff --git a/fastify.d.ts b/fastify.d.ts index 5f261620d15..750ebc3c09b 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -109,7 +109,7 @@ declare namespace fastify { requestIdHeader?: string | false, requestIdLogLabel?: string; jsonShorthand?: boolean; - genReqId?: (req: FastifyRequest, FastifySchema, TypeProvider>) => string, + genReqId?: (req: RawRequestDefaultExpression) => string, trustProxy?: boolean | string | string[] | number | TrustProxyFunction, querystringParser?: (str: string) => { [key: string]: unknown }, /** diff --git a/test/genReqId.test.js b/test/genReqId.test.js index 74ab442f286..7dce2b8ac87 100644 --- a/test/genReqId.test.js +++ b/test/genReqId.test.js @@ -1,5 +1,6 @@ 'use strict' +const { Readable } = require('stream') const { test } = require('tap') const Fastify = require('..') @@ -30,3 +31,44 @@ test('Should accept a custom genReqId function', t => { }) }) }) + +test('Custom genReqId function gets raw request as argument', t => { + t.plan(9) + + const REQUEST_ID = 'REQ-1234' + + const fastify = Fastify({ + genReqId: function (req) { + t.notOk('id' in req) + t.notOk('raw' in req) + t.ok(req instanceof Readable) + // http.IncomingMessage does have `rawHeaders` property, but FastifyRequest does not + const index = req.rawHeaders.indexOf('x-request-id') + const xReqId = req.rawHeaders[index + 1] + t.equal(xReqId, REQUEST_ID) + t.equal(req.headers['x-request-id'], REQUEST_ID) + return xReqId + } + }) + + fastify.get('/', (req, reply) => { + t.equal(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.inject({ + method: 'GET', + headers: { + 'x-request-id': REQUEST_ID + }, + url: 'http://localhost:' + fastify.server.address().port + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, REQUEST_ID) + fastify.close() + }) + }) +}) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index ca99d902c8c..6264fb78abd 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -8,6 +8,7 @@ import fastify, { LightMyRequestResponse, LightMyRequestCallback, InjectOptions, FastifyBaseLogger, + RawRequestDefaultExpression, RouteGenericInterface, ValidationResult, FastifyErrorCodes, @@ -127,7 +128,12 @@ expectAssignable(fastify({ serverFactory: () => http.createServ expectAssignable(fastify({ caseSensitive: true })) expectAssignable(fastify({ requestIdHeader: 'request-id' })) expectAssignable(fastify({ requestIdHeader: false })) -expectAssignable(fastify({ genReqId: () => 'request-id' })) +expectAssignable(fastify({ + genReqId: (req) => { + expectType(req) + return 'foo' + } +})) expectAssignable(fastify({ trustProxy: true })) expectAssignable(fastify({ querystringParser: () => ({ foo: 'bar' }) })) expectAssignable(fastify({ querystringParser: () => ({ foo: { bar: 'fuzz' } }) })) From 61b86760019c85afbef6cc0f175c75870381ed04 Mon Sep 17 00:00:00 2001 From: Horie Issei Date: Sat, 17 Jun 2023 22:31:44 +0900 Subject: [PATCH 0338/1295] fix typo (#4828) --- lib/reply.js | 2 +- lib/request.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reply.js b/lib/reply.js index 2068957bff5..d8da8c2a785 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -357,7 +357,7 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null }) // We create a WeakMap to compile the schema only once - // Its done leazily to avoid add overhead by creating the WeakMap + // Its done lazily to avoid add overhead by creating the WeakMap // if it is not used // TODO: Explore a central cache for all the schemas shared across // encapsulated contexts diff --git a/lib/request.js b/lib/request.js index da0de5ba6c1..12221cbd036 100644 --- a/lib/request.js +++ b/lib/request.js @@ -293,7 +293,7 @@ Object.defineProperties(Request.prototype, { }) // We create a WeakMap to compile the schema only once - // Its done leazily to avoid add overhead by creating the WeakMap + // Its done lazily to avoid add overhead by creating the WeakMap // if it is not used // TODO: Explore a central cache for all the schemas shared across // encapsulated contexts From c2b89732d583a0e0d8e0a666b2eb9b62610edf34 Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Tue, 20 Jun 2023 10:50:36 +0200 Subject: [PATCH 0339/1295] fix: handle undefined req.routeConfig in frameworkErrors (#4826) Co-authored-by: Giulio Davide --- lib/request.js | 12 +++---- test/inject.test.js | 30 +++++++++++++++++ test/internals/reply.test.js | 4 +-- test/internals/request.test.js | 59 ++++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 8 deletions(-) diff --git a/lib/request.js b/lib/request.js index 12221cbd036..6a42210f6d3 100644 --- a/lib/request.js +++ b/lib/request.js @@ -172,7 +172,7 @@ Object.defineProperties(Request.prototype, { }, routerPath: { get () { - return this[kRouteContext].config.url + return this[kRouteContext].config?.url } }, routeOptions: { @@ -182,8 +182,8 @@ Object.defineProperties(Request.prototype, { const serverLimit = context.server.initialConfig.bodyLimit const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined const options = { - method: context.config.method, - url: context.config.url, + method: context.config?.method, + url: context.config?.url, bodyLimit: (routeLimit || serverLimit), attachValidation: context.attachValidation, logLevel: context.logLevel, @@ -196,12 +196,12 @@ Object.defineProperties(Request.prototype, { }, routerMethod: { get () { - return this[kRouteContext].config.method + return this[kRouteContext].config?.method } }, routeConfig: { get () { - return this[kRouteContext][kPublicRouteContext].config + return this[kRouteContext][kPublicRouteContext]?.config } }, routeSchema: { @@ -211,7 +211,7 @@ Object.defineProperties(Request.prototype, { }, is404: { get () { - return this[kRouteContext].config.url === undefined + return this[kRouteContext].config?.url === undefined } }, connection: { diff --git a/test/inject.test.js b/test/inject.test.js index 34469d66b93..e728e4fd908 100644 --- a/test/inject.test.js +++ b/test/inject.test.js @@ -6,6 +6,7 @@ const Stream = require('stream') const util = require('util') const Fastify = require('..') const FormData = require('form-data') +const { Readable } = require('stream') test('inject should exist', t => { t.plan(2) @@ -455,3 +456,32 @@ test('should handle errors in builder-style injection correctly', async (t) => { t.equal(err.message, 'Kaboom') } }) + +test('Should not throw on access to routeConfig frameworkErrors handler - FST_ERR_BAD_URL', t => { + t.plan(6) + + const fastify = Fastify({ + frameworkErrors: function (err, req, res) { + t.ok(typeof req.id === 'string') + t.ok(req.raw instanceof Readable) + t.same(req.routerPath, undefined) + t.same(req.routeConfig, undefined) + res.send(`${err.message} - ${err.code}`) + } + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.error(err) + t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + } + ) +}) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 00b6e0ff586..f80f2146f7b 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -301,7 +301,7 @@ test('within an instance', t => { }) }) - test('auto status code shoud be 200', t => { + test('auto status code should be 200', t => { t.plan(3) sget({ method: 'GET', @@ -313,7 +313,7 @@ test('within an instance', t => { }) }) - test('auto type shoud be text/plain', t => { + test('auto type should be text/plain', t => { t.plan(3) sget({ method: 'GET', diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 7936af87fd6..f0173cbd5bb 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -75,6 +75,65 @@ test('Regular request', t => { t.end() }) +test('Request with undefined config', t => { + const headers = { + host: 'hostname' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + server: { + [kReply]: {}, + [kRequest]: Request + } + }) + req.connection = req.socket + const request = new Request('id', 'params', req, 'query', 'log', context) + t.type(request, Request) + t.type(request.validateInput, Function) + t.type(request.getValidationFunction, Function) + t.type(request.compileValidationSchema, Function) + t.equal(request.id, 'id') + t.equal(request.params, 'params') + t.equal(request.raw, req) + t.equal(request.query, 'query') + t.equal(request.headers, headers) + t.equal(request.log, 'log') + t.equal(request.ip, 'ip') + t.equal(request.ips, undefined) + t.equal(request.hostname, 'hostname') + t.equal(request.body, undefined) + t.equal(request.method, 'GET') + t.equal(request.url, '/') + t.equal(request.originalUrl, '/') + t.equal(request.socket, req.socket) + t.equal(request.protocol, 'http') + t.equal(request.routeSchema, context[kPublicRouteContext].schema) + t.equal(request.routerPath, undefined) + t.equal(request.routerMethod, undefined) + t.equal(request.routeConfig, undefined) + + // Aim to not bad property keys (including Symbols) + t.notOk('undefined' in request) + + // This will be removed, it's deprecated + t.equal(request.connection, req.connection) + t.end() +}) + test('Regular request - hostname from authority', t => { t.plan(2) const headers = { From 639e15a05bbedad1a2030aa30430b6e619dcc61b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 19:32:56 +0100 Subject: [PATCH 0340/1295] build(deps-dev): Bump @sinonjs/fake-timers from 10.2.0 to 11.0.0 (#4818) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4da230f6ae..f81e297fbeb 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "devDependencies": { "@fastify/pre-commit": "^2.0.2", "@sinclair/typebox": "^0.28.9", - "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/fake-timers": "^11.0.0", "@types/node": "^20.1.0", "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", From 5992c0bea713db8d46274b4756a3db12b5651fd1 Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Tue, 20 Jun 2023 21:56:39 +0200 Subject: [PATCH 0341/1295] docs(Routes.md): added short description for constraints option (#4838) --- docs/Reference/Routes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 8d0ffb522b8..20ca9b8e86e 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -112,6 +112,11 @@ fastify.route(options) * `config`: object used to store custom configuration. * `version`: a [semver](https://semver.org/) compatible string that defined the version of the endpoint. [Example](#version-constraints). +* `constraints`: defines route restrictions based on request properties or + values, enabling customized matching using + [find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes + built-in `version` and `host` constraints, with support for custom constraint + strategies. * `prefixTrailingSlash`: string used to determine how to handle passing `/` as a route with a prefix. * `both` (default): Will register both `/prefix` and `/prefix/`. From 49b416c8f58190e393f782478d70a00f4e054e82 Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Thu, 22 Jun 2023 20:08:14 +0200 Subject: [PATCH 0342/1295] [fix] Tests are failing in Windows due to command line env syntax (#4845) --- package.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f81e297fbeb..8b3adcc614b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"", "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", - "coverage": "NODE_OPTIONS=no-network-family-autoselection npm run unit -- --cov --coverage-report=html", + "coverage": "cross-env NODE_OPTIONS=no-network-family-autoselection npm run unit -- --cov --coverage-report=html", "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage", "coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100", "license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"", @@ -19,8 +19,8 @@ "lint:markdown": "markdownlint-cli2", "lint:standard": "standard | snazzy", "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", - "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", - "test": "npm run lint && NODE_OPTIONS=no-network-family-autoselection npm run unit && npm run test:typescript", + "prepublishOnly": "cross-env PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", + "test": "npm run lint && cross-env NODE_OPTIONS=no-network-family-autoselection npm run unit && npm run test:typescript", "test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", @@ -145,6 +145,7 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "branch-comparer": "^1.1.0", + "cross-env": "^7.0.3", "eslint": "^8.39.0", "eslint-config-standard": "^17.0.0", "eslint-import-resolver-node": "^0.3.7", @@ -181,10 +182,10 @@ "@fastify/ajv-compiler": "^3.5.0", "@fastify/error": "^3.2.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", - "fast-json-stringify": "^5.7.0", "abstract-logging": "^2.0.1", "avvio": "^8.2.1", "fast-content-type-parse": "^1.0.0", + "fast-json-stringify": "^5.7.0", "find-my-way": "^7.6.0", "light-my-request": "^5.9.1", "pino": "^8.12.0", From c4b8d85119068056df7d90782cd33841c943e062 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Fri, 23 Jun 2023 15:52:58 +0200 Subject: [PATCH 0343/1295] fix(#4774): close bindings through the hook system (#4776) * fix: close bindings through the hook system * docs: replace comments Co-authored-by: James Sumners * refactor: shallow error for hook * docs: add warning about Server usage * fix: bad quoting Co-authored-by: Manuel Spigolon * test: add proper testing * test: improve scenarios * test: adjust pipelining test --------- Co-authored-by: James Sumners Co-authored-by: Manuel Spigolon --- docs/Reference/Server.md | 3 + lib/server.js | 24 ++++++ test/close-pipelining.test.js | 5 +- test/close.test.js | 154 ++++++++++++++++++++++++++++++++-- 4 files changed, 179 insertions(+), 7 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index a068676c9d0..61f6a8575b1 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -856,6 +856,9 @@ is an instance-wide configuration. [server](https://nodejs.org/api/http.html#http_class_http_server) object as returned by the [**`Fastify factory function`**](#factory). +>__Warning__: If utilized improperly, certain Fastify features could be disrupted. +>It is recommended to only use it for attaching listeners. + #### after diff --git a/lib/server.js b/lib/server.js index ff1390b1576..384ac4de6c0 100644 --- a/lib/server.js +++ b/lib/server.js @@ -132,6 +132,30 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o /* istanbul ignore next: the else won't be taken unless listening fails */ if (!_ignoreErr) { this[kServerBindings].push(secondaryServer) + // Due to the nature of the feature is not possible to know + // ahead of time the number of bindings that will be made. + // For instance, each binding is hooked to be closed at their own + // pace through the `onClose` hook. + // It also allows them to handle possible connections already + // attached to them if any. + this.onClose((instance, done) => { + if (instance[kState].listening) { + // No new TCP connections are accepted + // We swallow any error from the secondary + // server + secondaryServer.close(() => done()) + /* istanbul ignore next: Cannot test this without Node.js core support */ + if (serverOpts.forceCloseConnections === 'idle') { + // Not needed in Node 19 + secondaryServer.closeIdleConnections() + /* istanbul ignore next: Cannot test this without Node.js core support */ + } else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) { + secondaryServer.closeAllConnections() + } + } else { + done() + } + }) } if (binded === binding) { diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index 7c952e83847..37cb0f53883 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -87,13 +87,16 @@ test('Should close the socket abruptly - pipelining - return503OnClosing: false, }) const responses = await Promise.allSettled([ + instance.request({ path: '/', method: 'GET' }), instance.request({ path: '/', method: 'GET' }), instance.request({ path: '/', method: 'GET' }), instance.request({ path: '/', method: 'GET' }) ]) + t.equal(responses[0].status, 'fulfilled') - t.equal(responses[1].status, 'rejected') + t.equal(responses[1].status, 'fulfilled') t.equal(responses[2].status, 'rejected') + t.equal(responses[3].status, 'rejected') await instance.close() }) diff --git a/test/close.test.js b/test/close.test.js index fa0b58fc72f..9e6bfb8c43b 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -2,8 +2,7 @@ const net = require('net') const http = require('http') -const t = require('tap') -const test = t.test +const { test } = require('tap') const Fastify = require('..') const { Client } = require('undici') const semver = require('semver') @@ -205,7 +204,7 @@ test('Should return error while closing (callback) - injection', t => { }) const isV19plus = semver.gte(process.version, '19.0.0') -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 => { +test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, t => { const fastify = Fastify({ return503OnClosing: false, forceCloseConnections: false @@ -243,7 +242,7 @@ t.test('Current opened connection should continue to work after closing and retu }) }) -t.test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, t => { +test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, t => { t.plan(4) const fastify = Fastify({ return503OnClosing: false, @@ -282,7 +281,7 @@ t.test('Current opened connection should NOT continue to work after closing and }) }) -t.test('Current opened connection should not accept new incoming connections', t => { +test('Current opened connection should not accept new incoming connections', t => { t.plan(3) const fastify = Fastify({ forceCloseConnections: false }) fastify.get('/', (req, reply) => { @@ -304,7 +303,7 @@ t.test('Current opened connection should not accept new incoming connections', t }) }) -t.test('rejected incoming connections should be logged', t => { +test('rejected incoming connections should be logged', t => { t.plan(2) const stream = split(JSON.parse) const fastify = Fastify({ @@ -449,6 +448,149 @@ test('shutsdown while keep-alive connections are active (non-async, idle, native }) }) +test('triggers on-close hook in the right order with multiple bindings', async t => { + const expectedOrder = [1, 2, 3] + const order = [] + const fastify = Fastify() + + t.plan(1) + + // Follows LIFO + fastify.addHook('onClose', () => { + order.push(2) + }) + + fastify.addHook('onClose', () => { + order.push(1) + }) + + await fastify.listen({ port: 0 }) + + await new Promise((resolve, reject) => { + setTimeout(() => { + fastify.close(err => { + order.push(3) + t.match(order, expectedOrder) + + if (err) t.error(err) + else resolve() + }) + }, 2000) + }) +}) + +test('triggers on-close hook in the right order with multiple bindings (forceCloseConnections - idle)', { skip: noSupport }, async t => { + const expectedPayload = { hello: 'world' } + const timeoutTime = 2 * 60 * 1000 + const expectedOrder = [1, 2] + const order = [] + const fastify = Fastify({ forceCloseConnections: 'idle' }) + + fastify.server.setTimeout(timeoutTime) + fastify.server.keepAliveTimeout = timeoutTime + + fastify.get('/', async (req, reply) => { + await new Promise((resolve) => { + setTimeout(resolve, 1000) + }) + + return expectedPayload + }) + + fastify.addHook('onClose', () => { + order.push(1) + }) + + await fastify.listen({ port: 0 }) + const addresses = fastify.addresses() + const testPlan = (addresses.length * 2) + 1 + + t.plan(testPlan) + + for (const addr of addresses) { + const { family, address, port } = addr + const host = family === 'IPv6' ? `[${address}]` : address + const client = new Client(`http://${host}:${port}`, { + keepAliveTimeout: 1 * 60 * 1000 + }) + + client.request({ path: '/', method: 'GET' }) + .then((res) => res.body.json(), err => t.error(err)) + .then(json => { + t.match(json, expectedPayload, 'should payload match') + t.notOk(client.closed, 'should client not be closed') + }, err => t.error(err)) + } + + await new Promise((resolve, reject) => { + setTimeout(() => { + fastify.close(err => { + order.push(2) + t.match(order, expectedOrder) + + if (err) t.error(err) + else resolve() + }) + }, 2000) + }) +}) + +test('triggers on-close hook in the right order with multiple bindings (forceCloseConnections - true)', { skip: noSupport }, async t => { + const expectedPayload = { hello: 'world' } + const timeoutTime = 2 * 60 * 1000 + const expectedOrder = [1, 2] + const order = [] + const fastify = Fastify({ forceCloseConnections: true }) + + fastify.server.setTimeout(timeoutTime) + fastify.server.keepAliveTimeout = timeoutTime + + fastify.get('/', async (req, reply) => { + await new Promise((resolve) => { + setTimeout(resolve, 1000) + }) + + return expectedPayload + }) + + fastify.addHook('onClose', () => { + order.push(1) + }) + + await fastify.listen({ port: 0 }) + const addresses = fastify.addresses() + const testPlan = (addresses.length * 2) + 1 + + t.plan(testPlan) + + for (const addr of addresses) { + const { family, address, port } = addr + const host = family === 'IPv6' ? `[${address}]` : address + const client = new Client(`http://${host}:${port}`, { + keepAliveTimeout: 1 * 60 * 1000 + }) + + client.request({ path: '/', method: 'GET' }) + .then((res) => res.body.json(), err => t.error(err)) + .then(json => { + t.match(json, expectedPayload, 'should payload match') + t.notOk(client.closed, 'should client not be closed') + }, err => t.error(err)) + } + + await new Promise((resolve, reject) => { + setTimeout(() => { + fastify.close(err => { + order.push(2) + t.match(order, expectedOrder) + + if (err) t.error(err) + else resolve() + }) + }, 2000) + }) +}) + test('shutsdown while keep-alive connections are active (non-async, custom)', t => { t.plan(5) From 8de9eabedaf1de9ac6ac33b7443ad26500faa328 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Sun, 25 Jun 2023 04:42:11 -0400 Subject: [PATCH 0344/1295] Fix/add missing types fastifycontextconfig (#4850) --- test/types/route.test-d.ts | 82 ++++++++++++++++++++++++++++++++++++-- types/context.d.ts | 3 +- types/route.d.ts | 5 +++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 569341d15fd..1eb232ba4bb 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -74,7 +74,7 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' Headers: HeadersInterface; } - fastify()[lowerCaseMethod]('/', { config: { foo: 'bar', bar: 100, extra: true } }, (req, res) => { + fastify()[lowerCaseMethod]('/', { config: { foo: 'bar', bar: 100, extra: true, url: '/', method: lowerCaseMethod } }, (req, res) => { expectType(req.body) expectType(req.query) expectType(req.params) @@ -82,15 +82,19 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.context.config.foo) expectType(req.context.config.bar) expectType(req.context.config.extra) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) expectType(res.context.config.extra) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }) fastify().route({ url: '/', method: method as HTTPMethods, - config: { foo: 'bar', bar: 100 }, + config: { foo: 'bar', bar: 100, url: '/', method: method as HTTPMethods }, prefixTrailingSlash: 'slash', onRequest: (req, res, done) => { // these handlers are tested in `hooks.test-d.ts` expectType(req.body) @@ -99,8 +103,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, preParsing: (req, res, payload, done) => { expectType(req.body) @@ -109,8 +117,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) expectType(payload) expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done) expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) @@ -122,8 +134,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, preHandler: (req, res, done) => { expectType(req.body) @@ -132,8 +148,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, onResponse: (req, res, done) => { expectType(req.body) @@ -142,8 +162,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) expectType(res.statusCode) }, onError: (req, res, error, done) => { @@ -153,8 +177,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, preSerialization: (req, res, payload, done) => { expectType(req.body) @@ -163,8 +191,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, onSend: (req, res, payload, done) => { expectType(req.body) @@ -173,8 +205,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, handler: (req, res) => { expectType(req.body) @@ -183,15 +219,19 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) } }) fastify().route({ url: '/', method: method as HTTPMethods, - config: { foo: 'bar', bar: 100 }, + config: { foo: 'bar', bar: 100, url: '/', method: method as HTTPMethods }, prefixTrailingSlash: 'slash', onRequest: async (req, res, done) => { // these handlers are tested in `hooks.test-d.ts` expectType(req.body) @@ -200,8 +240,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, preParsing: async (req, res, payload, done) => { expectType(req.body) @@ -210,8 +254,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) expectType(payload) expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done) expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) @@ -223,8 +271,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, preHandler: async (req, res, done) => { expectType(req.body) @@ -233,8 +285,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, onResponse: async (req, res, done) => { expectType(req.body) @@ -243,8 +299,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) expectType(res.statusCode) }, onError: async (req, res, error, done) => { @@ -254,8 +314,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, preSerialization: async (req, res, payload, done) => { expectType(req.body) @@ -264,8 +328,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, onSend: async (req, res, payload, done) => { expectType(req.body) @@ -274,8 +342,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) }, handler: (req, res) => { expectType(req.body) @@ -284,8 +356,12 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.headers) expectType(req.context.config.foo) expectType(req.context.config.bar) + expectType(req.context.config.url) + expectType(req.context.config.method) expectType(res.context.config.foo) expectType(res.context.config.bar) + expectType(req.routeConfig.url) + expectType(req.routeConfig.method) } }) }) diff --git a/types/context.d.ts b/types/context.d.ts index ad23ed752f2..f431969afbd 100644 --- a/types/context.d.ts +++ b/types/context.d.ts @@ -1,7 +1,8 @@ import { ContextConfigDefault } from './utils' +import { FastifyRouteConfig } from './route' // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface FastifyContextConfig { +export interface FastifyContextConfig extends FastifyRouteConfig { } /** diff --git a/types/route.d.ts b/types/route.d.ts index 9f33a246e18..beabc41965a 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -13,6 +13,11 @@ import { } from './type-provider' import { FastifyBaseLogger, LogLevel } from './logger' +export interface FastifyRouteConfig { + url: string; + method: HTTPMethods | HTTPMethods[]; +} + export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface {} /** From 5d4182139de0bf46e1f948792cc1ffb8bd156ef9 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 25 Jun 2023 12:13:47 +0200 Subject: [PATCH 0345/1295] Added "Principles" to explain the key technical principles behind Fastify (#4852) * Added "Principles" to explain the key technical principles behind Fastify Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * Apply suggestions from code review Co-authored-by: Frazer Smith * Update docs/Reference/Principles.md Co-authored-by: Matteo Collina --------- Signed-off-by: Matteo Collina Co-authored-by: Frazer Smith --- docs/Reference/Principles.md | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/Reference/Principles.md diff --git a/docs/Reference/Principles.md b/docs/Reference/Principles.md new file mode 100644 index 00000000000..2279ab3f062 --- /dev/null +++ b/docs/Reference/Principles.md @@ -0,0 +1,78 @@ +# Technical Principles + +Every decision in the Fastify framework and its official plugins is guided by +the following technical principles: + +1. “Zero” overhead in production +2. “Good” developer experience +3. Works great for small & big projects alike +4. Easy to migrate to microservices (or even serverless) and back +5. Security & data validation +6. If something could be a plugin, it likely should be +7. Easily testable +8. Do not monkeypatch core +9. Semantic versioning & Long Term Support +10. Specification adherence + +## "Zero" Overhead in Production + +Fastify aims to implement its features by adding as minimal overhead to your +application as possible. +This is usually delivered by implementing fast algorithms and data structures, +as well as JavaScript-specific features. + +Given that JavaScript does not offer zero-overhead data structures, this principle +is at odds with providing a great developer experience and providing more features, +as usually those cost some overhead. + +## "Good" Developer Experience + +Fastify aims to provide the best developer experience at the performance point +it is operating. +It provides a great out-of-the-box experience that is flexible enough to be +adapted to a variety of situations. + +As an example, this means that binary addons are forbidden because most JavaScript +developers would not +have access to a compiler. + +## Works great for small and big projects alike + +We recognize that most applications start small and become more complex over time. +Fastify aims to grow with +the complexity of your application, providing advanced features to structure +your codebase. + +## Easy to migrate to microservices (or even serverless) and back + +How you deploy your routes should not matter. The framework should "just work". + +## Security and Data Validation + +Your web framework is the first point of contact with untrusted data, and it +needs to act as the first line of defense for your system. + +## If something could be a plugin, it likely should + +We recognize that there are an infinite amount of use cases for an HTTP framework +for Node.js. Catering to them in a single module would make the codebase unmaintainable. +Therefore we provide hooks and options to allow you to customize the framework +as you please. + +## Easily testable + +Testing Fastify applications should be a first-class concern. + +## Do not monkeypatch core + +Moonkeypatch Node.js APIs or installing globals that alter the behavior of the +runtime makes building modular applications harder, and limit the use cases of Fastify. +Other frameworks do this and we do not. + +## Semantic Versioning and Long Term Support + +We provide a clear Long Term Support strategy so developers can know when to upgrade. + +## Specification adherence + +In doubt, we chose the strict behavior as defined by the relevant Specifications. From 3230a10fa4d682ad8d676705941654e1e3203b77 Mon Sep 17 00:00:00 2001 From: Md Adil Date: Sun, 25 Jun 2023 16:15:32 +0530 Subject: [PATCH 0346/1295] pluginName will be exposed in FastifyInstance (#4848) * pluginName will be exposed in FastifyInstance * adding pluginName typescript property test and removed readonly from pluginName * exposed pluginName type in typescript --------- Co-authored-by: Md Adil --- test/types/instance.test-d.ts | 2 ++ types/instance.d.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index a44fe041ad4..aa7df287f24 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -26,6 +26,8 @@ expectAssignable(server.addSchema({ schemas: [] })) +expectType(server.pluginName) + expectType>(server.getSchemas()) expectType(server.addresses()) expectType(server.getSchema('SchemaId')) diff --git a/types/instance.d.ts b/types/instance.d.ts index f7b2ecd7ba3..01262f1d617 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -96,6 +96,7 @@ export interface FastifyInstance< TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { server: RawServer; + pluginName: string; prefix: string; version: string; log: Logger; From 433439f94c2c82ba6c15791fe74c7e8bb0daf4a4 Mon Sep 17 00:00:00 2001 From: Sergey Burnevsky Date: Sun, 25 Jun 2023 13:48:15 +0300 Subject: [PATCH 0347/1295] fix(#4808): pass Fastify request to frameworkErrors and omit logging if disabled (#4825) Co-authored-by: Carlos Fuentes --- fastify.js | 14 ++- test/router-options.test.js | 221 ++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 4 deletions(-) diff --git a/fastify.js b/fastify.js index 86efc3f56ac..48d7304fc4a 100644 --- a/fastify.js +++ b/fastify.js @@ -692,10 +692,13 @@ function fastify (options) { const id = genReqId(req) const childLogger = logger.child({ reqId: id }) - childLogger.info({ req }, 'incoming request') - const request = new Request(id, null, req, null, childLogger, onBadUrlContext) const reply = new Reply(res, request, childLogger) + + if (disableRequestLogging === false) { + childLogger.info({ req: request }, 'incoming request') + } + return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply) } const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}` @@ -714,10 +717,13 @@ function fastify (options) { const id = genReqId(req) const childLogger = logger.child({ reqId: id }) - childLogger.info({ req }, 'incoming request') - const request = new Request(id, null, req, null, childLogger, onBadUrlContext) const reply = new Reply(res, request, childLogger) + + if (disableRequestLogging === false) { + childLogger.info({ req: request }, 'incoming request') + } + return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply) } const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}' diff --git a/test/router-options.test.js b/test/router-options.test.js index 0b83aabcea1..da78fafa2df 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -1,5 +1,6 @@ 'use strict' +const split = require('split2') const test = require('tap').test const Fastify = require('../') const { @@ -169,6 +170,95 @@ test('Should honor frameworkErrors option - FST_ERR_BAD_URL', t => { ) }) +test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_BAD_URL', t => { + t.plan(8) + + const REQ_ID = 'REQ-1234' + const logStream = split(JSON.parse) + + const fastify = Fastify({ + frameworkErrors: function (err, req, res) { + t.same(req.id, REQ_ID) + t.same(req.raw.httpVersion, '1.1') + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + serializers: { + req (request) { + t.same(request.id, REQ_ID) + return { httpVersion: request.raw.httpVersion } + } + } + }, + genReqId: () => REQ_ID + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + logStream.on('data', (json) => { + t.same(json.msg, 'incoming request') + t.same(json.reqId, REQ_ID) + t.same(json.req.httpVersion, '1.1') + }) + + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.error(err) + t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + } + ) +}) + +test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_BAD_URL', t => { + t.plan(2) + + const logStream = split(JSON.parse) + + const fastify = Fastify({ + disableRequestLogging: true, + frameworkErrors: function (err, req, res) { + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + serializers: { + req () { + t.fail('should not be called') + }, + res () { + t.fail('should not be called') + } + } + } + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + logStream.on('data', (json) => { + t.fail('should not be called') + }) + + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.error(err) + t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + } + ) +}) + test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', t => { t.plan(3) @@ -219,3 +309,134 @@ test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', t => { } ) }) + +test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', t => { + t.plan(8) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const REQ_ID = 'REQ-1234' + const logStream = split(JSON.parse) + + const fastify = Fastify({ + constraints: { secret: constraint }, + frameworkErrors: function (err, req, res) { + t.same(req.id, REQ_ID) + t.same(req.raw.httpVersion, '1.1') + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + serializers: { + req (request) { + t.same(request.id, REQ_ID) + return { httpVersion: request.raw.httpVersion } + } + } + }, + genReqId: () => REQ_ID + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + logStream.on('data', (json) => { + t.same(json.msg, 'incoming request') + t.same(json.reqId, REQ_ID) + t.same(json.req.httpVersion, '1.1') + }) + + fastify.inject( + { + method: 'GET', + url: '/' + }, + (err, res) => { + t.error(err) + t.equal(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + } + ) +}) + +test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', t => { + t.plan(2) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const logStream = split(JSON.parse) + + const fastify = Fastify({ + constraints: { secret: constraint }, + disableRequestLogging: true, + frameworkErrors: function (err, req, res) { + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + serializers: { + req () { + t.fail('should not be called') + }, + res () { + t.fail('should not be called') + } + } + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + logStream.on('data', (json) => { + t.fail('should not be called') + }) + + fastify.inject( + { + method: 'GET', + url: '/' + }, + (err, res) => { + t.error(err) + t.equal(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + } + ) +}) From 03c5fc7116478b3a47a0df23fa95bed1115369b2 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Sun, 25 Jun 2023 15:47:16 +0300 Subject: [PATCH 0348/1295] docs: Add documentation for zod type provider (#4456) Co-authored-by: Frazer Smith --- docs/Reference/Type-Providers.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index c199bf83df2..49a155c445d 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -11,15 +11,16 @@ keep associated types for each schema defined in your project. Type Providers are offered as additional packages you will need to install into your project. Each provider uses a different inference library under the hood; -allowing you to select the library most appropriate for your needs. Type +allowing you to select the library most appropriate for your needs. Official Type Provider packages follow a `@fastify/type-provider-{provider-name}` naming -convention. +convention, and there are several community ones available as well. The following inference packages are supported: - `json-schema-to-ts` - [github](https://github.com/ThomasAribart/json-schema-to-ts) - `typebox` - [github](https://github.com/sinclairzx81/typebox) +- `zod` - [github](https://github.com/colinhacks/zod) ### Json Schema to Ts @@ -91,6 +92,12 @@ See also the [TypeBox documentation](https://github.com/sinclairzx81/typebox#validation) on how to set up AJV to work with TypeBox. +### Zod + +See [official documentation](https://github.com/turkerdev/fastify-type-provider-zod) +for Zod type provider instructions. + + ### Scoped Type-Provider The provider types don't propagate globally. In encapsulated usage, one can From 6e062296d7b3a3b05407b169bd1a84e103293587 Mon Sep 17 00:00:00 2001 From: M ABD AZIZ ALFIAN Date: Mon, 26 Jun 2023 18:27:08 +0700 Subject: [PATCH 0349/1295] docs(ecosystem): add fastify cacheman (#4851) --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index af4bdeaa6fe..28b1130d0f2 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -260,6 +260,9 @@ section. to add [bree](https://github.com/breejs/bree) support. - [`fastify-bugsnag`](https://github.com/ZigaStrgar/fastify-bugsnag) Fastify plugin to add support for [Bugsnag](https://www.bugsnag.com/) error reporting. +- [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman) + Small and efficient cache provider for Node.JS with In-memory, File, Redis + and MongoDB engines for Fastify - [`fastify-casbin`](https://github.com/nearform/fastify-casbin) Casbin support for Fastify. - [`fastify-casbin-rest`](https://github.com/nearform/fastify-casbin-rest) From d8aece2c9ed313896abde2a9b440c0658252efaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:36:45 +0000 Subject: [PATCH 0350/1295] build(deps-dev): Bump eslint-plugin-n from 15.7.0 to 16.0.1 (#4857) Bumps [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) from 15.7.0 to 16.0.1. - [Release notes](https://github.com/eslint-community/eslint-plugin-n/releases) - [Commits](https://github.com/eslint-community/eslint-plugin-n/compare/15.7.0...16.0.1) --- updated-dependencies: - dependency-name: eslint-plugin-n dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b3adcc614b..34537f44118 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "eslint-config-standard": "^17.0.0", "eslint-import-resolver-node": "^0.3.7", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.7.0", + "eslint-plugin-n": "^16.0.1", "eslint-plugin-promise": "^6.1.1", "fast-json-body": "^1.1.0", "fastify-plugin": "^4.5.0", From 3f02c96bc37929cb57f7ce6a15bade106fd6af64 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 29 Jun 2023 08:23:03 +0200 Subject: [PATCH 0351/1295] fix: return 431 status code on HTTP header overflow error (#4856) --- fastify.js | 5 +++ test/header-overflow.test.js | 65 ++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 test/header-overflow.test.js diff --git a/fastify.js b/fastify.js index 48d7304fc4a..c6bce3b50f2 100644 --- a/fastify.js +++ b/fastify.js @@ -653,6 +653,11 @@ function fastify (options) { errorStatus = http.STATUS_CODES[errorCode] body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}` errorLabel = 'timeout' + } else if (err.code === 'HPE_HEADER_OVERFLOW') { + errorCode = '431' + errorStatus = http.STATUS_CODES[errorCode] + body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}` + errorLabel = 'header_overflow' } else { errorCode = '400' errorStatus = http.STATUS_CODES[errorCode] diff --git a/test/header-overflow.test.js b/test/header-overflow.test.js new file mode 100644 index 00000000000..3b63830d545 --- /dev/null +++ b/test/header-overflow.test.js @@ -0,0 +1,65 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') +const sget = require('simple-get').concat + +const maxHeaderSize = 1024 + +test('Should return 431 if request header fields are too large', t => { + t.plan(3) + + const fastify = Fastify({ http: { maxHeaderSize } }) + fastify.route({ + method: 'GET', + url: '/', + handler: (_req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.listen({ port: 0 }, function (err) { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port, + headers: { + 'Large-Header': 'a'.repeat(maxHeaderSize) + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 431) + }) + }) + + t.teardown(() => fastify.close()) +}) + +test('Should return 431 if URI is too long', t => { + t.plan(3) + + const fastify = Fastify({ http: { maxHeaderSize } }) + fastify.route({ + method: 'GET', + url: '/', + handler: (_req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.listen({ port: 0 }, function (err) { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + `/${'a'.repeat(maxHeaderSize)}` + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 431) + }) + }) + + t.teardown(() => fastify.close()) +}) From 6c3e37cab2e35a533c65beb3df58cf857c7211c4 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 29 Jun 2023 20:33:10 +0200 Subject: [PATCH 0352/1295] chore: trigger the new website build (#4839) Co-authored-by: Frazer Smith --- .github/workflows/website.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 54699b65fe0..99ed11f65d1 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -6,6 +6,7 @@ on: - main paths: - 'docs/**' + - '**.md' release: types: - released @@ -19,8 +20,6 @@ jobs: steps: - name: Build website run: | - curl -X POST \ - -H 'Content-Type: application/json' \ - -H 'Circle-Token: ${{ secrets.circleci_token }}' \ - -d '{"branch":"master"}' \ - "https://circleci.com/api/v2/project/gh/fastify/website/pipeline" + gh workflow run ci-cd.yml -R fastify/website + env: + GH_TOKEN: ${{ secrets.GHA_WEBSITE_FINE_TOKEN }} From 3a52ca662a8ed2c010b82dd27964a3dd34f50c01 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 30 Jun 2023 11:30:45 +0200 Subject: [PATCH 0353/1295] Do not leak memory if .listen()` is not called (#4860) --- fastify.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index c6bce3b50f2..30e46a36c8f 100644 --- a/fastify.js +++ b/fastify.js @@ -426,10 +426,13 @@ function fastify (options) { router.closeRoutes() hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () { - if (fastify[kState].listening) { - // No new TCP connections are accepted - instance.server.close(done) + // No new TCP connections are accepted. + // We must call close on the server even if we are not listening + // otherwise memory will be leaked. + // https://github.com/nodejs/node/issues/48604 + instance.server.close(done) + if (fastify[kState].listening) { /* istanbul ignore next: Cannot test this without Node.js core support */ if (forceCloseConnections === 'idle') { // Not needed in Node 19 From 1b393adaa23a319c132b4083da385c37ba605caf Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Fri, 30 Jun 2023 14:47:06 +0200 Subject: [PATCH 0354/1295] Extend `decorate()` etc to enforce defined values (#4858) * feat: have `decorate()` etc enforce typed values Non-breaking backwards compatible enhancement of decorators. Should not have the issues of #4110 which was reverted in #4129 Signed-off-by: Pelle Wessman * test: test typed decoration properties * Add support + tests for `getter`/`setter` interface * Use the same definition for all decoration methods --------- Signed-off-by: Pelle Wessman --- test/types/instance.test-d.ts | 68 +++++++++++++++++++++++++++++++++++ types/instance.d.ts | 43 +++++++++++----------- 2 files changed, 91 insertions(+), 20 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index aa7df287f24..d1c497f2268 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -299,6 +299,22 @@ server.decorate<(x: string) => void>('test', function (x: string): void { server.decorate('test', function (x: string): void { expectType(this) }) +server.decorate('test', { + getter () { + expectType(this) + return 'foo' + } +}) +server.decorate('test', { + getter () { + expectType(this) + return 'foo' + }, + setter (x) { + expectType(x) + expectType(this) + } +}) server.decorateRequest<(x: string, y: number) => void>('test', function (x: string, y: number): void { expectType(this) @@ -318,6 +334,58 @@ expectError(server.decorate('test', true)) expectError(server.decorate<(myNumber: number) => number>('test', function (myNumber: number): string { return '' })) +expectError(server.decorate('test', { + getter () { + return true + } +})) +expectError(server.decorate('test', { + setter (x) {} +})) + +declare module '../../fastify' { + interface FastifyInstance { + typedTestProperty: boolean + typedTestPropertyGetterSetter: string + typedTestMethod (x: string): string + } +} +server.decorate('typedTestProperty', false) +server.decorate('typedTestProperty', { + getter () { + return false + } +}) +server.decorate('typedTestProperty', { + getter (): boolean { + return true + }, + setter (x) { + expectType(x) + expectType(this) + } +}) +expectError(server.decorate('typedTestProperty', 'foo')) +expectError(server.decorate('typedTestProperty', { + getter () { + return 'foo' + } +})) +server.decorate('typedTestMethod', function (x) { + expectType(x) + expectType(this) + return 'foo' +}) +server.decorate('typedTestMethod', x => x) +expectError(server.decorate('typedTestMethod', function (x: boolean) { + return 'foo' +})) +expectError(server.decorate('typedTestMethod', function (x) { + return true +})) +expectError(server.decorate('typedTestMethod', async function (x) { + return 'foo' +})) const versionConstraintStrategy = { name: 'version', diff --git a/types/instance.d.ts b/types/instance.d.ts index 01262f1d617..a7cce10efa6 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -85,6 +85,26 @@ export interface FastifyListenOptions { type NotInInterface = Key extends keyof _Interface ? never : Key type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2 +type GetterSetter = T | { + getter: (this: This) => T, + setter?: (this: This, value: T) => void +} + +type DecorationMethod = { + < + // Need to disable "no-use-before-define" to maintain backwards compatibility, as else decorate would suddenly mean something new + // eslint-disable-next-line no-use-before-define + T extends (P extends keyof This ? This[P] : unknown), + P extends string | symbol = string | symbol + >(property: P, + value: GetterSetter any + ? (this: This, ...args: Parameters) => ReturnType + : T + >, + dependencies?: string[] + ): Return; +} + /** * Fastify server instance. Returned by the core `fastify()` method. */ @@ -115,26 +135,9 @@ export interface FastifyInstance< close(closeListener: () => void): undefined; // should be able to define something useful with the decorator getter/setter pattern using Generics to enforce the users function returns what they expect it to - decorate(property: string | symbol, - value: T extends (...args: any[]) => any - ? (this: FastifyInstance, ...args: Parameters) => ReturnType - : T, - dependencies?: string[] - ): FastifyInstance; - - decorateRequest(property: string | symbol, - value: T extends (...args: any[]) => any - ? (this: FastifyRequest, ...args: Parameters) => ReturnType - : T, - dependencies?: string[] - ): FastifyInstance; - - decorateReply(property: string | symbol, - value: T extends (...args: any[]) => any - ? (this: FastifyReply, ...args: Parameters) => ReturnType - : T, - dependencies?: string[] - ): FastifyInstance; + decorate: DecorationMethod>; + decorateRequest: DecorationMethod>; + decorateReply: DecorationMethod>; hasDecorator(decorator: string | symbol): boolean; hasRequestDecorator(decorator: string | symbol): boolean; From 253506e0d5739fef3537e67c72eb34948e601a4f Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Fri, 30 Jun 2023 14:47:22 +0200 Subject: [PATCH 0355/1295] minor jsdoc fixes (#4855) * minor jsdoc fixes * Update lib/schema-controller.js Co-authored-by: James Sumners * Update lib/schema-controller.js Co-authored-by: James Sumners --------- Co-authored-by: James Sumners --- lib/reply.js | 2 +- lib/schema-controller.js | 12 ++++++------ lib/schemas.js | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/reply.js b/lib/reply.js index d8da8c2a785..18af968a096 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -859,7 +859,7 @@ function notFound (reply) { * @param {object} context the request context * @param {object} data the JSON payload to serialize * @param {number} statusCode the http status code - * @param {string} contentType the reply content type + * @param {string} [contentType] the reply content type * @returns {string} the serialized payload */ function serialize (context, data, statusCode, contentType) { diff --git a/lib/schema-controller.js b/lib/schema-controller.js index 8c746735ff4..29a5de5b09e 100644 --- a/lib/schema-controller.js +++ b/lib/schema-controller.js @@ -100,28 +100,28 @@ class SchemaController { /** * This method will be called when a validator must be setup. * Do not setup the compiler more than once - * @param {object} serverOptions: the fastify server option + * @param {object} serverOptions the fastify server options */ - setupValidator (serverOption) { + setupValidator (serverOptions) { const isReady = this.validatorCompiler !== undefined && !this.addedSchemas if (isReady) { return } - this.validatorCompiler = this.getValidatorBuilder()(this.schemaBucket.getSchemas(), serverOption.ajv) + this.validatorCompiler = this.getValidatorBuilder()(this.schemaBucket.getSchemas(), serverOptions.ajv) } /** * This method will be called when a serializer must be setup. * Do not setup the compiler more than once - * @param {object} serverOptions: the fastify server option + * @param {object} serverOptions the fastify server options */ - setupSerializer (serverOption) { + setupSerializer (serverOptions) { const isReady = this.serializerCompiler !== undefined && !this.addedSchemas if (isReady) { return } - this.serializerCompiler = this.getSerializerBuilder()(this.schemaBucket.getSchemas(), serverOption.serializerOpts) + this.serializerCompiler = this.getSerializerBuilder()(this.schemaBucket.getSchemas(), serverOptions.serializerOpts) } } diff --git a/lib/schemas.js b/lib/schemas.js index ac3caabaff2..077b977d410 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -147,8 +147,8 @@ function getSchemaAnyway (schema, jsonShorthand) { * * @param {object} context the request context * @param {number} statusCode the http status code - * @param {string} contentType the reply content type - * @returns {function|boolean} the right JSON Schema function to serialize + * @param {string} [contentType] the reply content type + * @returns {function|false} the right JSON Schema function to serialize * the reply or false if it is not set */ function getSchemaSerializer (context, statusCode, contentType) { From 7d02f769ee81abba336fd7a7988b0797a6948dfe Mon Sep 17 00:00:00 2001 From: Aadit Olkar <63646058+aadito123@users.noreply.github.com> Date: Fri, 30 Jun 2023 20:48:23 +0800 Subject: [PATCH 0356/1295] feat: Type narrow on status().send() chains with Reply generics (#4823) * initial type narrowing functionality * type narrow on wildcards * fix typo in tests and add docs * fix typo in test/types/reply.test-d.ts Co-authored-by: Uzlopak * Fix typo on reply.test-d.ts * renaming and refactoring * fixed failing test --------- Co-authored-by: Uzlopak --- docs/Reference/TypeScript.md | 25 +++++++++++++++++----- test/types/reply.test-d.ts | 40 ++++++++++++++++++++++++++++++++---- types/reply.d.ts | 16 +++++++++------ types/utils.d.ts | 24 ++++++++++++++++++++++ 4 files changed, 90 insertions(+), 15 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 5632acd6c2a..085b52205e5 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -111,7 +111,7 @@ generic types for route schemas and the dynamic properties located on the route-level `request` object. 1. If you did not complete the previous example, follow steps 1-4 to get set up. -2. Inside `index.ts`, define two interfaces `IQuerystring` and `IHeaders`: +2. Inside `index.ts`, define three interfaces `IQuerystring`,`IHeaders` and `IReply`: ```typescript interface IQuerystring { username: string; @@ -121,8 +121,14 @@ route-level `request` object. interface IHeaders { 'h-Custom': string; } + + interface IReply { + 200: { success: boolean }; + 302: { url: string }; + '4xx': { error: string }; + } ``` -3. Using the two interfaces, define a new API route and pass them as generics. +3. Using the three interfaces, define a new API route and pass them as generics. The shorthand route methods (i.e. `.get`) accept a generic object `RouteGenericInterface` containing five named properties: `Body`, `Querystring`, `Params`, `Headers` and `Reply`. The interfaces `Body`, @@ -132,12 +138,20 @@ route-level `request` object. ```typescript server.get<{ Querystring: IQuerystring, - Headers: IHeaders + Headers: IHeaders, + Reply: IReply }>('/auth', async (request, reply) => { const { username, password } = request.query const customerHeader = request.headers['h-Custom'] // do something with request data + // chaining .statusCode/.code calls with .send allows type narrowing. For example: + // this works + reply.code(200).send({ success: true }); + // but this gives a type error + reply.code(200).send('uh-oh'); + // it even works for wildcards + reply.code(404).send({ error: 'Not found' }); return `logged in!` }) ``` @@ -154,7 +168,8 @@ route-level `request` object. ```typescript server.get<{ Querystring: IQuerystring, - Headers: IHeaders + Headers: IHeaders, + Reply: IReply }>('/auth', { preValidation: (request, reply, done) => { const { username, password } = request.query @@ -172,7 +187,7 @@ route-level `request` object. admin"}` 🎉 Good work, now you can define interfaces for each route and have strictly -typed request and reply instances. Other parts of the Fastify type system rely +typed request and reply instances. Other parts of the Fastify type system rely on generic properties. Make sure to reference the detailed type system documentation below to learn more about what is available. diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 006a8bea8d1..ac50fe90232 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,12 +1,14 @@ import { expectType, expectError, expectAssignable } from 'tsd' -import fastify, { RouteHandlerMethod, RouteHandler, RawRequestDefaultExpression, FastifyContext, FastifyContextConfig, FastifyRequest, FastifyReply } from '../../fastify' +import fastify, { RouteHandlerMethod, RouteHandler, RawRequestDefaultExpression, FastifyContext, FastifyContextConfig, FastifyRequest, FastifyReply, FastifySchema, FastifyTypeProviderDefault } from '../../fastify' import { RawServerDefault, RawReplyDefaultExpression, ContextConfigDefault } from '../../types/utils' import { FastifyLoggerInstance } from '../../types/logger' import { RouteGenericInterface } from '../../types/route' import { FastifyInstance } from '../../types/instance' import { Buffer } from 'buffer' +import { ReplyTypeConstrainer } from '../../types/reply' -type DefaultSerializationFunction = (payload: {[key: string]: unknown}) => string +type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string +type DefaultFastifyReplyWithCode = FastifyReply> const getHandler: RouteHandlerMethod = function (_request, reply) { expectType(reply.raw) @@ -14,8 +16,8 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType(reply.context.config) expectType(reply.log) expectType>(reply.request) - expectType<(statusCode: number) => FastifyReply>(reply.code) - expectType<(statusCode: number) => FastifyReply>(reply.status) + expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) + expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.status) expectType(reply.statusCode) expectType(reply.sent) expectType<((payload?: unknown) => FastifyReply)>(reply.send) @@ -58,6 +60,15 @@ interface ReplyUnion { } } +interface ReplyHttpCodes { + Reply: { + '1xx': number, + 200: 'abc', + 201: boolean, + 300: { foo: string }, + } +} + const typedHandler: RouteHandler = async (request, reply) => { expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.send) } @@ -103,3 +114,24 @@ expectError(server.get('/get-generic-union-return-error-1', async fu expectError(server.get('/get-generic-union-return-error-2', async function handler (request, reply) { return { error: 500 } })) +server.get('/get-generic-http-codes-send', async function handler (request, reply) { + reply.code(200).send('abc') + reply.code(201).send(true) + reply.code(300).send({ foo: 'bar' }) + reply.code(101).send(123) +}) +expectError(server.get('/get-generic-http-codes-send-error-1', async function handler (request, reply) { + reply.code(200).send('def') +})) +expectError(server.get('/get-generic-http-codes-send-error-2', async function handler (request, reply) { + reply.code(201).send(0) +})) +expectError(server.get('/get-generic-http-codes-send-error-3', async function handler (request, reply) { + reply.code(300).send({ foo: 123 }) +})) +expectError(server.get('/get-generic-http-codes-send-error-4', async function handler (request, reply) { + reply.code(100).send('asdasd') +})) +expectError(server.get('/get-generic-http-codes-send-error-5', async function handler (request, reply) { + reply.code(401).send({ foo: 123 }) +})) diff --git a/types/reply.d.ts b/types/reply.d.ts index c3ab401d70a..1b0ba294d98 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -1,17 +1,21 @@ -import { RawReplyDefaultExpression, RawServerBase, RawServerDefault, ContextConfigDefault, RawRequestDefaultExpression, ReplyDefault } from './utils' -import { FastifyReplyType, ResolveFastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' +import { Buffer } from 'buffer' import { FastifyContext } from './context' +import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' -import { FastifyInstance } from './instance' import { FastifySchema } from './schema' -import { Buffer } from 'buffer' +import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider' +import { CodeToReplyKey, ContextConfigDefault, ReplyKeysToCodes, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault } from './utils' export interface ReplyGenericInterface { Reply?: ReplyDefault; } +export type ReplyTypeConstrainer> = + Code extends keyof RouteGenericReply ? RouteGenericReply[Code] : + CodeToReplyKey extends keyof RouteGenericReply ? RouteGenericReply[CodeToReplyKey] : + unknown; /** * FastifyReply is an instance of the standard http or http2 reply types. * It defaults to http.ServerResponse, and it also extends the relative reply object. @@ -31,8 +35,8 @@ export interface FastifyReply< log: FastifyBaseLogger; request: FastifyRequest; server: FastifyInstance; - code(statusCode: number): FastifyReply; - status(statusCode: number): FastifyReply; + code>(statusCode: Code): FastifyReply>; + status>(statusCode: Code): FastifyReply>; statusCode: number; sent: boolean; send(payload?: ReplyType): FastifyReply; diff --git a/types/utils.d.ts b/types/utils.d.ts index 425d933152c..8caa3070470 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -45,3 +45,27 @@ export type RequestHeadersDefault = unknown export type ContextConfigDefault = unknown export type ReplyDefault = unknown + +/** + * Helpers for determining the type of the response payload based on the code + */ + +type StringAsNumber = T extends `${infer N extends number}` ? N : never; +type CodeClasses = 1 | 2 | 3 | 4 | 5; +type Digit = 0 |1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; +type HttpCodes = StringAsNumber<`${CodeClasses}${Digit}${Digit}`>; +type HttpKeys = HttpCodes | `${Digit}xx`; +export type StatusCodeReply = { + // eslint-disable-next-line no-unused-vars + [Key in HttpKeys]?: unknown; +}; + +// weird TS quirk: https://stackoverflow.com/questions/58977876/generic-conditional-type-resolves-to-never-when-the-generic-type-is-set-to-never +export type ReplyKeysToCodes = [Key] extends [never] ? number : + Key extends HttpCodes ? Key : + Key extends `${infer X extends CodeClasses}xx` ? + StringAsNumber<`${X}${Digit}${Digit}`> : number; + +export type CodeToReplyKey = `${Code}` extends `${infer FirstDigit extends CodeClasses}${number}` + ? `${FirstDigit}xx` + : never; From 6684a3775fdc8aa38f765540000ae97699323b23 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 30 Jun 2023 14:55:06 +0200 Subject: [PATCH 0357/1295] Bumped v4.19.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 30e46a36c8f..af3a50ced89 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.18.0' +const VERSION = '4.19.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 34537f44118..f2dff12626e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.18.0", + "version": "4.19.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 97e96aead2e3877352be8028021bb2b9efe3ed7f Mon Sep 17 00:00:00 2001 From: Jon Webb Date: Fri, 30 Jun 2023 18:20:48 -0400 Subject: [PATCH 0358/1295] Fix typo in docs/Reference/TypeScript.md (#4861) --- docs/Reference/TypeScript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 085b52205e5..7148d016067 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -508,7 +508,7 @@ Fastify Plugin in a TypeScript Project. `"compilerOptions"` object. ```json { - "compileOptions": { + "compilerOptions": { "declaration": true } } From 98ca4fec817169cdd9d6bc50a1b320bbb7a97545 Mon Sep 17 00:00:00 2001 From: Ekott2006 <84778161+Ekott2006@users.noreply.github.com> Date: Sat, 1 Jul 2023 07:47:47 +0100 Subject: [PATCH 0359/1295] docs(testing): add plugin testing guide (#4849) --- docs/Guides/Testing.md | 137 +++++++++++++++++++++++++++++++++++- docs/Guides/Write-Plugin.md | 4 +- 2 files changed, 138 insertions(+), 3 deletions(-) diff --git a/docs/Guides/Testing.md b/docs/Guides/Testing.md index 421a1e16e3a..7101ffd6430 100644 --- a/docs/Guides/Testing.md +++ b/docs/Guides/Testing.md @@ -1,12 +1,15 @@ -

Fastify

+

Fastify

-## Testing +# Testing + Testing is one of the most important parts of developing an application. Fastify is very flexible when it comes to testing and is compatible with most testing frameworks (such as [Tap](https://www.npmjs.com/package/tap), which is used in the examples below). +## Application + Let's `cd` into a fresh directory called 'testing-example' and type `npm init -y` in our terminal. @@ -345,3 +348,133 @@ test('should ...', {only: true}, t => ...) Now you should be able to step through your test file (and the rest of `Fastify`) in your code editor. + + + +## Plugins +Let's `cd` into a fresh directory called 'testing-plugin-example' and type `npm init +-y` in our terminal. + +Run `npm i fastify fastify-plugin && npm i tap -D` + +**plugin/myFirstPlugin.js**: + +```js +const fP = require("fastify-plugin") + +async function myPlugin(fastify, options) { + fastify.decorateRequest("helloRequest", "Hello World") + fastify.decorate("helloInstance", "Hello Fastify Instance") +} + +module.exports = fP(myPlugin) +``` + +A basic example of a Plugin. See [Plugin Guide](./Plugins-Guide.md) + +**test/myFirstPlugin.test.js**: + +```js +const Fastify = require("fastify"); +const tap = require("tap"); +const myPlugin = require("../plugin/myFirstPlugin"); + +tap.test("Test the Plugin Route", async t => { + // Create a mock fastify application to test the plugin + const fastify = Fastify() + + fastify.register(myPlugin) + + // Add an endpoint of your choice + fastify.get("/", async (request, reply) => { + return ({ message: request.helloRequest }) + }) + + // Use fastify.inject to fake a HTTP Request + const fastifyResponse = await fastify.inject({ + method: "GET", + url: "/" + }) + + console.log('status code: ', fastifyResponse.statusCode) + console.log('body: ', fastifyResponse.body) +}) +``` +Learn more about [```fastify.inject()```](#benefits-of-using-fastifyinject). +Run the test file in your terminal `node test/myFirstPlugin.test.js` + +```sh +status code: 200 +body: {"message":"Hello World"} +``` + +Now we can replace our `console.log` calls with actual tests! + +In your `package.json` change the "test" script to: + +`"test": "tap --reporter=list --watch"` + +Create the tap test for the endpoint. + +**test/myFirstPlugin.test.js**: + +```js +const Fastify = require("fastify"); +const tap = require("tap"); +const myPlugin = require("../plugin/myFirstPlugin"); + +tap.test("Test the Plugin Route", async t => { + // Specifies the number of test + t.plan(2) + + const fastify = Fastify() + + fastify.register(myPlugin) + + fastify.get("/", async (request, reply) => { + return ({ message: request.helloRequest }) + }) + + const fastifyResponse = await fastify.inject({ + method: "GET", + url: "/" + }) + + t.equal(fastifyResponse.statusCode, 200) + t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" }) +}) +``` + +Finally, run `npm test` in the terminal and see your test results! + +Test the ```.decorate()``` and ```.decorateRequest()```. + +**test/myFirstPlugin.test.js**: + +```js +const Fastify = require("fastify"); +const tap = require("tap"); +const myPlugin = require("../plugin/myFirstPlugin"); + +tap.test("Test the Plugin Route", async t => { + t.plan(5) + const fastify = Fastify() + + fastify.register(myPlugin) + + fastify.get("/", async (request, reply) => { + // Testing the fastify decorators + t.not(request.helloRequest, null) + t.ok(request.helloRequest, "Hello World") + t.ok(fastify.helloInstance, "Hello Fastify Instance") + return ({ message: request.helloRequest }) + }) + + const fastifyResponse = await fastify.inject({ + method: "GET", + url: "/" + }) + t.equal(fastifyResponse.statusCode, 200) + t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" }) +}) +``` \ No newline at end of file diff --git a/docs/Guides/Write-Plugin.md b/docs/Guides/Write-Plugin.md index 8cde23730b2..eae024b62ed 100644 --- a/docs/Guides/Write-Plugin.md +++ b/docs/Guides/Write-Plugin.md @@ -1,4 +1,4 @@ -

Fastify

+

Fastify

# How to write a good plugin First, thank you for deciding to write a plugin for Fastify. Fastify is a @@ -63,6 +63,8 @@ among different versions of its dependencies. We do not enforce any testing library. We use [`tap`](https://www.node-tap.org/) since it offers out-of-the-box parallel testing and code coverage, but it is up to you to choose your library of preference. +We highly recommend you read the [Plugin Testing](./Testing.md#plugins) to +learn about how to test your plugins. ## Code Linter It is not mandatory, but we highly recommend you use a code linter in your From fd62068892b7312fcb8552834fbf60a035442ad3 Mon Sep 17 00:00:00 2001 From: sher Date: Sat, 1 Jul 2023 18:48:55 +0900 Subject: [PATCH 0360/1295] fix platformatic-cloud anchor id (#4863) --- docs/Guides/Serverless.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 685ce50bc03..4c057aa6898 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -28,7 +28,7 @@ snippet of code. - [Google Cloud Functions](#google-cloud-functions) - [Google Cloud Run](#google-cloud-run) - [Netlify Lambda](#netlify-lambda) -- [Platformatic Cloud](#platformatic) +- [Platformatic Cloud](#platformatic-cloud) - [Vercel](#vercel) ## AWS From 4c532d7921cfe9c85de38089cc5203d6aa90355c Mon Sep 17 00:00:00 2001 From: Aadit Olkar <63646058+aadito123@users.noreply.github.com> Date: Sun, 2 Jul 2023 17:27:17 +0800 Subject: [PATCH 0361/1295] fix: Type narrow fix issue from PR #4823 (#4869) --- test/types/reply.test-d.ts | 1 + types/reply.d.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index ac50fe90232..0420a792447 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -18,6 +18,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType>(reply.request) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.status) + expectType<(payload?: unknown) => FastifyReply>(reply.code(100 as number).send) expectType(reply.statusCode) expectType(reply.sent) expectType<((payload?: unknown) => FastifyReply)>(reply.send) diff --git a/types/reply.d.ts b/types/reply.d.ts index 1b0ba294d98..cbfe7d78675 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -12,10 +12,11 @@ export interface ReplyGenericInterface { Reply?: ReplyDefault; } -export type ReplyTypeConstrainer> = +export type ReplyTypeConstrainer, ReplyKey = CodeToReplyKey> = Code extends keyof RouteGenericReply ? RouteGenericReply[Code] : - CodeToReplyKey extends keyof RouteGenericReply ? RouteGenericReply[CodeToReplyKey] : - unknown; + [ReplyKey] extends [never] ? unknown : + ReplyKey extends keyof RouteGenericReply ? RouteGenericReply[ReplyKey] : + unknown; /** * FastifyReply is an instance of the standard http or http2 reply types. * It defaults to http.ServerResponse, and it also extends the relative reply object. From 5b7fd1835cc4f8efb52a6946677da37d8c1561c1 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 2 Jul 2023 14:44:26 +0200 Subject: [PATCH 0362/1295] Bumped v4.19.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index af3a50ced89..25d092a8a56 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.19.0' +const VERSION = '4.19.1' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index f2dff12626e..c7dd4c5591a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.19.0", + "version": "4.19.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 2d9e695839bc201fa639626731c61b12d2a9074b Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Mon, 3 Jul 2023 15:27:00 +0800 Subject: [PATCH 0363/1295] fix(typescript): route config should not pass url and method (#4872) --- test/types/hooks.test-d.ts | 129 ++++++++++++++++++----------------- test/types/reply.test-d.ts | 14 ++-- test/types/request.test-d.ts | 35 +++++----- test/types/route.test-d.ts | 14 ++-- types/context.d.ts | 6 +- types/reply.d.ts | 2 +- types/request.d.ts | 10 +-- types/route.d.ts | 14 ++-- 8 files changed, 112 insertions(+), 112 deletions(-) diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index b6d654090ae..f29543aaa00 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -1,21 +1,21 @@ import { FastifyError } from '@fastify/error' import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { + ContextConfigDefault, FastifyContextConfig, FastifyInstance, + FastifyPluginOptions, FastifyReply, FastifyRequest, + FastifySchema, + FastifyTypeProviderDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, - RouteOptions, + RawServerDefault, RegisterOptions, - FastifyPluginOptions, - FastifySchema, - FastifyTypeProviderDefault, - ContextConfigDefault, FastifyContextConfig, RawServerDefault + RouteOptions } from '../../fastify' -import { preHandlerAsyncHookHandler, RequestPayload } from '../../types/hooks' -import { RouteGenericInterface } from '../../types/route' -import { ResolveFastifyRequestType } from '../../types/type-provider' +import { RequestPayload, preHandlerAsyncHookHandler } from '../../types/hooks' +import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' const server = fastify() @@ -252,89 +252,90 @@ type CustomContextConfig = FastifyContextConfig & { foo: string; bar: number; } +type CustomContextConfigWithDefault = CustomContextConfig & FastifyRouteConfig server.route({ method: 'GET', url: '/', handler: () => { }, onRequest: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preParsing: (request, reply, payload, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preValidation: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preHandler: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preSerialization: (request, reply, payload, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onSend: (request, reply, payload, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onResponse: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onTimeout: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onError: (request, reply, error, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) } }) server.get('/', { onRequest: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preParsing: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preValidation: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preHandler: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preSerialization: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onSend: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onResponse: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onTimeout: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onError: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) } }, async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }) type CustomContextRequest = FastifyRequest @@ -344,40 +345,40 @@ server.route({ url: '/', handler: () => { }, onRequest: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preParsing: async (request: CustomContextRequest, reply: CustomContextReply, payload: RequestPayload) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preValidation: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preHandler: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, preSerialization: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onSend: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onResponse: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onTimeout: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) }, onError: async (request: CustomContextRequest, reply: CustomContextReply, error: FastifyError) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.context.config) + expectType(reply.context.config) } }) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 0420a792447..9df4c1d001d 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,11 +1,11 @@ -import { expectType, expectError, expectAssignable } from 'tsd' -import fastify, { RouteHandlerMethod, RouteHandler, RawRequestDefaultExpression, FastifyContext, FastifyContextConfig, FastifyRequest, FastifyReply, FastifySchema, FastifyTypeProviderDefault } from '../../fastify' -import { RawServerDefault, RawReplyDefaultExpression, ContextConfigDefault } from '../../types/utils' -import { FastifyLoggerInstance } from '../../types/logger' -import { RouteGenericInterface } from '../../types/route' -import { FastifyInstance } from '../../types/instance' import { Buffer } from 'buffer' +import { expectAssignable, expectError, expectType } from 'tsd' +import fastify, { FastifyContext, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' +import { FastifyInstance } from '../../types/instance' +import { FastifyLoggerInstance } from '../../types/logger' import { ReplyTypeConstrainer } from '../../types/reply' +import { RouteGenericInterface } from '../../types/route' +import { ContextConfigDefault, RawReplyDefaultExpression, RawServerDefault } from '../../types/utils' type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string type DefaultFastifyReplyWithCode = FastifyReply> @@ -13,7 +13,7 @@ type DefaultFastifyReplyWithCode = FastifyReply(reply.raw) expectType>(reply.context) - expectType(reply.context.config) + expectType['config']>(reply.context.config) expectType(reply.log) expectType>(reply.request) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 783080e7676..34b114a6e13 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -1,26 +1,25 @@ -import { expectAssignable, expectType } from 'tsd' import pino from 'pino' +import { expectAssignable, expectType } from 'tsd' import fastify, { - RouteHandler, - RawRequestDefaultExpression, - RequestBodyDefault, - RequestGenericInterface, - FastifyContext, ContextConfigDefault, - FastifyContextConfig, + FastifyContext, FastifyLogFn, - RouteHandlerMethod, - RawServerDefault, - RawReplyDefaultExpression, FastifySchema, - FastifyTypeProviderDefault + FastifyTypeProviderDefault, + RawReplyDefaultExpression, + RawRequestDefaultExpression, + RawServerDefault, + RequestBodyDefault, + RequestGenericInterface, + RouteHandler, + RouteHandlerMethod } from '../../fastify' -import { RequestParamsDefault, RequestHeadersDefault, RequestQuerystringDefault } from '../../types/utils' +import { FastifyInstance } from '../../types/instance' import { FastifyLoggerInstance } from '../../types/logger' -import { FastifyRequest, RequestRouteOptions } from '../../types/request' import { FastifyReply } from '../../types/reply' -import { FastifyInstance } from '../../types/instance' +import { FastifyRequest, RequestRouteOptions } from '../../types/request' import { RouteGenericInterface } from '../../types/route' +import { RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from '../../types/utils' interface RequestBody { content: string; @@ -76,8 +75,8 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.body) expectType(request.params) expectType>(request.context) - expectType(request.context.config) - expectType(request.routeConfig) + expectType['config']>(request.context.config) + expectType['config']>(request.routeConfig) expectType(request.routeSchema) expectType(request.headers) @@ -110,7 +109,7 @@ const postHandler: Handler = function (request) { expectType(request.headers['x-foobar']) expectType(request.server) expectType>(request.context) - expectType(request.context.config) + expectType['config']>(request.context.config) } function putHandler (request: CustomRequest, reply: FastifyReply) { @@ -128,7 +127,7 @@ function putHandler (request: CustomRequest, reply: FastifyReply) { expectType(request.headers['x-foobar']) expectType(request.server) expectType>(request.context) - expectType(request.context.config) + expectType['config']>(request.context.config) } const server = fastify() diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 1eb232ba4bb..eae73058d85 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -1,9 +1,9 @@ -import fastify, { FastifyInstance, FastifyRequest, FastifyReply, RouteHandlerMethod } from '../../fastify' -import { expectType, expectError, expectAssignable, printType } from 'tsd' -import { HTTPMethods } from '../../types/utils' +import { FastifyError } from '@fastify/error' import * as http from 'http' +import { expectAssignable, expectError, expectType } from 'tsd' +import fastify, { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from '../../fastify' import { RequestPayload } from '../../types/hooks' -import { FastifyError } from '@fastify/error' +import { HTTPMethods } from '../../types/utils' /* * Testing Fastify HTTP Routes and Route Shorthands. @@ -74,7 +74,7 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' Headers: HeadersInterface; } - fastify()[lowerCaseMethod]('/', { config: { foo: 'bar', bar: 100, extra: true, url: '/', method: lowerCaseMethod } }, (req, res) => { + fastify()[lowerCaseMethod]('/', { config: { foo: 'bar', bar: 100, extra: true } }, (req, res) => { expectType(req.body) expectType(req.query) expectType(req.params) @@ -94,7 +94,7 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' fastify().route({ url: '/', method: method as HTTPMethods, - config: { foo: 'bar', bar: 100, url: '/', method: method as HTTPMethods }, + config: { foo: 'bar', bar: 100 }, prefixTrailingSlash: 'slash', onRequest: (req, res, done) => { // these handlers are tested in `hooks.test-d.ts` expectType(req.body) @@ -231,7 +231,7 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' fastify().route({ url: '/', method: method as HTTPMethods, - config: { foo: 'bar', bar: 100, url: '/', method: method as HTTPMethods }, + config: { foo: 'bar', bar: 100 }, prefixTrailingSlash: 'slash', onRequest: async (req, res, done) => { // these handlers are tested in `hooks.test-d.ts` expectType(req.body) diff --git a/types/context.d.ts b/types/context.d.ts index f431969afbd..0ebd05c2c20 100644 --- a/types/context.d.ts +++ b/types/context.d.ts @@ -1,8 +1,8 @@ -import { ContextConfigDefault } from './utils' import { FastifyRouteConfig } from './route' +import { ContextConfigDefault } from './utils' // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface FastifyContextConfig extends FastifyRouteConfig { +export interface FastifyContextConfig { } /** @@ -12,5 +12,5 @@ export interface FastifyContext { /** * @deprecated Use Request#routeConfig or Request#routeSchema instead */ - config: FastifyContextConfig & ContextConfig; + config: FastifyContextConfig & FastifyRouteConfig & ContextConfig; } diff --git a/types/reply.d.ts b/types/reply.d.ts index cbfe7d78675..4ab04c35833 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -6,7 +6,7 @@ import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider' -import { CodeToReplyKey, ContextConfigDefault, ReplyKeysToCodes, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault } from './utils' +import { CodeToReplyKey, ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils' export interface ReplyGenericInterface { Reply?: ReplyDefault; diff --git a/types/request.d.ts b/types/request.d.ts index d0cbbe05ee2..bd75639fad9 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,11 +1,11 @@ import { ErrorObject } from '@fastify/ajv-compiler' +import { FastifyContext } from './context' +import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' -import { ContextConfigDefault, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './utils' import { RouteGenericInterface } from './route' -import { FastifyInstance } from './instance' -import { FastifyTypeProvider, FastifyTypeProviderDefault, FastifyRequestType, ResolveFastifyRequestType } from './type-provider' import { FastifySchema } from './schema' -import { FastifyContext, FastifyContextConfig } from './context' +import { FastifyRequestType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyRequestType } from './type-provider' +import { ContextConfigDefault, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './utils' type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' export interface RequestGenericInterface { @@ -59,7 +59,7 @@ export interface FastifyRequest; - routeConfig: FastifyContextConfig & ContextConfig; + routeConfig: FastifyContext['config']; routeSchema: FastifySchema /** in order for this to be used the user should ensure they have set the attachValidation option. */ diff --git a/types/route.d.ts b/types/route.d.ts index beabc41965a..8c511a37d82 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -1,17 +1,17 @@ +import { FastifyError } from '@fastify/error' +import { FastifyContext } from './context' +import { onErrorHookHandler, onRequestAbortHookHandler, onRequestHookHandler, onResponseHookHandler, onSendHookHandler, onTimeoutHookHandler, preHandlerHookHandler, preParsingHookHandler, preSerializationHookHandler, preValidationHookHandler } from './hooks' import { FastifyInstance } from './instance' -import { FastifyRequest, RequestGenericInterface } from './request' +import { FastifyBaseLogger, LogLevel } from './logger' import { FastifyReply, ReplyGenericInterface } from './reply' +import { FastifyRequest, RequestGenericInterface } from './request' import { FastifySchema, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorFormatter } from './schema' -import { HTTPMethods, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' -import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler, onRequestAbortHookHandler } from './hooks' -import { FastifyError } from '@fastify/error' -import { FastifyContext } from './context' import { FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyReturnType } from './type-provider' -import { FastifyBaseLogger, LogLevel } from './logger' +import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' export interface FastifyRouteConfig { url: string; @@ -41,7 +41,7 @@ export interface RouteShorthandOptions< serializerCompiler?: FastifySerializerCompiler; bodyLimit?: number; logLevel?: LogLevel; - config?: FastifyContext['config']; + config?: Omit['config'], 'url' | 'method'>; version?: string; constraints?: { [name: string]: any }, prefixTrailingSlash?: 'slash'|'no-slash'|'both'; From 956bc05cfcd816e5b40ca1aea8cb45c9729f0711 Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Mon, 3 Jul 2023 14:36:12 +0200 Subject: [PATCH 0364/1295] Overload `DecorationMethod` to fix #4870 (#4874) * Overload `DecorationMethod` to fix #4870 Signed-off-by: Pelle Wessman * Add additional tests for other decorators * Test all of `DecorationMethod` on all decorators * Allow `decorate('name', null)` for now --------- Signed-off-by: Pelle Wessman --- test/types/instance.test-d.ts | 102 ++++++++++++++++++++++++++++++++++ types/instance.d.ts | 6 ++ 2 files changed, 108 insertions(+) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index d1c497f2268..0a0b60192cc 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -315,6 +315,8 @@ server.decorate('test', { expectType(this) } }) +server.decorate('test') +server.decorate('test', null, ['foo']) server.decorateRequest<(x: string, y: number) => void>('test', function (x: string, y: number): void { expectType(this) @@ -322,6 +324,8 @@ server.decorateRequest<(x: string, y: number) => void>('test', function (x: stri server.decorateRequest('test', function (x: string, y: number): void { expectType(this) }) +server.decorateRequest('test') +server.decorateRequest('test', null, ['foo']) server.decorateReply<(x: string) => void>('test', function (x: string): void { expectType(this) @@ -329,6 +333,8 @@ server.decorateReply<(x: string) => void>('test', function (x: string): void { server.decorateReply('test', function (x: string): void { expectType(this) }) +server.decorateReply('test') +server.decorateReply('test', null, ['foo']) expectError(server.decorate('test', true)) expectError(server.decorate<(myNumber: number) => number>('test', function (myNumber: number): string { @@ -349,7 +355,20 @@ declare module '../../fastify' { typedTestPropertyGetterSetter: string typedTestMethod (x: string): string } + + interface FastifyRequest { + typedTestRequestProperty: boolean + typedTestRequestPropertyGetterSetter: string + typedTestRequestMethod (x: string): string + } + + interface FastifyReply { + typedTestReplyProperty: boolean + typedTestReplyPropertyGetterSetter: string + typedTestReplyMethod (x: string): string + } } + server.decorate('typedTestProperty', false) server.decorate('typedTestProperty', { getter () { @@ -365,6 +384,9 @@ server.decorate('typedTestProperty', { expectType(this) } }) +server.decorate('typedTestProperty') +server.decorate('typedTestProperty', null, ['foo']) +server.decorate('typedTestProperty', null) expectError(server.decorate('typedTestProperty', 'foo')) expectError(server.decorate('typedTestProperty', { getter () { @@ -387,6 +409,86 @@ expectError(server.decorate('typedTestMethod', async function (x) { return 'foo' })) +server.decorateRequest('typedTestRequestProperty', false) +server.decorateRequest('typedTestRequestProperty', { + getter () { + return false + } +}) +server.decorateRequest('typedTestRequestProperty', { + getter (): boolean { + return true + }, + setter (x) { + expectType(x) + expectType(this) + } +}) +server.decorateRequest('typedTestRequestProperty') +server.decorateRequest('typedTestRequestProperty', null, ['foo']) +server.decorateRequest('typedTestRequestProperty', null) +expectError(server.decorateRequest('typedTestRequestProperty', 'foo')) +expectError(server.decorateRequest('typedTestRequestProperty', { + getter () { + return 'foo' + } +})) +server.decorateRequest('typedTestRequestMethod', function (x) { + expectType(x) + expectType(this) + return 'foo' +}) +server.decorateRequest('typedTestRequestMethod', x => x) +expectError(server.decorateRequest('typedTestRequestMethod', function (x: boolean) { + return 'foo' +})) +expectError(server.decorateRequest('typedTestRequestMethod', function (x) { + return true +})) +expectError(server.decorateRequest('typedTestRequestMethod', async function (x) { + return 'foo' +})) + +server.decorateReply('typedTestReplyProperty', false) +server.decorateReply('typedTestReplyProperty', { + getter () { + return false + } +}) +server.decorateReply('typedTestReplyProperty', { + getter (): boolean { + return true + }, + setter (x) { + expectType(x) + expectType(this) + } +}) +server.decorateReply('typedTestReplyProperty') +server.decorateReply('typedTestReplyProperty', null, ['foo']) +server.decorateReply('typedTestReplyProperty', null) +expectError(server.decorateReply('typedTestReplyProperty', 'foo')) +expectError(server.decorateReply('typedTestReplyProperty', { + getter () { + return 'foo' + } +})) +server.decorateReply('typedTestReplyMethod', function (x) { + expectType(x) + expectType(this) + return 'foo' +}) +server.decorateReply('typedTestReplyMethod', x => x) +expectError(server.decorateReply('typedTestReplyMethod', function (x: boolean) { + return 'foo' +})) +expectError(server.decorateReply('typedTestReplyMethod', function (x) { + return true +})) +expectError(server.decorateReply('typedTestReplyMethod', async function (x) { + return 'foo' +})) + const versionConstraintStrategy = { name: 'version', storage: () => ({ diff --git a/types/instance.d.ts b/types/instance.d.ts index a7cce10efa6..1a2afc9d5b1 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -103,6 +103,12 @@ type DecorationMethod = { >, dependencies?: string[] ): Return; + + (property: string | symbol): Return; + + (property: string | symbol, value: null): Return; + + (property: string | symbol, value: null|undefined, dependencies: string[]): Return; } /** From e3a2812213414bda4b801fa3c0c44daddec6837d Mon Sep 17 00:00:00 2001 From: Aadit Olkar <63646058+aadito123@users.noreply.github.com> Date: Mon, 3 Jul 2023 21:27:04 +0800 Subject: [PATCH 0365/1295] fix: Type inference breaking when Reply generic is not passed (#4875) * initial type narrowing functionality * type narrow on wildcards * fix typo in tests and add docs * fix typo in test/types/reply.test-d.ts Co-authored-by: Uzlopak * Fix typo on reply.test-d.ts * renaming and refactoring * fixed failing test * fix broken narrowing on number type statusCodes * fixed type narrow on number type status codes * fix type inferrence breaking without Reply generic --------- Co-authored-by: Uzlopak --- test/types/reply.test-d.ts | 6 +++--- test/types/type-provider.test-d.ts | 4 ++++ types/reply.d.ts | 11 ++++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 9df4c1d001d..9e50e8374b1 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,14 +1,14 @@ import { Buffer } from 'buffer' import { expectAssignable, expectError, expectType } from 'tsd' -import fastify, { FastifyContext, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' +import fastify, { FastifyContext, FastifyReply, FastifyRequest, FastifySchema, FastifySchemaCompiler, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' import { FastifyInstance } from '../../types/instance' import { FastifyLoggerInstance } from '../../types/logger' -import { ReplyTypeConstrainer } from '../../types/reply' +import { ResolveReplyTypeWithRouteGeneric } from '../../types/reply' import { RouteGenericInterface } from '../../types/route' import { ContextConfigDefault, RawReplyDefaultExpression, RawServerDefault } from '../../types/utils' type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string -type DefaultFastifyReplyWithCode = FastifyReply> +type DefaultFastifyReplyWithCode = FastifyReply> const getHandler: RouteHandlerMethod = function (_request, reply) { expectType(reply.raw) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index c9fb9f1212b..ae7ad228c3b 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -299,6 +299,8 @@ expectAssignable(server.withTypeProvider().get( res.send('hello') res.send(42) res.send({ error: 'error' }) + expectType<(payload?: string | number | { error: string }) => typeof res>(res.code(200).send) + expectError<(payload?: unknown) => typeof res>(res.code(200).send) } )) @@ -527,6 +529,8 @@ expectAssignable(server.withTypeProvider().get( res.send('hello') res.send(42) res.send({ error: 'error' }) + expectType<(payload?: string | number | { [x: string]: unknown, error?: string | undefined }) => typeof res>(res.code(200).send) + expectError<(payload?: unknown) => typeof res>(res.code(200).send) } )) diff --git a/types/reply.d.ts b/types/reply.d.ts index 4ab04c35833..094f7e55bd9 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -12,11 +12,16 @@ export interface ReplyGenericInterface { Reply?: ReplyDefault; } -export type ReplyTypeConstrainer, ReplyKey = CodeToReplyKey> = +type ReplyTypeConstrainer, ReplyKey = CodeToReplyKey> = Code extends keyof RouteGenericReply ? RouteGenericReply[Code] : [ReplyKey] extends [never] ? unknown : ReplyKey extends keyof RouteGenericReply ? RouteGenericReply[ReplyKey] : unknown; + +export type ResolveReplyTypeWithRouteGeneric, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> = + ResolveFastifyReplyType }> /** * FastifyReply is an instance of the standard http or http2 reply types. * It defaults to http.ServerResponse, and it also extends the relative reply object. @@ -36,8 +41,8 @@ export interface FastifyReply< log: FastifyBaseLogger; request: FastifyRequest; server: FastifyInstance; - code>(statusCode: Code): FastifyReply>; - status>(statusCode: Code): FastifyReply>; + code>(statusCode: Code): FastifyReply>; + status>(statusCode: Code): FastifyReply>; statusCode: number; sent: boolean; send(payload?: ReplyType): FastifyReply; From 5814956cbe7e575eb8d278b4b78c58f266b8df81 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 3 Jul 2023 16:44:42 +0200 Subject: [PATCH 0366/1295] fix: regression with close and serverFactory (#4876) Signed-off-by: Matteo Collina --- fastify.js | 22 +++++++++++------ test/custom-http-server.test.js | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/fastify.js b/fastify.js index 25d092a8a56..1a628a4cf5a 100644 --- a/fastify.js +++ b/fastify.js @@ -426,12 +426,6 @@ function fastify (options) { router.closeRoutes() hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () { - // No new TCP connections are accepted. - // We must call close on the server even if we are not listening - // otherwise memory will be leaked. - // https://github.com/nodejs/node/issues/48604 - instance.server.close(done) - if (fastify[kState].listening) { /* istanbul ignore next: Cannot test this without Node.js core support */ if (forceCloseConnections === 'idle') { @@ -450,8 +444,22 @@ function fastify (options) { fastify[kKeepAliveConnections].delete(conn) } } + } + + // No new TCP connections are accepted. + // We must call close on the server even if we are not listening + // otherwise memory will be leaked. + // https://github.com/nodejs/node/issues/48604 + if (!options.serverFactory || fastify[kState].listening) { + instance.server.close(function (err) { + if (err && err.code !== 'ERR_SERVER_NOT_RUNNING') { + done(null) + } else { + done() + } + }) } else { - done(null) + process.nextTick(done, null) } }) }) diff --git a/test/custom-http-server.test.js b/test/custom-http-server.test.js index 4312973d884..cb6c9367c32 100644 --- a/test/custom-http-server.test.js +++ b/test/custom-http-server.test.js @@ -82,3 +82,45 @@ test('Should accept user defined serverFactory and ignore secondary server creat await app.listen({ port: 0 }) }) }) + +test('Should not call close on the server if it has not created it', async t => { + const server = http.createServer() + + const serverFactory = (handler, opts) => { + server.on('request', handler) + return server + } + + const fastify = Fastify({ serverFactory }) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + + await new Promise((resolve, reject) => { + server.listen(0) + server.on('listening', resolve) + server.on('error', reject) + }) + + const address = server.address() + t.equal(server.listening, true) + await fastify.close() + + t.equal(server.listening, true) + t.same(server.address(), address) + t.same(fastify.addresses(), [address]) + + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + return reject(err) + } + resolve() + }) + }) + t.equal(server.listening, false) + t.same(server.address(), null) +}) From 4ca4c3e6a3b3dbd58d255dc5f612151e56a21f35 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 3 Jul 2023 16:45:52 +0200 Subject: [PATCH 0367/1295] Bumped v4.19.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 1a628a4cf5a..bdb8c15792a 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.19.1' +const VERSION = '4.19.2' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index c7dd4c5591a..098feda011d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.19.1", + "version": "4.19.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From fe1b9ab547fa8ced5cb313d9c12a1f14f21b6af5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:49:40 +0000 Subject: [PATCH 0368/1295] build(deps-dev): Bump @sinclair/typebox from 0.28.20 to 0.29.1 (#4877) Bumps [@sinclair/typebox](https://github.com/sinclairzx81/typebox) from 0.28.20 to 0.29.1. - [Commits](https://github.com/sinclairzx81/typebox/compare/0.28.20...0.29.1) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 098feda011d..ed2c97b5eae 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "homepage": "https://www.fastify.io/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.28.9", + "@sinclair/typebox": "^0.29.1", "@sinonjs/fake-timers": "^11.0.0", "@types/node": "^20.1.0", "@typescript-eslint/eslint-plugin": "^5.59.2", From 4004d6d0af1425aed53001f3eb979d1477e565b8 Mon Sep 17 00:00:00 2001 From: Eduardo Henrique <62185704+ed-henrique@users.noreply.github.com> Date: Tue, 4 Jul 2023 13:04:14 -0400 Subject: [PATCH 0369/1295] Update Prototype-Poisoning.md (#4879) --- docs/Guides/Prototype-Poisoning.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Guides/Prototype-Poisoning.md b/docs/Guides/Prototype-Poisoning.md index bf5f502d887..7864fd6d7d3 100644 --- a/docs/Guides/Prototype-Poisoning.md +++ b/docs/Guides/Prototype-Poisoning.md @@ -13,8 +13,7 @@ open-source software and the limitations of existing communication channels. But first, if we use a JavaScript framework to process incoming JSON data, take a moment to read up on [Prototype Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96) -in general, and the specific [technical details] -(https://github.com/hapijs/hapi/issues/3916) of this issue. +in general, and the specific [technical details](https://github.com/hapijs/hapi/issues/3916) of this issue. This could be a critical issue so, we might need to verify your own code first. It focuses on specific framework however, any solution that uses `JSON.parse()` to process external data is potentially at risk. From 9789ddea1256b64160c896f13c0219b0db22497b Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Thu, 6 Jul 2023 10:04:38 +0200 Subject: [PATCH 0370/1295] docs: adjust line for linter (#4882) --- docs/Guides/Prototype-Poisoning.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Prototype-Poisoning.md b/docs/Guides/Prototype-Poisoning.md index 7864fd6d7d3..e6b0b2be43a 100644 --- a/docs/Guides/Prototype-Poisoning.md +++ b/docs/Guides/Prototype-Poisoning.md @@ -13,7 +13,8 @@ open-source software and the limitations of existing communication channels. But first, if we use a JavaScript framework to process incoming JSON data, take a moment to read up on [Prototype Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96) -in general, and the specific [technical details](https://github.com/hapijs/hapi/issues/3916) of this issue. +in general, and the specific +[technical details](https://github.com/hapijs/hapi/issues/3916) of this issue. This could be a critical issue so, we might need to verify your own code first. It focuses on specific framework however, any solution that uses `JSON.parse()` to process external data is potentially at risk. From 9f005638774b72cce55496c8941e818256e4d3e7 Mon Sep 17 00:00:00 2001 From: Aadit Olkar <63646058+aadito123@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:49:34 +0800 Subject: [PATCH 0371/1295] fixed type inference (#4880) --- test/types/reply.test-d.ts | 1 + types/reply.d.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 9e50e8374b1..145cf8c03fe 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -72,6 +72,7 @@ interface ReplyHttpCodes { const typedHandler: RouteHandler = async (request, reply) => { expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.send) + expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.code(100).send) } const server = fastify() diff --git a/types/reply.d.ts b/types/reply.d.ts index 094f7e55bd9..fc1386936d0 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -16,7 +16,7 @@ type ReplyTypeConstrainer, SchemaCompiler extends FastifySchema = FastifySchema, From 61b8f2a3e11feb87da7eab3ca86e090256ae3234 Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Fri, 7 Jul 2023 06:32:30 +0200 Subject: [PATCH 0372/1295] feat!: disallow `decorate('name', null)` in the types (#4878) * Disallow `decorate('name', null)` in the types Preferred to use `decorate('name')` , `decorateRequest('name')`, `decorateReply('name')` variants instead Deprecation not feasible because of https://github.com/microsoft/TypeScript/issues/54872 Follow up to #4874 * Docs: Drop `null` from placeholder decorations Signed-off-by: Pelle Wessman --------- Signed-off-by: Pelle Wessman --- docs/Guides/Delay-Accepting-Requests.md | 6 +++--- docs/Reference/Decorators.md | 4 ++-- test/types/instance.test-d.ts | 6 +++--- types/instance.d.ts | 2 -- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/Guides/Delay-Accepting-Requests.md b/docs/Guides/Delay-Accepting-Requests.md index 9a45ec26db5..5ccfbf3f79d 100644 --- a/docs/Guides/Delay-Accepting-Requests.md +++ b/docs/Guides/Delay-Accepting-Requests.md @@ -103,7 +103,7 @@ server.get('/v1*', async function (request, reply) { } }) -server.decorate('magicKey', null) +server.decorate('magicKey') server.listen({ port: '1234' }, () => { provider.thirdPartyMagicKeyGenerator(USUAL_WAIT_TIME_MS) @@ -303,7 +303,7 @@ async function setup(fastify) { fastify.server.on('listening', doMagic) // Set up the placeholder for the magicKey - fastify.decorate('magicKey', null) + fastify.decorate('magicKey') // Our magic -- important to make sure errors are handled. Beware of async // functions outside `try/catch` blocks @@ -406,7 +406,7 @@ https://nodejs.org/api/net.html#event-listening). We use that to reach out to our provider as soon as possible, with the `doMagic` function. ```js - fastify.decorate('magicKey', null) + fastify.decorate('magicKey') ``` The `magicKey` decoration is also part of the plugin now. We initialize it with diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index fe413c139b1..a5b8267ef3f 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -193,7 +193,7 @@ hook](./Hooks.md#onrequest). Example: const fp = require('fastify-plugin') async function myPlugin (app) { - app.decorateRequest('foo', null) + app.decorateRequest('foo') app.addHook('onRequest', async (req, reply) => { req.foo = { bar: 42 } }) @@ -236,7 +236,7 @@ incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). Example: const fp = require('fastify-plugin') async function myPlugin (app) { - app.decorateRequest('foo', null) + app.decorateRequest('foo') app.addHook('onRequest', async (req, reply) => { req.foo = { bar: 42 } }) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index abb065dc222..d76dbc96638 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -386,7 +386,7 @@ server.decorate('typedTestProperty', { }) server.decorate('typedTestProperty') server.decorate('typedTestProperty', null, ['foo']) -server.decorate('typedTestProperty', null) +expectError(server.decorate('typedTestProperty', null)) expectError(server.decorate('typedTestProperty', 'foo')) expectError(server.decorate('typedTestProperty', { getter () { @@ -426,7 +426,7 @@ server.decorateRequest('typedTestRequestProperty', { }) server.decorateRequest('typedTestRequestProperty') server.decorateRequest('typedTestRequestProperty', null, ['foo']) -server.decorateRequest('typedTestRequestProperty', null) +expectError(server.decorateRequest('typedTestRequestProperty', null)) expectError(server.decorateRequest('typedTestRequestProperty', 'foo')) expectError(server.decorateRequest('typedTestRequestProperty', { getter () { @@ -466,7 +466,7 @@ server.decorateReply('typedTestReplyProperty', { }) server.decorateReply('typedTestReplyProperty') server.decorateReply('typedTestReplyProperty', null, ['foo']) -server.decorateReply('typedTestReplyProperty', null) +expectError(server.decorateReply('typedTestReplyProperty', null)) expectError(server.decorateReply('typedTestReplyProperty', 'foo')) expectError(server.decorateReply('typedTestReplyProperty', { getter () { diff --git a/types/instance.d.ts b/types/instance.d.ts index fe6fe8f0b15..399baaebf13 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -106,8 +106,6 @@ type DecorationMethod = { (property: string | symbol): Return; - (property: string | symbol, value: null): Return; - (property: string | symbol, value: null|undefined, dependencies: string[]): Return; } From b5172f6a641c3506e5738210e75617f082750b16 Mon Sep 17 00:00:00 2001 From: aarontravass <43901677+aarontravass@users.noreply.github.com> Date: Fri, 7 Jul 2023 17:23:13 -0400 Subject: [PATCH 0373/1295] feat!: Add req.hostname and req.port (#4766) Co-authored-by: Carlos Fuentes --- docs/Reference/Logging.md | 2 +- docs/Reference/Request.md | 6 +++- docs/Reference/Routes.md | 2 +- docs/Reference/Server.md | 6 ++-- lib/logger.js | 2 +- lib/request.js | 43 +++++++++++++++++------- test/async-await.test.js | 46 +++++++++++++++++++++++++- test/get.test.js | 28 ++++++++++++++++ test/http2/constraint.test.js | 23 ++++++++++++- test/http2/plain.test.js | 27 +++++++++++---- test/http2/secure.test.js | 13 +++++++- test/https/https.test.js | 57 ++++++++++++++++++++++++++++++++ test/internals/logger.test.js | 2 +- test/internals/request.test.js | 24 +++++++------- test/serial/logger.1.test.js | 2 +- test/server.test.js | 16 +++++++++ test/trust-proxy.test.js | 60 +++++++++++++++++++--------------- test/types/fastify.test-d.ts | 2 +- test/types/logger.test-d.ts | 2 +- test/types/request.test-d.ts | 2 ++ test/url-rewriting.test.js | 5 +-- test/versioned-routes.test.js | 2 +- types/logger.d.ts | 2 +- types/request.d.ts | 2 ++ 24 files changed, 303 insertions(+), 73 deletions(-) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 59fee063102..7a8fc285434 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -243,7 +243,7 @@ const fastify = Fastify({ method: request.method, url: request.url, headers: request.headers, - hostname: request.hostname, + host: request.host, remoteAddress: request.ip, remotePort: request.socket.remotePort } diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 23ac9a3c77c..8e043bdab2e 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -20,10 +20,12 @@ Request is a core Fastify object containing the following fields: - `ips` - an array of the IP addresses, ordered from closest to furthest, in the `X-Forwarded-For` header of the incoming request (only when the [`trustProxy`](./Server.md#factory-trust-proxy) option is enabled) -- `hostname` - the host of the incoming request (derived from `X-Forwarded-Host` +- `host` - the host of the incoming request (derived from `X-Forwarded-Host` header when the [`trustProxy`](./Server.md#factory-trust-proxy) option is enabled). For HTTP/2 compatibility it returns `:authority` if no host header exists. +- `hostname` - the host of the incoming request without the port +- `port` - the port that the server is listening on - `protocol` - the protocol of the incoming request (`https` or `http`) - `method` - the method of the incoming request - `url` - the URL of the incoming request @@ -99,7 +101,9 @@ fastify.post('/:params', options, function (request, reply) { console.log(request.id) console.log(request.ip) console.log(request.ips) + console.log(request.host) console.log(request.hostname) + console.log(request.port) console.log(request.protocol) console.log(request.url) console.log(request.routerMethod) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index cc19735f130..220283827b3 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -587,7 +587,7 @@ const fastify = Fastify({ method: req.method, url: req.url, headers: req.headers, - hostname: req.hostname, + host: req.host, remoteAddress: req.ip, remotePort: req.socket.remotePort } diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 289902f864a..a67606ada0e 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -574,21 +574,21 @@ const fastify = Fastify({ trustProxy: true }) For more examples, refer to the [`proxy-addr`](https://www.npmjs.com/package/proxy-addr) package. -You may access the `ip`, `ips`, `hostname` and `protocol` values on the +You may access the `ip`, `ips`, `host` and `protocol` values on the [`request`](./Request.md) object. ```js fastify.get('/', (request, reply) => { console.log(request.ip) console.log(request.ips) - console.log(request.hostname) + console.log(request.host) console.log(request.protocol) }) ``` **Note: if a request contains multiple x-forwarded-host or x-forwarded-proto headers, it is only the last one that is used to -derive request.hostname and request.protocol** +derive request.host and request.protocol** ### `pluginTimeout` diff --git a/lib/logger.js b/lib/logger.js index a2934d1ee23..fa9626c176d 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -51,7 +51,7 @@ const serializers = { method: req.method, url: req.url, version: req.headers && req.headers['accept-version'], - hostname: req.hostname, + host: req.host, remoteAddress: req.ip, remotePort: req.socket ? req.socket.remotePort : undefined } diff --git a/lib/request.js b/lib/request.js index 6a42210f6d3..7a685bd1121 100644 --- a/lib/request.js +++ b/lib/request.js @@ -116,7 +116,7 @@ function buildRequestWithTrustProxy (R, trustProxy) { return proxyAddr.all(this.raw, proxyFn) } }, - hostname: { + host: { get () { if (this.ip !== undefined && this.headers['x-forwarded-host']) { return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host']) @@ -235,11 +235,32 @@ Object.defineProperties(Request.prototype, { } } }, - hostname: { + host: { get () { return this.raw.headers.host || this.raw.headers[':authority'] } }, + hostname: { + get () { + return (this.host).split(':')[0] + } + }, + port: { + get () { + // first try taking port from host + const portFromHost = parseInt((this.host).split(':').slice(-1)[0]) + if (!isNaN(portFromHost)) { + return portFromHost + } + // now fall back to port from host/:authority header + const portFromHeader = parseInt((this.headers.host || this.headers[':authority']).split(':').slice(-1)[0]) + if (!isNaN(portFromHeader)) { + return portFromHeader + } + // fall back to null + return null + } + }, protocol: { get () { if (this.socket) { @@ -277,13 +298,13 @@ Object.defineProperties(Request.prototype, { } const validatorCompiler = this[kRouteContext].validatorCompiler || - this.server[kSchemaController].validatorCompiler || - ( - // We compile the schemas if no custom validatorCompiler is provided - // nor set - this.server[kSchemaController].setupValidator(this.server[kOptions]) || - this.server[kSchemaController].validatorCompiler - ) + this.server[kSchemaController].validatorCompiler || + ( + // We compile the schemas if no custom validatorCompiler is provided + // nor set + this.server[kSchemaController].setupValidator(this.server[kOptions]) || + this.server[kSchemaController].validatorCompiler + ) const validateFn = validatorCompiler({ schema, @@ -320,8 +341,8 @@ Object.defineProperties(Request.prototype, { // We cannot compile if the schema is missed if (validate == null && (schema == null || - typeof schema !== 'object' || - Array.isArray(schema)) + typeof schema !== 'object' || + Array.isArray(schema)) ) { throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart) } diff --git a/test/async-await.test.js b/test/async-await.test.js index be40490a11d..189bed85c7e 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -24,8 +24,28 @@ const opts = { } } +const optsWithHostnameAndPort = { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + }, + hostname: { + type: 'string' + }, + port: { + type: 'string' + } + } + } + } + } +} test('async await', t => { - t.plan(11) + t.plan(13) const fastify = Fastify() try { fastify.get('/', opts, async function awaitMyFunc (req, reply) { @@ -46,6 +66,16 @@ test('async await', t => { t.fail() } + try { + fastify.get('/await/hostname_port', optsWithHostnameAndPort, async function awaitMyFunc (req, reply) { + await sleep(200) + return { hello: 'world', hostname: req.hostname, port: req.port } + }) + t.pass() + } catch (e) { + t.fail() + } + fastify.listen({ port: 0 }, err => { t.error(err) t.teardown(() => { fastify.close() }) @@ -69,6 +99,20 @@ test('async await', t => { t.equal(response.headers['content-length'], '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) }) + + t.test('test for hostname and port in request', t => { + t.plan(4) + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/await/hostname_port' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + const parsedBody = JSON.parse(body) + t.equal(parsedBody.hostname, 'localhost') + t.equal(parseInt(parsedBody.port), fastify.server.address().port) + }) + }) }) }) diff --git a/test/get.test.js b/test/get.test.js index 48270cc0a78..9c12c82940a 100644 --- a/test/get.test.js +++ b/test/get.test.js @@ -203,6 +203,18 @@ test('send a falsy boolean', t => { } }) +test('shorthand - get, set port', t => { + t.plan(1) + try { + fastify.get('/port', headersSchema, function (req, reply) { + reply.code(200).send({ port: req.port }) + }) + t.pass() + } catch (e) { + t.fail() + } +}) + fastify.listen({ port: 0 }, err => { t.error(err) t.teardown(() => { fastify.close() }) @@ -380,4 +392,20 @@ fastify.listen({ port: 0 }, err => { t.same(body.toString(), 'null') }) }) + + test('shorthand - request get headers - test fall back port', t => { + t.plan(3) + sget({ + method: 'GET', + headers: { + host: 'example.com' + }, + json: true, + url: 'http://localhost:' + fastify.server.address().port + '/port' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.port, null) + }) + }) }) diff --git a/test/http2/constraint.test.js b/test/http2/constraint.test.js index 6c0162bf1dd..bd75e5e3455 100644 --- a/test/http2/constraint.test.js +++ b/test/http2/constraint.test.js @@ -12,7 +12,7 @@ const { buildCertificate } = require('../build-certificate') t.before(buildCertificate) test('A route supports host constraints under http2 protocol and secure connection', (t) => { - t.plan(5) + t.plan(6) let fastify try { @@ -45,6 +45,14 @@ test('A route supports host constraints under http2 protocol and secure connecti reply.code(200).send(beta) } }) + fastify.route({ + method: 'GET', + url: '/hostname_port', + constraints: { host: constrain }, + handler: function (req, reply) { + reply.code(200).send({ ...beta, hostname: req.hostname }) + } + }) fastify.listen({ port: 0 }, err => { t.error(err) @@ -87,5 +95,18 @@ test('A route supports host constraints under http2 protocol and secure connecti t.equal(res.headers[':status'], 404) }) + t.test('https get request - constrain - verify hostname and port from request', async (t) => { + t.plan(1) + + const url = `https://localhost:${fastify.server.address().port}/hostname_port` + const res = await h2url.concat({ + url, + headers: { + ':authority': constrain + } + }) + const body = JSON.parse(res.body) + t.equal(body.hostname, constrain) + }) }) }) diff --git a/test/http2/plain.test.js b/test/http2/plain.test.js index 75c5f09cb94..98691334485 100644 --- a/test/http2/plain.test.js +++ b/test/http2/plain.test.js @@ -20,8 +20,12 @@ fastify.get('/', function (req, reply) { reply.code(200).send(msg) }) -fastify.get('/hostname', function (req, reply) { - reply.code(200).send(req.hostname) +fastify.get('/host', function (req, reply) { + reply.code(200).send(req.host) +}) + +fastify.get('/hostname_port', function (req, reply) { + reply.code(200).send({ hostname: req.hostname, port: req.port }) }) fastify.listen({ port: 0 }, err => { @@ -40,14 +44,25 @@ fastify.listen({ port: 0 }, err => { t.same(JSON.parse(res.body), msg) }) - test('http hostname', async (t) => { + test('http host', async (t) => { t.plan(1) - const hostname = `localhost:${fastify.server.address().port}` + const host = `localhost:${fastify.server.address().port}` + + const url = `http://${host}/host` + const res = await h2url.concat({ url }) + + t.equal(res.body, host) + }) + test('http hostname and port', async (t) => { + t.plan(2) + + const host = `localhost:${fastify.server.address().port}` - const url = `http://${hostname}/hostname` + const url = `http://${host}/hostname_port` const res = await h2url.concat({ url }) - t.equal(res.body, hostname) + t.equal(JSON.parse(res.body).hostname, host.split(':')[0]) + t.equal(JSON.parse(res.body).port, parseInt(host.split(':')[1])) }) }) diff --git a/test/http2/secure.test.js b/test/http2/secure.test.js index 75b0526788a..86f4f1d3418 100644 --- a/test/http2/secure.test.js +++ b/test/http2/secure.test.js @@ -10,7 +10,7 @@ const { buildCertificate } = require('../build-certificate') t.before(buildCertificate) test('secure', (t) => { - t.plan(4) + t.plan(5) let fastify try { @@ -32,6 +32,9 @@ test('secure', (t) => { fastify.get('/proto', function (req, reply) { reply.code(200).send({ proto: req.protocol }) }) + fastify.get('/hostname_port', function (req, reply) { + reply.code(200).send({ hostname: req.hostname, port: req.port }) + }) fastify.listen({ port: 0 }, err => { t.error(err) @@ -55,5 +58,13 @@ test('secure', (t) => { t.same(JSON.parse((await h2url.concat({ url })).body), { proto: 'https' }) t.same(JSON.parse((await h2url.concat({ url, headers: { 'X-Forwarded-Proto': 'lorem' } })).body), { proto: 'https' }) }) + t.test('https get request - test hostname and port', async (t) => { + t.plan(2) + + const url = `https://localhost:${fastify.server.address().port}/hostname_port` + const parsedbody = JSON.parse((await h2url.concat({ url })).body) + t.equal(parsedbody.hostname, 'localhost') + t.equal(parsedbody.port, fastify.server.address().port) + }) }) }) diff --git a/test/https/https.test.js b/test/https/https.test.js index cc6b40f8e2f..ceaea513d27 100644 --- a/test/https/https.test.js +++ b/test/https/https.test.js @@ -74,3 +74,60 @@ test('https', (t) => { }) }) }) + +test('https - headers', (t) => { + t.plan(4) + let fastify + try { + fastify = Fastify({ + https: { + key: global.context.key, + cert: global.context.cert + } + }) + t.pass('Key/cert successfully loaded') + } catch (e) { + t.fail('Key/cert loading failed', e) + } + + fastify.get('/', function (req, reply) { + reply.code(200).send({ hello: 'world', hostname: req.hostname, port: req.port }) + }) + + t.teardown(async () => { await fastify.close() }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + t.test('https get request', t => { + t.plan(4) + sget({ + method: 'GET', + url: 'https://localhost:' + fastify.server.address().port, + rejectUnauthorized: false + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + const parsedBody = JSON.parse(body) + t.equal(parsedBody.hostname, 'localhost') + t.equal(parsedBody.port, fastify.server.address().port) + }) + }) + t.test('https get request - test port fall back', t => { + t.plan(3) + sget({ + method: 'GET', + headers: { + host: 'example.com' + }, + url: 'https://localhost:' + fastify.server.address().port, + rejectUnauthorized: false + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + const parsedBody = JSON.parse(body) + t.equal(parsedBody.port, null) + }) + }) + }) +}) diff --git a/test/internals/logger.test.js b/test/internals/logger.test.js index dec0863fe6b..6f3246fdda0 100644 --- a/test/internals/logger.test.js +++ b/test/internals/logger.test.js @@ -127,7 +127,7 @@ test('The serializer prevent fails if the request socket is undefined', t => { method: 'GET', url: '/', version: undefined, - hostname: undefined, + host: undefined, remoteAddress: undefined, remotePort: undefined }) diff --git a/test/internals/request.test.js b/test/internals/request.test.js index f0173cbd5bb..c50207b9f01 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -56,7 +56,7 @@ test('Regular request', t => { t.equal(request.log, 'log') t.equal(request.ip, 'ip') t.equal(request.ips, undefined) - t.equal(request.hostname, 'hostname') + t.equal(request.host, 'hostname') t.equal(request.body, undefined) t.equal(request.method, 'GET') t.equal(request.url, '/') @@ -135,7 +135,7 @@ test('Request with undefined config', t => { }) test('Regular request - hostname from authority', t => { - t.plan(2) + t.plan(3) const headers = { ':authority': 'authority' } @@ -148,11 +148,12 @@ test('Regular request - hostname from authority', t => { const request = new Request('id', 'params', req, 'query', 'log') t.type(request, Request) - t.equal(request.hostname, 'authority') + t.equal(request.host, 'authority') + t.equal(request.port, null) }) test('Regular request - host header has precedence over authority', t => { - t.plan(2) + t.plan(3) const headers = { host: 'hostname', ':authority': 'authority' @@ -165,7 +166,8 @@ test('Regular request - host header has precedence over authority', t => { } const request = new Request('id', 'params', req, 'query', 'log') t.type(request, Request) - t.equal(request.hostname, 'hostname') + t.equal(request.host, 'hostname') + t.equal(request.port, null) }) test('Request with trust proxy', t => { @@ -212,7 +214,7 @@ test('Request with trust proxy', t => { t.equal(request.log, 'log') t.equal(request.ip, '2.2.2.2') t.same(request.ips, ['ip', '1.1.1.1', '2.2.2.2']) - t.equal(request.hostname, 'example.com') + t.equal(request.host, 'example.com') t.equal(request.body, undefined) t.equal(request.method, 'GET') t.equal(request.url, '/') @@ -262,7 +264,7 @@ test('Request with trust proxy - no x-forwarded-host header', t => { const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') t.type(request, TpRequest) - t.equal(request.hostname, 'hostname') + t.equal(request.host, 'hostname') }) test('Request with trust proxy - no x-forwarded-host header and fallback to authority', t => { @@ -281,7 +283,7 @@ test('Request with trust proxy - no x-forwarded-host header and fallback to auth const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') t.type(request, TpRequest) - t.equal(request.hostname, 'authority') + t.equal(request.host, 'authority') }) test('Request with trust proxy - x-forwarded-host header has precedence over host', t => { @@ -301,7 +303,7 @@ test('Request with trust proxy - x-forwarded-host header has precedence over hos const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') t.type(request, TpRequest) - t.equal(request.hostname, 'example.com') + t.equal(request.host, 'example.com') }) test('Request with trust proxy - handles multiple entries in x-forwarded-host/proto', t => { @@ -320,7 +322,7 @@ test('Request with trust proxy - handles multiple entries in x-forwarded-host/pr const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') t.type(request, TpRequest) - t.equal(request.hostname, 'example.com') + t.equal(request.host, 'example.com') t.equal(request.protocol, 'https') }) @@ -363,7 +365,7 @@ test('Request with undefined socket', t => { t.equal(request.log, 'log') t.equal(request.ip, undefined) t.equal(request.ips, undefined) - t.equal(request.hostname, 'hostname') + t.equal(request.host, 'hostname') t.same(request.body, null) t.equal(request.method, 'GET') t.equal(request.url, '/') diff --git a/test/serial/logger.1.test.js b/test/serial/logger.1.test.js index 206de71fb86..987081bfd7a 100644 --- a/test/serial/logger.1.test.js +++ b/test/serial/logger.1.test.js @@ -700,7 +700,7 @@ t.test('test log stream', (t) => { method: req.method, url: req.url, headers: req.headers, - hostname: req.hostname, + host: req.host, remoteAddress: req.ip, remotePort: req.socket.remotePort } diff --git a/test/server.test.js b/test/server.test.js index c946bbd6771..9cd6db676c2 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -3,6 +3,7 @@ const t = require('tap') const test = t.test const Fastify = require('..') +const sget = require('simple-get').concat test('listen should accept null port', t => { t.plan(1) @@ -61,3 +62,18 @@ test('listen should reject string port', async (t) => { t.equal(error.code, 'ERR_SOCKET_BAD_PORT') } }) + +test('Test for hostname and port', t => { + const app = Fastify() + t.teardown(app.close.bind(app)) + app.get('/host', (req, res) => { + const host = 'localhost:8000' + t.equal(req.host, host) + t.equal(req.hostname, req.host.split(':')[0]) + t.equal(req.port, Number(req.host.split(':')[1])) + res.send('ok') + }) + app.listen({ port: 8000 }, () => { + sget('http://localhost:8000/host', () => { t.end() }) + }) +}) diff --git a/test/trust-proxy.test.js b/test/trust-proxy.test.js index fb6c52d6818..c37e7c9bf86 100644 --- a/test/trust-proxy.test.js +++ b/test/trust-proxy.test.js @@ -26,9 +26,11 @@ const testRequestValues = (t, req, options) => { t.ok(req.ip, 'ip is defined') t.equal(req.ip, options.ip, 'gets ip from x-forwarded-for') } - if (options.hostname) { - t.ok(req.hostname, 'hostname is defined') - t.equal(req.hostname, options.hostname, 'gets hostname from x-forwarded-host') + if (options.host) { + t.ok(req.host, 'host is defined') + t.equal(req.host, options.host, 'gets host from x-forwarded-host') + t.ok(req.hostname) + t.equal(req.hostname, options.host, 'gets hostname from x-forwarded-host') } if (options.ips) { t.same(req.ips, options.ips, 'gets ips from x-forwarded-for') @@ -37,6 +39,10 @@ const testRequestValues = (t, req, options) => { t.ok(req.protocol, 'protocol is defined') t.equal(req.protocol, options.protocol, 'gets protocol from x-forwarded-proto') } + if (options.port) { + t.ok(req.port, 'port is defined') + t.equal(req.port, options.port, 'port is taken from x-forwarded-for or host') + } } let localhost @@ -47,18 +53,18 @@ before(async function () { }) test('trust proxy, not add properties to node req', (t) => { - t.plan(8) + t.plan(14) const app = fastify({ trustProxy: true }) app.get('/trustproxy', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', hostname: 'example.com' }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) app.get('/trustproxychain', function (req, reply) { - testRequestValues(t, req, { ip: '2.2.2.2', ips: [localhost, '1.1.1.1', '2.2.2.2'] }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '2.2.2.2', ips: [localhost, '1.1.1.1', '2.2.2.2'], port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) t.teardown(app.close.bind(app)) @@ -72,14 +78,14 @@ test('trust proxy, not add properties to node req', (t) => { }) test('trust proxy chain', (t) => { - t.plan(3) + t.plan(9) const app = fastify({ trustProxy: [localhost, '192.168.1.1'] }) app.get('/trustproxychain', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1' }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) t.teardown(app.close.bind(app)) @@ -92,13 +98,13 @@ test('trust proxy chain', (t) => { }) test('trust proxy function', (t) => { - t.plan(3) + t.plan(9) const app = fastify({ trustProxy: (address) => address === localhost }) app.get('/trustproxyfunc', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1' }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) t.teardown(app.close.bind(app)) @@ -111,13 +117,13 @@ test('trust proxy function', (t) => { }) test('trust proxy number', (t) => { - t.plan(4) + t.plan(10) const app = fastify({ trustProxy: 1 }) app.get('/trustproxynumber', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'] }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'example.com', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) t.teardown(app.close.bind(app)) @@ -130,13 +136,13 @@ test('trust proxy number', (t) => { }) test('trust proxy IP addresses', (t) => { - t.plan(4) + t.plan(10) const app = fastify({ trustProxy: `${localhost}, 2.2.2.2` }) app.get('/trustproxyipaddrs', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'] }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'example.com', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) t.teardown(app.close.bind(app)) @@ -149,21 +155,21 @@ test('trust proxy IP addresses', (t) => { }) test('trust proxy protocol', (t) => { - t.plan(13) + t.plan(31) const app = fastify({ trustProxy: true }) app.get('/trustproxyprotocol', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem' }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem', host: 'example.com', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) app.get('/trustproxynoprotocol', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'http' }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'http', host: 'example.com', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) app.get('/trustproxyprotocols', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'dolor' }) - reply.code(200).send({ ip: req.ip, hostname: req.hostname }) + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'dolor', host: 'example.com', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) }) t.teardown(app.close.bind(app)) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 6264fb78abd..079317b8037 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -93,7 +93,7 @@ expectAssignable>(request.routeOptions) expectType(request.is404) expectType(request.hostname) + expectType(request.host) + expectType(request.port) expectType(request.ip) expectType(request.ips) expectType(request.raw) diff --git a/test/url-rewriting.test.js b/test/url-rewriting.test.js index 77768cf8bc6..bc2c0bf6965 100644 --- a/test/url-rewriting.test.js +++ b/test/url-rewriting.test.js @@ -119,8 +119,8 @@ test('Should rewrite url but keep originalUrl unchanged', t => { method: 'GET', url: '/', handler: (req, reply) => { + reply.send({ hello: 'world', hostname: req.hostname, port: req.port }) t.equal(req.originalUrl, '/this-would-404-without-url-rewrite') - reply.send({ hello: 'world' }) } }) @@ -132,7 +132,8 @@ test('Should rewrite url but keep originalUrl unchanged', t => { url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' }, (err, response, body) => { t.error(err) - t.same(JSON.parse(body), { hello: 'world' }) + const parsedBody = JSON.parse(body) + t.same(parsedBody, { hello: 'world', hostname: 'localhost', port: fastify.server.address().port }) t.equal(response.statusCode, 200) }) }) diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js index 8d79b6129db..988c257c139 100644 --- a/test/versioned-routes.test.js +++ b/test/versioned-routes.test.js @@ -454,7 +454,7 @@ test('test log stream', t => { t.teardown(() => { fastify.close() }) http.get({ - hostname: fastify.server.address().hostname, + host: fastify.server.address().hostname, port: fastify.server.address().port, path: '/', method: 'GET', diff --git a/types/logger.d.ts b/types/logger.d.ts index 6b698b31aaf..eae0595712d 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -57,7 +57,7 @@ export interface FastifyLoggerOptions< method?: string; url?: string; version?: string; - hostname?: string; + host?: string; remoteAddress?: string; remotePort?: number; [key: string]: unknown; diff --git a/types/request.d.ts b/types/request.d.ts index bd75639fad9..cd10a57b78e 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -71,6 +71,8 @@ export interface FastifyRequest Date: Sat, 8 Jul 2023 12:42:09 +0200 Subject: [PATCH 0374/1295] chore: refactor Error-typings and tests (#4824) * Added FST_ERR_VALIDATION to errors.d.ts The error code FST_ERR_VALIDATION is used as an error code in lib/context.js * add unit tests, fix FST_ERR_ROUTE_REWRITE_NOT_STR * add typings --------- Co-authored-by: Manuel Spigolon Co-authored-by: Uzlopak --- docs/Reference/Errors.md | 6 + lib/errors.js | 7 +- test/internals/errors.test.js | 806 ++++++++++++++++++++++++++++++++++ test/types/errors.test-d.ts | 82 ++++ types/errors.d.ts | 52 ++- 5 files changed, 929 insertions(+), 24 deletions(-) create mode 100644 test/internals/errors.test.js create mode 100644 test/types/errors.test-d.ts diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index a18b441628c..6ce53514a1e 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -504,3 +504,9 @@ Plugin did not start in time. Default timeout (in millis): `10000` #### FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE The decorator is not present in the instance. + + + +#### FST_ERR_VALIDATION + +The Request failed the payload validation. diff --git a/lib/errors.js b/lib/errors.js index 9b78792b3b5..192c0cdb0c7 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -47,6 +47,11 @@ const codes = { 'Version constraint should be a string.', 500 ), + FST_ERR_VALIDATION: createError( + 'FST_ERR_VALIDATION', + '%s', + 400 + ), /** * ContentTypeParser @@ -358,7 +363,7 @@ const codes = { TypeError ), FST_ERR_ROUTE_REWRITE_NOT_STR: createError( - 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', + 'FST_ERR_ROUTE_REWRITE_NOT_STR', 'Rewrite url for "%s" needs to be of type "string" but received "%s"', 500 ), diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js new file mode 100644 index 00000000000..7d133c7877c --- /dev/null +++ b/test/internals/errors.test.js @@ -0,0 +1,806 @@ +'use strict' + +const { test } = require('tap') +const errors = require('../../lib/errors') + +test('should expose 77 errors', t => { + t.plan(1) + const exportedKeys = Object.keys(errors) + let counter = 0 + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + counter++ + } + } + t.equal(counter, 77) +}) + +test('ensure name and codes of Errors are identical', t => { + t.plan(77) + const exportedKeys = Object.keys(errors) + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + t.equal(key, new errors[key]().code, key) + } + } +}) + +test('FST_ERR_NOT_FOUND', t => { + t.plan(5) + const error = new errors.FST_ERR_NOT_FOUND() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_NOT_FOUND') + t.equal(error.message, 'Not Found') + t.equal(error.statusCode, 404) + t.ok(error instanceof Error) +}) + +test('FST_ERR_OPTIONS_NOT_OBJ', t => { + t.plan(5) + const error = new errors.FST_ERR_OPTIONS_NOT_OBJ() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_OPTIONS_NOT_OBJ') + t.equal(error.message, 'Options must be an object') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_QSP_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_QSP_NOT_FN() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_QSP_NOT_FN') + t.equal(error.message, "querystringParser option should be a function, instead got '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN') + t.equal(error.message, "schemaController.bucket option should be a function, instead got '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + t.equal(error.message, "schemaErrorFormatter option should be a non async function. Instead got '%s'.") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ', t => { + t.plan(5) + const error = new errors.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ') + t.equal(error.message, "sajv.customOptions option should be an object, instead got '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR', t => { + t.plan(5) + const error = new errors.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR') + t.equal(error.message, "sajv.plugins option should be an array, instead got '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_VERSION_CONSTRAINT_NOT_STR', t => { + t.plan(5) + const error = new errors.FST_ERR_VERSION_CONSTRAINT_NOT_STR() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_VERSION_CONSTRAINT_NOT_STR') + t.equal(error.message, 'Version constraint should be a string.') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_ALREADY_PRESENT', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_ALREADY_PRESENT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.equal(error.message, "Content type parser '%s' already present.") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_INVALID_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_INVALID_TYPE') + t.equal(error.message, 'The content type should be a string or a RegExp') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_EMPTY_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_EMPTY_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_EMPTY_TYPE') + t.equal(error.message, 'The content type cannot be an empty string') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_INVALID_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_HANDLER() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_INVALID_HANDLER') + t.equal(error.message, 'The content type handler should be a function') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_INVALID_PARSE_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_PARSE_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') + t.equal(error.message, "The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.") + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_BODY_TOO_LARGE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_BODY_TOO_LARGE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_BODY_TOO_LARGE') + t.equal(error.message, 'Request body is too large') + t.equal(error.statusCode, 413) + t.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_INVALID_MEDIA_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_MEDIA_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + t.equal(error.message, 'Unsupported Media Type: %s') + t.equal(error.statusCode, 415) + t.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_INVALID_CONTENT_LENGTH', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_CONTENT_LENGTH() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_INVALID_CONTENT_LENGTH') + t.equal(error.message, 'Request body size did not match Content-Length') + t.equal(error.statusCode, 400) + t.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_EMPTY_JSON_BODY', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_EMPTY_JSON_BODY() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_EMPTY_JSON_BODY') + t.equal(error.message, "Body cannot be empty when content-type is set to 'application/json'") + t.equal(error.statusCode, 400) + t.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_INSTANCE_ALREADY_STARTED', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INSTANCE_ALREADY_STARTED() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED') + t.equal(error.message, 'Cannot call "%s" when fastify instance is already started!') + t.equal(error.statusCode, 400) + t.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_ALREADY_PRESENT', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_ALREADY_PRESENT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_DEC_ALREADY_PRESENT') + t.equal(error.message, "The decorator '%s' has already been added!") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_DEPENDENCY_INVALID_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_DEPENDENCY_INVALID_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE') + t.equal(error.message, "The dependencies of decorator '%s' must be of type Array.") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_MISSING_DEPENDENCY', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_MISSING_DEPENDENCY() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.equal(error.message, "The decorator is missing dependency '%s'.") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_AFTER_START', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_AFTER_START() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_DEC_AFTER_START') + t.equal(error.message, "The decorator '%s' has been added after start!") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_HOOK_INVALID_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_INVALID_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_HOOK_INVALID_TYPE') + t.equal(error.message, 'The hook name must be a string') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_HOOK_INVALID_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_INVALID_HANDLER() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.equal(error.message, '%s hook should be a function, instead got %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_HOOK_INVALID_ASYNC_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(error.message, "Async function has too many arguments. Async hooks should not use the 'done' argument.") + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_HOOK_NOT_SUPPORTED', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_NOT_SUPPORTED() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_HOOK_NOT_SUPPORTED') + t.equal(error.message, '%s hook not supported!') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_MISSING_MIDDLEWARE', t => { + t.plan(5) + const error = new errors.FST_ERR_MISSING_MIDDLEWARE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_MISSING_MIDDLEWARE') + t.equal(error.message, 'You must register a plugin for handling middlewares, visit fastify.io/docs/latest/Reference/Middleware/ for more info.') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_HOOK_TIMEOUT', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_TIMEOUT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_HOOK_TIMEOUT') + t.equal(error.message, "A callback for '%s' hook timed out. You may have forgotten to call 'done' function or to resolve a Promise") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_LOG_INVALID_DESTINATION', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_INVALID_DESTINATION() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_LOG_INVALID_DESTINATION') + t.equal(error.message, 'Cannot specify both logger.stream and logger.file options') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_LOG_INVALID_LOGGER', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_INVALID_LOGGER() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_LOG_INVALID_LOGGER') + t.equal(error.message, "Invalid logger object provided. The logger instance should have these functions(s): '%s'.") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_INVALID_PAYLOAD_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') + t.equal(error.message, "Attempted to send payload of invalid type '%s'. Expected a string or Buffer.") + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_REP_ALREADY_SENT', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_ALREADY_SENT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_REP_ALREADY_SENT') + t.equal(error.message, 'Reply was already sent.') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_REP_SENT_VALUE', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_SENT_VALUE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_REP_SENT_VALUE') + t.equal(error.message, 'The only possible value for reply.sent is true.') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SEND_INSIDE_ONERR', t => { + t.plan(5) + const error = new errors.FST_ERR_SEND_INSIDE_ONERR() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SEND_INSIDE_ONERR') + t.equal(error.message, 'You cannot use `send` inside the `onError` hook') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SEND_UNDEFINED_ERR', t => { + t.plan(5) + const error = new errors.FST_ERR_SEND_UNDEFINED_ERR() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SEND_UNDEFINED_ERR') + t.equal(error.message, 'Undefined error has occurred') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_BAD_STATUS_CODE', t => { + t.plan(5) + const error = new errors.FST_ERR_BAD_STATUS_CODE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_BAD_STATUS_CODE') + t.equal(error.message, 'Called reply with an invalid status code: %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_BAD_TRAILER_NAME', t => { + t.plan(5) + const error = new errors.FST_ERR_BAD_TRAILER_NAME() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_BAD_TRAILER_NAME') + t.equal(error.message, 'Called reply.trailer with an invalid header name: %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_BAD_TRAILER_VALUE', t => { + t.plan(5) + const error = new errors.FST_ERR_BAD_TRAILER_VALUE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_BAD_TRAILER_VALUE') + t.equal(error.message, "Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function.") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_FAILED_ERROR_SERIALIZATION', t => { + t.plan(5) + const error = new errors.FST_ERR_FAILED_ERROR_SERIALIZATION() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_FAILED_ERROR_SERIALIZATION') + t.equal(error.message, 'Failed to serialize an error. Error: %s. Original error: %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_MISSING_SERIALIZATION_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_MISSING_SERIALIZATION_FN() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_MISSING_SERIALIZATION_FN') + t.equal(error.message, 'Missing serialization function. Key "%s"') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN') + t.equal(error.message, 'Missing serialization function. Key "%s:%s"') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_REQ_INVALID_VALIDATION_INVOCATION', t => { + t.plan(5) + const error = new errors.FST_ERR_REQ_INVALID_VALIDATION_INVOCATION() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + t.equal(error.message, 'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_MISSING_ID', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_MISSING_ID() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCH_MISSING_ID') + t.equal(error.message, 'Missing schema $id property') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_ALREADY_PRESENT', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_ALREADY_PRESENT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCH_ALREADY_PRESENT') + t.equal(error.message, "Schema with id '%s' already declared!") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_CONTENT_MISSING_SCHEMA', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_CONTENT_MISSING_SCHEMA() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + t.equal(error.message, "Schema is missing for the content type '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_DUPLICATE', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_DUPLICATE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCH_DUPLICATE') + t.equal(error.message, "Schema with '%s' already present!") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_VALIDATION_BUILD', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_VALIDATION_BUILD() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.equal(error.message, 'Failed building the validation schema for %s: %s, due to error %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_SERIALIZATION_BUILD', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_SERIALIZATION_BUILD() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + t.equal(error.message, 'Failed building the serialization schema for %s: %s, due to error %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX') + t.equal(error.message, 'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_HTTP2_INVALID_VERSION', t => { + t.plan(5) + const error = new errors.FST_ERR_HTTP2_INVALID_VERSION() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_HTTP2_INVALID_VERSION') + t.equal(error.message, 'HTTP2 is available only from node >= 8.8.1') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_INIT_OPTS_INVALID', t => { + t.plan(5) + const error = new errors.FST_ERR_INIT_OPTS_INVALID() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_INIT_OPTS_INVALID') + t.equal(error.message, "Invalid initialization options: '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE', t => { + t.plan(5) + const error = new errors.FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE') + t.equal(error.message, "Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_DUPLICATED_ROUTE', t => { + t.plan(5) + const error = new errors.FST_ERR_DUPLICATED_ROUTE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') + t.equal(error.message, "Method '%s' already declared for route '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_BAD_URL', t => { + t.plan(5) + const error = new errors.FST_ERR_BAD_URL() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_BAD_URL') + t.equal(error.message, "'%s' is not a valid url component") + t.equal(error.statusCode, 400) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ASYNC_CONSTRAINT', t => { + t.plan(5) + const error = new errors.FST_ERR_ASYNC_CONSTRAINT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ASYNC_CONSTRAINT') + t.equal(error.message, 'Unexpected error from async constraint') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_DEFAULT_ROUTE_INVALID_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_DEFAULT_ROUTE_INVALID_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE') + t.equal(error.message, 'The defaultRoute type should be a function') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_INVALID_URL', t => { + t.plan(5) + const error = new errors.FST_ERR_INVALID_URL() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_INVALID_URL') + t.equal(error.message, "URL must be a string. Received '%s'") + t.equal(error.statusCode, 400) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_OPTIONS_NOT_OBJ', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_OPTIONS_NOT_OBJ() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ') + t.equal(error.message, 'Options for "%s:%s" route must be an object') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_DUPLICATED_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_DUPLICATED_HANDLER() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_DUPLICATED_HANDLER') + t.equal(error.message, 'Duplicate handler for "%s:%s" route is not allowed!') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_HANDLER_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_HANDLER_NOT_FN() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_HANDLER_NOT_FN') + t.equal(error.message, 'Error Handler for %s:%s route, if defined, must be a function') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_MISSING_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_MISSING_HANDLER() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_MISSING_HANDLER') + t.equal(error.message, 'Missing handler function for "%s:%s" route.') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_METHOD_INVALID', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_METHOD_INVALID() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_METHOD_INVALID') + t.equal(error.message, 'Provided method is invalid!') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_METHOD_NOT_SUPPORTED() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED') + t.equal(error.message, '%s method is not supported.') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED') + t.equal(error.message, 'Body validation schema for %s:%s route is not supported!') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') + t.equal(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') + t.equal(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_REWRITE_NOT_STR', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_REWRITE_NOT_STR() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROUTE_REWRITE_NOT_STR') + t.equal(error.message, 'Rewrite url for "%s" needs to be of type "string" but received "%s"') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_REOPENED_CLOSE_SERVER', t => { + t.plan(5) + const error = new errors.FST_ERR_REOPENED_CLOSE_SERVER() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + t.equal(error.message, 'Fastify has already been closed and cannot be reopened') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_REOPENED_SERVER', t => { + t.plan(5) + const error = new errors.FST_ERR_REOPENED_SERVER() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_REOPENED_SERVER') + t.equal(error.message, 'Fastify is already listening') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_INSTANCE_ALREADY_LISTENING', t => { + t.plan(5) + const error = new errors.FST_ERR_INSTANCE_ALREADY_LISTENING() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') + t.equal(error.message, 'Fastify instance is already listening. %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_VERSION_MISMATCH', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_VERSION_MISMATCH() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + t.equal(error.message, "fastify-plugin: %s - expected '%s' fastify version, '%s' is installed") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE') + t.equal(error.message, "The decorator '%s'%s is not present in %s") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_CALLBACK_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_CALLBACK_NOT_FN() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_PLUGIN_CALLBACK_NOT_FN') + t.equal(error.message, 'fastify-plugin: %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_NOT_VALID', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_NOT_VALID() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_PLUGIN_NOT_VALID') + t.equal(error.message, 'fastify-plugin: %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_ROOT_PLG_BOOTED', t => { + t.plan(5) + const error = new errors.FST_ERR_ROOT_PLG_BOOTED() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ROOT_PLG_BOOTED') + t.equal(error.message, 'fastify-plugin: %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_PARENT_PLUGIN_BOOTED', t => { + t.plan(5) + const error = new errors.FST_ERR_PARENT_PLUGIN_BOOTED() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_PARENT_PLUGIN_BOOTED') + t.equal(error.message, 'fastify-plugin: %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_TIMEOUT', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_TIMEOUT() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.equal(error.message, 'fastify-plugin: %s') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + +test('FST_ERR_VALIDATION', t => { + t.plan(5) + const error = new errors.FST_ERR_VALIDATION() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_VALIDATION') + t.equal(error.message, '%s') + t.equal(error.statusCode, 400) + t.ok(error instanceof Error) +}) diff --git a/test/types/errors.test-d.ts b/test/types/errors.test-d.ts new file mode 100644 index 00000000000..afc423fee1e --- /dev/null +++ b/test/types/errors.test-d.ts @@ -0,0 +1,82 @@ +import { expectAssignable } from 'tsd' +import { errorCodes } from '../../fastify' +import { FastifyErrorConstructor } from '@fastify/error' + +expectAssignable(errorCodes.FST_ERR_VALIDATION) +expectAssignable(errorCodes.FST_ERR_NOT_FOUND) +expectAssignable(errorCodes.FST_ERR_OPTIONS_NOT_OBJ) +expectAssignable(errorCodes.FST_ERR_QSP_NOT_FN) +expectAssignable(errorCodes.FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN) +expectAssignable(errorCodes.FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN) +expectAssignable(errorCodes.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ) +expectAssignable(errorCodes.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR) +expectAssignable(errorCodes.FST_ERR_VERSION_CONSTRAINT_NOT_STR) +expectAssignable(errorCodes.FST_ERR_VALIDATION) +expectAssignable(errorCodes.FST_ERR_CTP_ALREADY_PRESENT) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_TYPE) +expectAssignable(errorCodes.FST_ERR_CTP_EMPTY_TYPE) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_HANDLER) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_PARSE_TYPE) +expectAssignable(errorCodes.FST_ERR_CTP_BODY_TOO_LARGE) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_MEDIA_TYPE) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_CONTENT_LENGTH) +expectAssignable(errorCodes.FST_ERR_CTP_EMPTY_JSON_BODY) +expectAssignable(errorCodes.FST_ERR_CTP_INSTANCE_ALREADY_STARTED) +expectAssignable(errorCodes.FST_ERR_DEC_ALREADY_PRESENT) +expectAssignable(errorCodes.FST_ERR_DEC_DEPENDENCY_INVALID_TYPE) +expectAssignable(errorCodes.FST_ERR_DEC_MISSING_DEPENDENCY) +expectAssignable(errorCodes.FST_ERR_DEC_AFTER_START) +expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_TYPE) +expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_HANDLER) +expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER) +expectAssignable(errorCodes.FST_ERR_HOOK_NOT_SUPPORTED) +expectAssignable(errorCodes.FST_ERR_MISSING_MIDDLEWARE) +expectAssignable(errorCodes.FST_ERR_HOOK_TIMEOUT) +expectAssignable(errorCodes.FST_ERR_LOG_INVALID_DESTINATION) +expectAssignable(errorCodes.FST_ERR_LOG_INVALID_LOGGER) +expectAssignable(errorCodes.FST_ERR_REP_INVALID_PAYLOAD_TYPE) +expectAssignable(errorCodes.FST_ERR_REP_ALREADY_SENT) +expectAssignable(errorCodes.FST_ERR_REP_SENT_VALUE) +expectAssignable(errorCodes.FST_ERR_SEND_INSIDE_ONERR) +expectAssignable(errorCodes.FST_ERR_SEND_UNDEFINED_ERR) +expectAssignable(errorCodes.FST_ERR_BAD_STATUS_CODE) +expectAssignable(errorCodes.FST_ERR_BAD_TRAILER_NAME) +expectAssignable(errorCodes.FST_ERR_BAD_TRAILER_VALUE) +expectAssignable(errorCodes.FST_ERR_FAILED_ERROR_SERIALIZATION) +expectAssignable(errorCodes.FST_ERR_MISSING_SERIALIZATION_FN) +expectAssignable(errorCodes.FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN) +expectAssignable(errorCodes.FST_ERR_REQ_INVALID_VALIDATION_INVOCATION) +expectAssignable(errorCodes.FST_ERR_SCH_MISSING_ID) +expectAssignable(errorCodes.FST_ERR_SCH_ALREADY_PRESENT) +expectAssignable(errorCodes.FST_ERR_SCH_CONTENT_MISSING_SCHEMA) +expectAssignable(errorCodes.FST_ERR_SCH_DUPLICATE) +expectAssignable(errorCodes.FST_ERR_SCH_VALIDATION_BUILD) +expectAssignable(errorCodes.FST_ERR_SCH_SERIALIZATION_BUILD) +expectAssignable(errorCodes.FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX) +expectAssignable(errorCodes.FST_ERR_HTTP2_INVALID_VERSION) +expectAssignable(errorCodes.FST_ERR_INIT_OPTS_INVALID) +expectAssignable(errorCodes.FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE) +expectAssignable(errorCodes.FST_ERR_DUPLICATED_ROUTE) +expectAssignable(errorCodes.FST_ERR_BAD_URL) +expectAssignable(errorCodes.FST_ERR_ASYNC_CONSTRAINT) +expectAssignable(errorCodes.FST_ERR_DEFAULT_ROUTE_INVALID_TYPE) +expectAssignable(errorCodes.FST_ERR_INVALID_URL) +expectAssignable(errorCodes.FST_ERR_ROUTE_OPTIONS_NOT_OBJ) +expectAssignable(errorCodes.FST_ERR_ROUTE_DUPLICATED_HANDLER) +expectAssignable(errorCodes.FST_ERR_ROUTE_HANDLER_NOT_FN) +expectAssignable(errorCodes.FST_ERR_ROUTE_MISSING_HANDLER) +expectAssignable(errorCodes.FST_ERR_ROUTE_METHOD_INVALID) +expectAssignable(errorCodes.FST_ERR_ROUTE_METHOD_NOT_SUPPORTED) +expectAssignable(errorCodes.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED) +expectAssignable(errorCodes.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT) +expectAssignable(errorCodes.FST_ERR_ROUTE_REWRITE_NOT_STR) +expectAssignable(errorCodes.FST_ERR_REOPENED_CLOSE_SERVER) +expectAssignable(errorCodes.FST_ERR_REOPENED_SERVER) +expectAssignable(errorCodes.FST_ERR_INSTANCE_ALREADY_LISTENING) +expectAssignable(errorCodes.FST_ERR_PLUGIN_VERSION_MISMATCH) +expectAssignable(errorCodes.FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE) +expectAssignable(errorCodes.FST_ERR_PLUGIN_CALLBACK_NOT_FN) +expectAssignable(errorCodes.FST_ERR_PLUGIN_NOT_VALID) +expectAssignable(errorCodes.FST_ERR_ROOT_PLG_BOOTED) +expectAssignable(errorCodes.FST_ERR_PARENT_PLUGIN_BOOTED) +expectAssignable(errorCodes.FST_ERR_PLUGIN_TIMEOUT) diff --git a/types/errors.d.ts b/types/errors.d.ts index ab2c7dc2ad0..d529bb66ac2 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -2,6 +2,14 @@ import { FastifyErrorConstructor } from '@fastify/error' export type FastifyErrorCodes = Record< 'FST_ERR_NOT_FOUND' | +'FST_ERR_OPTIONS_NOT_OBJ' | +'FST_ERR_QSP_NOT_FN' | +'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN' | +'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN' | +'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ' | +'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR' | +'FST_ERR_VERSION_CONSTRAINT_NOT_STR' | +'FST_ERR_VALIDATION' | 'FST_ERR_CTP_ALREADY_PRESENT' | 'FST_ERR_CTP_INVALID_TYPE' | 'FST_ERR_CTP_EMPTY_TYPE' | @@ -11,65 +19,63 @@ export type FastifyErrorCodes = Record< 'FST_ERR_CTP_INVALID_MEDIA_TYPE' | 'FST_ERR_CTP_INVALID_CONTENT_LENGTH' | 'FST_ERR_CTP_EMPTY_JSON_BODY' | +'FST_ERR_CTP_INSTANCE_ALREADY_STARTED' | 'FST_ERR_DEC_ALREADY_PRESENT' | 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE' | 'FST_ERR_DEC_MISSING_DEPENDENCY' | 'FST_ERR_DEC_AFTER_START' | 'FST_ERR_HOOK_INVALID_TYPE' | 'FST_ERR_HOOK_INVALID_HANDLER' | +'FST_ERR_HOOK_INVALID_ASYNC_HANDLER' | +'FST_ERR_HOOK_NOT_SUPPORTED' | 'FST_ERR_MISSING_MIDDLEWARE' | 'FST_ERR_HOOK_TIMEOUT' | 'FST_ERR_LOG_INVALID_DESTINATION' | 'FST_ERR_LOG_INVALID_LOGGER' | 'FST_ERR_REP_INVALID_PAYLOAD_TYPE' | 'FST_ERR_REP_ALREADY_SENT' | -'FST_ERR_REP_SENT_VALUE'| -'FST_ERR_SEND_INSIDE_ONERR'| -'FST_ERR_SEND_UNDEFINED_ERR'| -'FST_ERR_BAD_STATUS_CODE'| +'FST_ERR_REP_SENT_VALUE' | +'FST_ERR_SEND_INSIDE_ONERR' | +'FST_ERR_SEND_UNDEFINED_ERR' | +'FST_ERR_BAD_STATUS_CODE' | 'FST_ERR_BAD_TRAILER_NAME' | 'FST_ERR_BAD_TRAILER_VALUE' | 'FST_ERR_FAILED_ERROR_SERIALIZATION' | 'FST_ERR_MISSING_SERIALIZATION_FN' | +'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN' | 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION' | 'FST_ERR_SCH_MISSING_ID' | 'FST_ERR_SCH_ALREADY_PRESENT' | +'FST_ERR_SCH_CONTENT_MISSING_SCHEMA' | 'FST_ERR_SCH_DUPLICATE' | 'FST_ERR_SCH_VALIDATION_BUILD' | 'FST_ERR_SCH_SERIALIZATION_BUILD' | +'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX' | 'FST_ERR_HTTP2_INVALID_VERSION' | 'FST_ERR_INIT_OPTS_INVALID' | 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE' | 'FST_ERR_DUPLICATED_ROUTE' | 'FST_ERR_BAD_URL' | +'FST_ERR_ASYNC_CONSTRAINT' | 'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE' | 'FST_ERR_INVALID_URL' | -'FST_ERR_REOPENED_CLOSE_SERVER' | -'FST_ERR_REOPENED_SERVER' | -'FST_ERR_PLUGIN_VERSION_MISMATCH' | -'FST_ERR_PLUGIN_CALLBACK_NOT_FN' | -'FST_ERR_PLUGIN_NOT_VALID' | -'FST_ERR_ROOT_PLG_BOOTED' | -'FST_ERR_PARENT_PLUGIN_BOOTED' | -'FST_ERR_PLUGIN_TIMEOUT' | -'FST_ERR_OPTIONS_NOT_OBJ' | -'FST_ERR_QSP_NOT_FN' | -'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN' | -'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN' | -'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ' | -'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR' | -'FST_ERR_VERSION_CONSTRAINT_NOT_STR' | -'FST_ERR_CTP_INSTANCE_ALREADY_STARTED' | -'FST_ERR_HOOK_NOT_SUPPORTED' | -'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX' | 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ' | 'FST_ERR_ROUTE_DUPLICATED_HANDLER' | 'FST_ERR_ROUTE_HANDLER_NOT_FN' | 'FST_ERR_ROUTE_MISSING_HANDLER' | +'FST_ERR_ROUTE_METHOD_INVALID' | 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED' | 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED' | 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT' | 'FST_ERR_ROUTE_REWRITE_NOT_STR' | +'FST_ERR_REOPENED_CLOSE_SERVER' | +'FST_ERR_REOPENED_SERVER' | +'FST_ERR_INSTANCE_ALREADY_LISTENING' | +'FST_ERR_PLUGIN_VERSION_MISMATCH' | 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE' | -'FST_ERR_INSTANCE_ALREADY_LISTENING' +'FST_ERR_PLUGIN_CALLBACK_NOT_FN' | +'FST_ERR_PLUGIN_NOT_VALID' | +'FST_ERR_ROOT_PLG_BOOTED' | +'FST_ERR_PARENT_PLUGIN_BOOTED' | +'FST_ERR_PLUGIN_TIMEOUT' , FastifyErrorConstructor> From 93000c691b341fa85b6b0daa75230856bb0cec32 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Sat, 8 Jul 2023 14:30:45 +0300 Subject: [PATCH 0375/1295] test: add missing assertion for a test (#4701) * Add missing assertion for a test * add address assertion --------- Co-authored-by: Manuel Spigolon Co-authored-by: Uzlopak --- test/server.test.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/server.test.js b/test/server.test.js index c946bbd6771..f5f5fb77021 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -35,11 +35,19 @@ test('listen should accept stringified number port', t => { }) test('listen should accept log text resolution function', t => { - t.plan(1) + t.plan(3) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: '1234', listenTextResolver: () => 'hardcoded text' }, (err) => { + fastify.listen({ + host: '127.0.0.1', + port: '1234', + listenTextResolver: (address) => { + t.equal(address, 'http://127.0.0.1:1234') + t.pass('executed') + return 'hardcoded text' + } + }, (err) => { t.error(err) }) }) From 2401461c81f9955975468a08d716adaf225a8a43 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sat, 8 Jul 2023 18:18:54 +0200 Subject: [PATCH 0376/1295] chore: tests for genReqIdFactory and minor changes (#4783) * perf: improve performance of request id generation * use 0 comparison instead of object reference comparison for better performance * optimize * smaller lookup array * remove perf changes, add jsdoc, export as named export and complete unit tests --- fastify.js | 2 +- lib/reqIdGenFactory.js | 21 +++- test/internals/reqIdGenFactory.test.js | 129 +++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 test/internals/reqIdGenFactory.test.js diff --git a/fastify.js b/fastify.js index bdb8c15792a..ae820f14905 100644 --- a/fastify.js +++ b/fastify.js @@ -41,7 +41,7 @@ const SchemaController = require('./lib/schema-controller') const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks') const { createLogger } = require('./lib/logger') const pluginUtils = require('./lib/pluginUtils') -const reqIdGenFactory = require('./lib/reqIdGenFactory') +const { reqIdGenFactory } = require('./lib/reqIdGenFactory') const { buildRouting, validateBodyLimitOption } = require('./lib/route') const build404 = require('./lib/fourOhFour') const getSecuredInitialConfig = require('./lib/initialConfigValidation') diff --git a/lib/reqIdGenFactory.js b/lib/reqIdGenFactory.js index c8c36d0bea1..68a2dad314b 100644 --- a/lib/reqIdGenFactory.js +++ b/lib/reqIdGenFactory.js @@ -1,14 +1,26 @@ 'use strict' -module.exports = function (requestIdHeader, optGenReqId) { +/** + * @callback GenerateRequestId + * @param {Object} req + * @returns {string} + */ + +/** + * @param {string} [requestIdHeader] + * @param {GenerateRequestId} [optGenReqId] + * @returns {GenerateRequestId} + */ +function reqIdGenFactory (requestIdHeader, optGenReqId) { // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8). // With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days. // This is very likely to happen in real-world applications, hence the limit is enforced. // Growing beyond this value will make the id generation slower and cause a deopt. // In the worst cases, it will become a float, losing accuracy. const maxInt = 2147483647 + let nextReqId = 0 - function defaultGenReqId (req) { + function defaultGenReqId (_req) { nextReqId = (nextReqId + 1) & maxInt return `req-${nextReqId.toString(36)}` } @@ -16,7 +28,6 @@ module.exports = function (requestIdHeader, optGenReqId) { const genReqId = optGenReqId || defaultGenReqId if (requestIdHeader) { - // requestIdHeader = typeof requestIdHeader === 'string' ? requestIdHeader : 'request-id' return function (req) { return req.headers[requestIdHeader] || genReqId(req) } @@ -24,3 +35,7 @@ module.exports = function (requestIdHeader, optGenReqId) { return genReqId } + +module.exports = { + reqIdGenFactory +} diff --git a/test/internals/reqIdGenFactory.test.js b/test/internals/reqIdGenFactory.test.js new file mode 100644 index 00000000000..f06b05deea1 --- /dev/null +++ b/test/internals/reqIdGenFactory.test.js @@ -0,0 +1,129 @@ +'use strict' + +const { test } = require('tap') +const { reqIdGenFactory } = require('../../lib/reqIdGenFactory') + +test('should create incremental ids deterministically', t => { + t.plan(9999) + const reqIdGen = reqIdGenFactory() + + for (let i = 1; i < 1e4; ++i) { + t.equal(reqIdGen(), 'req-' + i.toString(36)) + } +}) + +test('should have prefix "req-"', t => { + t.plan(1) + const reqIdGen = reqIdGenFactory() + + t.ok(reqIdGen().startsWith('req-')) +}) + +test('different id generator functions should have separate internal counters', t => { + t.plan(5) + const reqIdGenA = reqIdGenFactory() + const reqIdGenB = reqIdGenFactory() + + t.equal(reqIdGenA(), 'req-1') + t.equal(reqIdGenA(), 'req-2') + t.equal(reqIdGenB(), 'req-1') + t.equal(reqIdGenA(), 'req-3') + t.equal(reqIdGenB(), 'req-2') +}) + +test('should start counting with 1', t => { + t.plan(1) + const reqIdGen = reqIdGenFactory() + + t.equal(reqIdGen(), 'req-1') +}) + +test('should handle requestIdHeader and return provided id in header', t => { + t.plan(1) + + const reqIdGen = reqIdGenFactory('id') + + t.equal(reqIdGen({ headers: { id: '1337' } }), '1337') +}) + +test('should handle requestIdHeader and fallback if id is not provided in header', t => { + t.plan(1) + + const reqIdGen = reqIdGenFactory('id') + + t.equal(reqIdGen({ headers: { notId: '1337' } }), 'req-1') +}) + +test('should handle requestIdHeader and increment internal counter if no header was provided', t => { + t.plan(4) + + const reqIdGen = reqIdGenFactory('id') + + t.equal(reqIdGen({ headers: {} }), 'req-1') + t.equal(reqIdGen({ headers: {} }), 'req-2') + t.equal(reqIdGen({ headers: { id: '1337' } }), '1337') + t.equal(reqIdGen({ headers: {} }), 'req-3') +}) + +test('should use optGenReqId to generate ids', t => { + t.plan(4) + + let i = 1 + let gotCalled = false + function optGenReqId () { + gotCalled = true + return (i++).toString(16) + } + const reqIdGen = reqIdGenFactory(undefined, optGenReqId) + + t.equal(gotCalled, false) + t.equal(reqIdGen(), '1') + t.equal(gotCalled, true) + t.equal(reqIdGen(), '2') +}) + +test('should use optGenReqId to generate ids if requestIdHeader is used but not provided', t => { + t.plan(4) + + let i = 1 + let gotCalled = false + function optGenReqId () { + gotCalled = true + return (i++).toString(16) + } + const reqIdGen = reqIdGenFactory('reqId', optGenReqId) + + t.equal(gotCalled, false) + t.equal(reqIdGen({ headers: {} }), '1') + t.equal(gotCalled, true) + t.equal(reqIdGen({ headers: {} }), '2') +}) + +test('should not use optGenReqId to generate ids if requestIdHeader is used and provided', t => { + t.plan(2) + + function optGenReqId () { + t.fail() + } + const reqIdGen = reqIdGenFactory('reqId', optGenReqId) + + t.equal(reqIdGen({ headers: { reqId: 'r1' } }), 'r1') + t.equal(reqIdGen({ headers: { reqId: 'r2' } }), 'r2') +}) + +test('should fallback to use optGenReqId to generate ids if requestIdHeader is sometimes provided', t => { + t.plan(4) + + let i = 1 + let gotCalled = false + function optGenReqId () { + gotCalled = true + return (i++).toString(16) + } + const reqIdGen = reqIdGenFactory('reqId', optGenReqId) + + t.equal(reqIdGen({ headers: { reqId: 'r1' } }), 'r1') + t.equal(gotCalled, false) + t.equal(reqIdGen({ headers: {} }), '1') + t.equal(gotCalled, true) +}) From 18918dfcc2906e659c2d7456be1425ac3f2e768e Mon Sep 17 00:00:00 2001 From: Vladyslav Yukhanov <46359576+xijdk@users.noreply.github.com> Date: Sat, 8 Jul 2023 20:48:56 +0200 Subject: [PATCH 0377/1295] Added type definition for allowUnsafeRegex (#4792) --- fastify.d.ts | 1 + test/types/fastify.test-d.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/fastify.d.ts b/fastify.d.ts index 750ebc3c09b..423222049a7 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -106,6 +106,7 @@ declare namespace fastify { serializerOpts?: FJSOptions | Record, serverFactory?: FastifyServerFactory, caseSensitive?: boolean, + allowUnsafeRegex?: boolean, requestIdHeader?: string | false, requestIdLogLabel?: string; jsonShorthand?: boolean; diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 6264fb78abd..230cf678fb1 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -261,3 +261,7 @@ expectType(routeGeneric.Reply) // ErrorCodes expectType(fastify.errorCodes) + +fastify({ allowUnsafeRegex: true }) +fastify({ allowUnsafeRegex: false }) +expectError(fastify({ allowUnsafeRegex: 'invalid' })) From 63669c73fa24f0f952a70154289d5999a062ef14 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Sun, 9 Jul 2023 03:59:55 -0400 Subject: [PATCH 0378/1295] docs(security): update policy on ci/cd reports (#4890) --- SECURITY.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index 23ea944aa5d..ef95ffcf90a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -27,6 +27,15 @@ reported vulnerabilities: validity of the report. In any case, the report should follow the same process as outlined below of inviting the maintainers to review and accept the vulnerability. +* ***Do not*** attempt to show CI/CD vulnerabilities by creating new pull + requests to any of the Fastify organization's repositories. Doing so will + result in a [content report][cr] to GitHub as an unsolicited exploit. + The proper way to provide such reports is by creating a new repository, + configured in the same manner as the repository you would like to submit + a report about, and with a pull request to your own repository showing + the proof of concept. + +[cr]: https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam#reporting-an-issue-or-pull-request ### Vulnerabilities found outside this process From 58dfb001fba96b25d24d169043460e301fb78b48 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sun, 9 Jul 2023 15:13:46 +0200 Subject: [PATCH 0379/1295] improve hooks.validate (#4804) * improve hooks.validate * Update lib/hooks.js Co-authored-by: Manuel Spigolon --------- Co-authored-by: Manuel Spigolon --- lib/hooks.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/hooks.js b/lib/hooks.js index 7c0bff02293..fecb42107eb 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -52,9 +52,11 @@ function Hooks () { this.preClose = [] } +Hooks.prototype = Object.create(null) + Hooks.prototype.validate = function (hook, fn) { if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE() - if (supportedHooks.indexOf(hook) === -1) { + if (Array.isArray(this[hook]) === false) { throw new FST_ERR_HOOK_NOT_SUPPORTED(hook) } if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(fn)) From c170beadc475d40b224c0fbe0a37572c9f9c9cfd Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sun, 9 Jul 2023 16:39:05 +0200 Subject: [PATCH 0380/1295] ci: enable caching; split jobs (#4889) * improve ci-yml * fix wf * fix wf * fix wf * fix wf * use built-in cache option of actions/setup-node --- .github/workflows/ci.yml | 124 ++++++++++++++++++++--------- .github/workflows/coverage-nix.yml | 10 +-- .github/workflows/coverage-win.yml | 10 +-- 3 files changed, 92 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21cbae700b4..19e1cb9d214 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,8 @@ jobs: - name: Dependency review uses: actions/dependency-review-action@v3 - linter: + check-licenses: + name: Check licenses runs-on: ubuntu-latest permissions: contents: read @@ -49,6 +50,33 @@ jobs: uses: actions/setup-node@v3 with: node-version: 'lts/*' + cache: 'npm' + cache-dependency-path: package.json + + - name: Install + run: | + npm install --ignore-scripts + + - name: Check licenses + run: | + npm run license-checker + + lint: + name: Lint + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 'lts/*' + cache: 'npm' + cache-dependency-path: package.json - name: Install run: | @@ -59,18 +87,22 @@ jobs: npm run lint coverage-nix: - needs: linter + needs: lint permissions: contents: read uses: ./.github/workflows/coverage-nix.yml + coverage-win: - needs: linter + needs: lint permissions: contents: read uses: ./.github/workflows/coverage-win.yml - test: - needs: [linter, coverage-nix, coverage-win] + test-unit: + needs: + - lint + - coverage-nix + - coverage-win runs-on: ${{ matrix.os }} permissions: contents: read @@ -93,45 +125,53 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - - - uses: actions/cache@v3 - id: check-cache - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package.json') }} - restore-keys: | - ${{ runner.os }}-node-${{ matrix.node-version }}- + cache: 'npm' + cache-dependency-path: package.json - name: Install run: | npm install --ignore-scripts - - name: Check licenses - run: | - npm run license-checker - - name: Run tests run: | - npm run test:ci + npm run unit env: NODE_OPTIONS: no-network-family-autoselection - automerge: - if: > - github.event_name == 'pull_request' && - github.event.pull_request.user.login == 'dependabot[bot]' - needs: test - runs-on: ubuntu-latest + test-typescript: + needs: + - lint + - coverage-nix + - coverage-win + runs-on: 'ubuntu-latest' permissions: - pull-requests: write - contents: write + contents: read + steps: - - uses: fastify/github-action-merge-dependabot@v3 + - uses: actions/checkout@v3 with: - github-token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false + + - uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: package.json + + - name: Install + run: | + npm install --ignore-scripts + + - name: Run typescript tests + run: | + npm run test:typescript + env: + NODE_OPTIONS: no-network-family-autoselection package: - needs: test + needs: + - test-typescript + - test-unit runs-on: ubuntu-latest permissions: contents: read @@ -143,13 +183,8 @@ jobs: uses: actions/setup-node@v3 with: node-version: 'lts/*' - - uses: actions/cache@v3 - id: check-cache - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} - restore-keys: | - ${{ runner.os }}-node- + cache: 'npm' + cache-dependency-path: package.json - name: install fastify run: | npm install --ignore-scripts @@ -165,3 +200,20 @@ jobs: - name: Test esbuild bundle run: | cd test/bundler/esbuild && npm run test + + automerge: + if: > + github.event_name == 'pull_request' && + github.event.pull_request.user.login == 'dependabot[bot]' + needs: + - test-typescript + - test-unit + - package + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + steps: + - uses: fastify/github-action-merge-dependabot@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index e356768049d..867014481be 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -16,14 +16,8 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 'lts/*' - - - uses: actions/cache@v3 - id: check-cache - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} - restore-keys: | - ${{ runner.os }}-node- + cache: 'npm' + cache-dependency-path: package.json - name: Install run: | diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 402ab292568..42bdcefd971 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -16,14 +16,8 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 'lts/*' - - - uses: actions/cache@v3 - id: check-cache - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} - restore-keys: | - ${{ runner.os }}-node- + cache: 'npm' + cache-dependency-path: package.json - name: Install run: | From 139a523a7e325bd994ee04392245dfe91f567486 Mon Sep 17 00:00:00 2001 From: Anderson Date: Sun, 9 Jul 2023 10:50:17 -0400 Subject: [PATCH 0381/1295] docs(plugins): note side-effect when using `await` in `fastify.register()` (#4846) * Add documentation for side-effect when using await in fastify.register() * Apply suggestions from code review Co-authored-by: Frazer Smith --------- Co-authored-by: Uzlopak Co-authored-by: Frazer Smith --- docs/Reference/Plugins.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Reference/Plugins.md b/docs/Reference/Plugins.md index 1e6a18f52b5..9b2952289d5 100644 --- a/docs/Reference/Plugins.md +++ b/docs/Reference/Plugins.md @@ -145,6 +145,10 @@ await fastify.ready() await fastify.listen({ port: 3000 }) ``` +*Note: Using `await` when registering a plugin loads the plugin +and the underlying dependency tree, "finalizing" the encapsulation process. +Any mutations to the plugin after it and its dependencies have been +loaded will not be reflected in the parent instance.* #### ESM support From c79b49e2aa2c7e210cb258a78a710376f6e17e38 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sun, 9 Jul 2023 17:11:06 +0200 Subject: [PATCH 0382/1295] [hooks] Refine hook runners (#4805) * remove hook iterator for onRequestAbortHookRunner * curry it * rename stream to payload * early exit with cb instead of next * rename payload to newPayload * change --- lib/handleRequest.js | 8 +-- lib/hooks.js | 112 +++++++++++++++++++++++------- lib/reply.js | 12 +--- lib/route.js | 56 +-------------- test/internals/hookRunner.test.js | 30 +++++--- 5 files changed, 119 insertions(+), 99 deletions(-) diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 84c9f640aa0..1c8c66216b5 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -1,7 +1,7 @@ 'use strict' const { validate: validateSchema } = require('./validation') -const { hookRunner, hookIterator } = require('./hooks') +const { onPreValidationHookRunner, onPreHandlerHookRunner } = require('./hooks') const wrapThenable = require('./wrapThenable') const { kReplyIsError, @@ -65,9 +65,8 @@ function handleRequest (err, request, reply) { function handler (request, reply) { try { if (request[kRouteContext].preValidation !== null) { - hookRunner( + onPreValidationHookRunner( request[kRouteContext].preValidation, - hookIterator, request, reply, preValidationCallback @@ -112,9 +111,8 @@ function validationCompleted (request, reply, validationErr) { // preHandler hook if (request[kRouteContext].preHandler !== null) { - hookRunner( + onPreHandlerHookRunner( request[kRouteContext].preHandler, - hookIterator, request, reply, preHandlerCallback diff --git a/lib/hooks.js b/lib/hooks.js index fecb42107eb..abe4b7064e9 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -32,7 +32,8 @@ const { const { kChildren, - kHooks + kHooks, + kRequestPayloadStream } = require('./symbols') function Hooks () { @@ -173,20 +174,77 @@ function hookRunnerApplication (hookName, boot, server, cb) { } } -function hookRunner (functions, runner, request, reply, cb) { - let i = 0 +function hookRunnerGenerator (iterator) { + return function hookRunner (functions, request, reply, cb) { + let i = 0 + + function next (err) { + if (err || i === functions.length) { + cb(err, request, reply) + return + } + + let result + try { + result = iterator(functions[i++], request, reply, next) + } catch (error) { + cb(error, request, reply) + return + } + if (result && typeof result.then === 'function') { + result.then(handleResolve, handleReject) + } + } + + function handleResolve () { + next() + } + + function handleReject (err) { + if (!err) { + err = new FST_ERR_SEND_UNDEFINED_ERR() + } - function next (err) { - if (err || i === functions.length) { cb(err, request, reply) + } + + next() + } +} + +function onResponseHookIterator (fn, request, reply, next) { + return fn(request, reply, next) +} + +const onResponseHookRunner = hookRunnerGenerator(onResponseHookIterator) +const onPreValidationHookRunner = hookRunnerGenerator(hookIterator) +const onPreHandlerHookRunner = hookRunnerGenerator(hookIterator) +const onTimeoutHookRunner = hookRunnerGenerator(hookIterator) +const onRequestHookRunner = hookRunnerGenerator(hookIterator) + +function onSendHookRunner (functions, request, reply, payload, cb) { + let i = 0 + + function next (err, newPayload) { + if (err) { + cb(err, request, reply, payload) + return + } + + if (newPayload !== undefined) { + payload = newPayload + } + + if (i === functions.length) { + cb(null, request, reply, payload) return } let result try { - result = runner(functions[i++], request, reply, next) + result = functions[i++](request, reply, payload, next) } catch (error) { - next(error) + cb(error, request, reply) return } if (result && typeof result.then === 'function') { @@ -194,8 +252,8 @@ function hookRunner (functions, runner, request, reply, cb) { } } - function handleResolve () { - next() + function handleResolve (newPayload) { + next(null, newPayload) } function handleReject (err) { @@ -203,37 +261,37 @@ function hookRunner (functions, runner, request, reply, cb) { err = new FST_ERR_SEND_UNDEFINED_ERR() } - cb(err, request, reply) + cb(err, request, reply, payload) } next() } -function onSendHookRunner (functions, request, reply, payload, cb) { +function preParsingHookRunner (functions, request, reply, cb) { let i = 0 function next (err, newPayload) { - if (err) { - cb(err, request, reply, payload) + if (reply.sent) { return } - if (newPayload !== undefined) { - payload = newPayload + if (typeof newPayload !== 'undefined') { + request[kRequestPayloadStream] = newPayload } - if (i === functions.length) { - cb(null, request, reply, payload) + if (err || i === functions.length) { + cb(err, request, reply) return } let result try { - result = functions[i++](request, reply, payload, next) + result = functions[i++](request, reply, request[kRequestPayloadStream], next) } catch (error) { - next(error) + cb(error, request, reply) return } + if (result && typeof result.then === 'function') { result.then(handleResolve, handleReject) } @@ -248,13 +306,13 @@ function onSendHookRunner (functions, request, reply, payload, cb) { err = new FST_ERR_SEND_UNDEFINED_ERR() } - cb(err, request, reply, payload) + cb(err, request, reply) } next() } -function onRequestAbortHookRunner (functions, runner, request, cb) { +function onRequestAbortHookRunner (functions, request, cb) { let i = 0 function next (err) { @@ -265,9 +323,9 @@ function onRequestAbortHookRunner (functions, runner, request, cb) { let result try { - result = runner(functions[i++], request, next) + result = functions[i++](request, next) } catch (error) { - next(error) + cb(error, request) return } if (result && typeof result.then === 'function') { @@ -298,11 +356,17 @@ function hookIterator (fn, request, reply, next) { module.exports = { Hooks, buildHooks, - hookRunner, + hookRunnerGenerator, + preParsingHookRunner, + onResponseHookRunner, onSendHookRunner, onRequestAbortHookRunner, hookIterator, hookRunnerApplication, + onPreHandlerHookRunner, + onPreValidationHookRunner, + onRequestHookRunner, + onTimeoutHookRunner, lifecycleHooks, supportedHooks } diff --git a/lib/reply.js b/lib/reply.js index 18af968a096..75b02394f55 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -23,7 +23,7 @@ const { kOptions, kRouteContext } = require('./symbols.js') -const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks') +const { onSendHookRunner, onResponseHookRunner, onPreHandlerHookRunner } = require('./hooks') const internals = require('./handleRequest')[Symbol.for('internals')] const loggerUtils = require('./logger') @@ -755,9 +755,8 @@ function setupResponseListeners (reply) { const ctx = reply[kRouteContext] if (ctx && ctx.onResponse !== null) { - hookRunner( + onResponseHookRunner( ctx.onResponse, - onResponseIterator, reply.request, reply, onResponseCallback @@ -771,10 +770,6 @@ function setupResponseListeners (reply) { reply.raw.on('error', onResFinished) } -function onResponseIterator (fn, request, reply, next) { - return fn(request, reply, next) -} - function onResponseCallback (err, request, reply) { if (reply.log[kDisableRequestLogging]) { return @@ -839,9 +834,8 @@ function notFound (reply) { // preHandler hook if (reply[kRouteContext].preHandler !== null) { - hookRunner( + onPreHandlerHookRunner( reply[kRouteContext].preHandler, - hookIterator, reply.request, reply, internals.preHandlerCallback diff --git a/lib/route.js b/lib/route.js index c2433552fb6..610d508d895 100644 --- a/lib/route.js +++ b/lib/route.js @@ -3,7 +3,7 @@ const FindMyWay = require('find-my-way') const Context = require('./context') const handleRequest = require('./handleRequest') -const { hookRunner, hookIterator, onRequestAbortHookRunner, lifecycleHooks } = require('./hooks') +const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks') const { supportedMethods } = require('./httpMethods') const { normalizeSchema } = require('./schemas') const { parseHeadOnSendHandlers } = require('./headRoute') @@ -20,7 +20,6 @@ const { FST_ERR_DEFAULT_ROUTE_INVALID_TYPE, FST_ERR_DUPLICATED_ROUTE, FST_ERR_INVALID_URL, - FST_ERR_SEND_UNDEFINED_ERR, FST_ERR_HOOK_INVALID_HANDLER, FST_ERR_ROUTE_OPTIONS_NOT_OBJ, FST_ERR_ROUTE_DUPLICATED_HANDLER, @@ -477,9 +476,8 @@ function buildRouting (options) { } if (context.onRequest !== null) { - hookRunner( + onRequestHookRunner( context.onRequest, - hookIterator, request, reply, runPreParsing @@ -494,7 +492,6 @@ function buildRouting (options) { if (req.aborted) { onRequestAbortHookRunner( context.onRequestAbort, - hookIterator, request, handleOnRequestAbortHooksErrors.bind(null, reply) ) @@ -519,9 +516,8 @@ function handleOnRequestAbortHooksErrors (reply, err) { function handleTimeout () { const { context, request, reply } = this._meta - hookRunner( + onTimeoutHookRunner( context.onTimeout, - hookIterator, request, reply, noop @@ -570,52 +566,6 @@ function runPreParsing (err, request, reply) { } } -function preParsingHookRunner (functions, request, reply, cb) { - let i = 0 - - function next (err, stream) { - if (reply.sent) { - return - } - - if (typeof stream !== 'undefined') { - request[kRequestPayloadStream] = stream - } - - if (err || i === functions.length) { - cb(err, request, reply) - return - } - - const fn = functions[i++] - let result - try { - result = fn(request, reply, request[kRequestPayloadStream], next) - } catch (error) { - next(error) - return - } - - if (result && typeof result.then === 'function') { - result.then(handleResolve, handleReject) - } - } - - function handleResolve (stream) { - next(null, stream) - } - - function handleReject (err) { - if (!err) { - err = new FST_ERR_SEND_UNDEFINED_ERR() - } - - next(err) - } - - next(null, request[kRequestPayloadStream]) -} - /** * Used within the route handler as a `net.Socket.close` event handler. * The purpose is to remove a socket from the tracked sockets collection when diff --git a/test/internals/hookRunner.test.js b/test/internals/hookRunner.test.js index 438e55c74d0..5dbb4d5e2a0 100644 --- a/test/internals/hookRunner.test.js +++ b/test/internals/hookRunner.test.js @@ -2,12 +2,14 @@ const t = require('tap') const test = t.test -const { hookRunner, onSendHookRunner } = require('../../lib/hooks') +const { hookRunnerGenerator, onSendHookRunner } = require('../../lib/hooks') test('hookRunner - Basic', t => { t.plan(9) - hookRunner([fn1, fn2, fn3], iterator, 'a', 'b', done) + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) function iterator (fn, a, b, done) { return fn(a, b, done) @@ -41,7 +43,9 @@ test('hookRunner - Basic', t => { test('hookRunner - In case of error should skip to done', t => { t.plan(7) - hookRunner([fn1, fn2, fn3], iterator, 'a', 'b', done) + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) function iterator (fn, a, b, done) { return fn(a, b, done) @@ -73,7 +77,9 @@ test('hookRunner - In case of error should skip to done', t => { test('hookRunner - Should handle throw', t => { t.plan(7) - hookRunner([fn1, fn2, fn3], iterator, 'a', 'b', done) + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) function iterator (fn, a, b, done) { return fn(a, b, done) @@ -105,7 +111,9 @@ test('hookRunner - Should handle throw', t => { test('hookRunner - Should handle promises', t => { t.plan(9) - hookRunner([fn1, fn2, fn3], iterator, 'a', 'b', done) + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) function iterator (fn, a, b, done) { return fn(a, b, done) @@ -139,7 +147,9 @@ test('hookRunner - Should handle promises', t => { test('hookRunner - In case of error should skip to done (with promises)', t => { t.plan(7) - hookRunner([fn1, fn2, fn3], iterator, 'a', 'b', done) + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) function iterator (fn, a, b, done) { return fn(a, b, done) @@ -171,8 +181,10 @@ test('hookRunner - In case of error should skip to done (with promises)', t => { test('hookRunner - Be able to exit before its natural end', t => { t.plan(4) + const hookRunner = hookRunnerGenerator(iterator) + let shouldStop = false - hookRunner([fn1, fn2, fn3], iterator, 'a', 'b', done) + hookRunner([fn1, fn2, fn3], 'a', 'b', done) function iterator (fn, a, b, done) { if (shouldStop) { @@ -208,7 +220,9 @@ test('hookRunner - Promises that resolve to a value do not change the state', t const originalState = { a: 'a', b: 'b' } - hookRunner([fn1, fn2, fn3], iterator, originalState, 'b', done) + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], originalState, 'b', done) function iterator (fn, state, b, done) { return fn(state, b, done) From f449b8af736da8f39c295f5f3e2576dd1aaa624b Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 10 Jul 2023 18:15:57 +1000 Subject: [PATCH 0383/1295] fix wrong header size in docs (#4893) "FST_ERR_ASYNC_CONSTRAINT" was set to a heading size that gave it a section title and was inconsistent with the rest of the docs on this page, quick and easy 1 character fix --- docs/Reference/Errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 6ce53514a1e..257cb601c1b 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -394,7 +394,7 @@ The HTTP method already has a registered controller for that URL The router received an invalid url. -### FST_ERR_ASYNC_CONSTRAINT +#### FST_ERR_ASYNC_CONSTRAINT The router received an error when using asynchronous constraints. From 8f9f3677d7bd3682974a75b060e6d0aeca809091 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 10 Jul 2023 11:40:53 +0200 Subject: [PATCH 0384/1295] fix: rework FastifyErrors, ensure documentation completeness (#4892) * fix: rework FastifyErrors, ensure documentation completeness * Update lib/errors.js Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> * Update test/internals/errors.test.js --------- Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --- docs/Reference/Errors.md | 47 ++++++++++++++---------- lib/errors.js | 56 +++++++++++++++++++---------- test/internals/errors.test.js | 67 ++++++++++++++++++++++++----------- 3 files changed, 114 insertions(+), 56 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 257cb601c1b..8ce6f156207 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -168,8 +168,6 @@ ajv.plugins option should be an array. Version constraint should be a string. - - #### FST_ERR_CTP_ALREADY_PRESENT @@ -260,6 +258,11 @@ The hook name must be a string. The hook callback must be a function. +#### FST_ERR_HOOK_INVALID_ASYNC_HANDLER + + +Async function has too many arguments. Async hooks should not use the `done` argument. + #### FST_ERR_HOOK_NOT_SUPPORTED @@ -271,8 +274,8 @@ The hook is not supported. You must register a plugin for handling middlewares, visit [`Middleware`](./Middleware.md) for more info. - #### FST_ERR_HOOK_TIMEOUT + A callback for a hook timed out @@ -327,11 +330,21 @@ Called `reply.trailer` with an invalid header name. Called `reply.trailer` with an invalid type. Expected a function. +#### FST_ERR_FAILED_ERROR_SERIALIZATION + + +Failed to serialize an error. + #### FST_ERR_MISSING_SERIALIZATION_FN Missing serialization function. +#### FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN + + +Missing serialization function. + #### FST_ERR_REQ_INVALID_VALIDATION_INVOCATION @@ -348,6 +361,11 @@ The schema provided does not have `$id` property. A schema with the same `$id` already exists. +#### FST_ERR_SCH_CONTENT_MISSING_SCHEMA + + +A schema is missing for the corresponding content type. + #### FST_ERR_SCH_DUPLICATE @@ -384,8 +402,8 @@ Invalid initialization options. Cannot set forceCloseConnections to `idle` as your HTTP server does not support `closeIdleConnections` method. - #### FST_ERR_DUPLICATED_ROUTE + The HTTP method already has a registered controller for that URL @@ -469,44 +487,37 @@ Fastify is already listening. Installed Fastify plugin mismatched expected version. - - #### FST_ERR_PLUGIN_CALLBACK_NOT_FN + Callback for a hook is not a function (mapped directly from `avvio`) - - #### FST_ERR_PLUGIN_NOT_VALID + Plugin must be a function or a promise. - - #### FST_ERR_ROOT_PLG_BOOTED + Root plugin has already booted (mapped directly from `avvio`) - - #### FST_ERR_PARENT_PLUGIN_BOOTED + Impossible to load plugin because the parent (mapped directly from `avvio`) - - #### FST_ERR_PLUGIN_TIMEOUT + Plugin did not start in time. Default timeout (in millis): `10000` - - #### FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE + The decorator is not present in the instance. - - #### FST_ERR_VALIDATION + The Request failed the payload validation. diff --git a/lib/errors.js b/lib/errors.js index 192c0cdb0c7..3a4df61d410 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -20,32 +20,38 @@ const codes = { FST_ERR_QSP_NOT_FN: createError( 'FST_ERR_QSP_NOT_FN', "querystringParser option should be a function, instead got '%s'", - 500 + 500, + TypeError ), FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN: createError( 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN', "schemaController.bucket option should be a function, instead got '%s'", - 500 + 500, + TypeError ), FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN: createError( 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN', "schemaErrorFormatter option should be a non async function. Instead got '%s'.", - 500 + 500, + TypeError ), FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ: createError( 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ', - "sajv.customOptions option should be an object, instead got '%s'", - 500 + "ajv.customOptions option should be an object, instead got '%s'", + 500, + TypeError ), FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR: createError( 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR', - "sajv.plugins option should be an array, instead got '%s'", - 500 + "ajv.plugins option should be an array, instead got '%s'", + 500, + TypeError ), FST_ERR_VERSION_CONSTRAINT_NOT_STR: createError( 'FST_ERR_VERSION_CONSTRAINT_NOT_STR', 'Version constraint should be a string.', - 500 + 500, + TypeError ), FST_ERR_VALIDATION: createError( 'FST_ERR_VALIDATION', @@ -121,7 +127,9 @@ const codes = { ), FST_ERR_DEC_DEPENDENCY_INVALID_TYPE: createError( 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE', - "The dependencies of decorator '%s' must be of type Array." + "The dependencies of decorator '%s' must be of type Array.", + 500, + TypeError ), FST_ERR_DEC_MISSING_DEPENDENCY: createError( 'FST_ERR_DEC_MISSING_DEPENDENCY', @@ -184,7 +192,9 @@ const codes = { FST_ERR_LOG_INVALID_LOGGER: createError( 'FST_ERR_LOG_INVALID_LOGGER', - "Invalid logger object provided. The logger instance should have these functions(s): '%s'." + "Invalid logger object provided. The logger instance should have these functions(s): '%s'.", + 500, + TypeError ), /** @@ -202,7 +212,9 @@ const codes = { ), FST_ERR_REP_SENT_VALUE: createError( 'FST_ERR_REP_SENT_VALUE', - 'The only possible value for reply.sent is true.' + 'The only possible value for reply.sent is true.', + 500, + TypeError ), FST_ERR_SEND_INSIDE_ONERR: createError( 'FST_ERR_SEND_INSIDE_ONERR', @@ -303,7 +315,8 @@ const codes = { FST_ERR_BAD_URL: createError( 'FST_ERR_BAD_URL', "'%s' is not a valid url component", - 400 + 400, + URIError ), FST_ERR_ASYNC_CONSTRAINT: createError( 'FST_ERR_ASYNC_CONSTRAINT', @@ -319,12 +332,14 @@ const codes = { FST_ERR_INVALID_URL: createError( 'FST_ERR_INVALID_URL', "URL must be a string. Received '%s'", - 400 + 400, + TypeError ), FST_ERR_ROUTE_OPTIONS_NOT_OBJ: createError( 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ', 'Options for "%s:%s" route must be an object', - 500 + 500, + TypeError ), FST_ERR_ROUTE_DUPLICATED_HANDLER: createError( 'FST_ERR_ROUTE_DUPLICATED_HANDLER', @@ -334,7 +349,8 @@ const codes = { FST_ERR_ROUTE_HANDLER_NOT_FN: createError( 'FST_ERR_ROUTE_HANDLER_NOT_FN', 'Error Handler for %s:%s route, if defined, must be a function', - 500 + 500, + TypeError ), FST_ERR_ROUTE_MISSING_HANDLER: createError( 'FST_ERR_ROUTE_MISSING_HANDLER', @@ -344,7 +360,8 @@ const codes = { FST_ERR_ROUTE_METHOD_INVALID: createError( 'FST_ERR_ROUTE_METHOD_INVALID', 'Provided method is invalid!', - 500 + 500, + TypeError ), FST_ERR_ROUTE_METHOD_NOT_SUPPORTED: createError( 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', @@ -365,7 +382,8 @@ const codes = { FST_ERR_ROUTE_REWRITE_NOT_STR: createError( 'FST_ERR_ROUTE_REWRITE_NOT_STR', 'Rewrite url for "%s" needs to be of type "string" but received "%s"', - 500 + 500, + TypeError ), /** @@ -401,7 +419,9 @@ const codes = { */ FST_ERR_PLUGIN_CALLBACK_NOT_FN: createError( 'FST_ERR_PLUGIN_CALLBACK_NOT_FN', - 'fastify-plugin: %s' + 'fastify-plugin: %s', + 500, + TypeError ), FST_ERR_PLUGIN_NOT_VALID: createError( 'FST_ERR_PLUGIN_NOT_VALID', diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 7d133c7877c..fd0b7b40f8e 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -2,6 +2,8 @@ const { test } = require('tap') const errors = require('../../lib/errors') +const { readFileSync } = require('fs') +const { resolve } = require('path') test('should expose 77 errors', t => { t.plan(1) @@ -52,7 +54,7 @@ test('FST_ERR_QSP_NOT_FN', t => { t.equal(error.code, 'FST_ERR_QSP_NOT_FN') t.equal(error.message, "querystringParser option should be a function, instead got '%s'") t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN', t => { @@ -62,7 +64,7 @@ test('FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN', t => { t.equal(error.code, 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN') t.equal(error.message, "schemaController.bucket option should be a function, instead got '%s'") t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN', t => { @@ -72,7 +74,7 @@ test('FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN', t => { t.equal(error.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') t.equal(error.message, "schemaErrorFormatter option should be a non async function. Instead got '%s'.") t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ', t => { @@ -80,9 +82,9 @@ test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ', t => { const error = new errors.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ() t.equal(error.name, 'FastifyError') t.equal(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ') - t.equal(error.message, "sajv.customOptions option should be an object, instead got '%s'") + t.equal(error.message, "ajv.customOptions option should be an object, instead got '%s'") t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR', t => { @@ -90,9 +92,9 @@ test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR', t => { const error = new errors.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR() t.equal(error.name, 'FastifyError') t.equal(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR') - t.equal(error.message, "sajv.plugins option should be an array, instead got '%s'") + t.equal(error.message, "ajv.plugins option should be an array, instead got '%s'") t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_VERSION_CONSTRAINT_NOT_STR', t => { @@ -102,7 +104,7 @@ test('FST_ERR_VERSION_CONSTRAINT_NOT_STR', t => { t.equal(error.code, 'FST_ERR_VERSION_CONSTRAINT_NOT_STR') t.equal(error.message, 'Version constraint should be a string.') t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_CTP_ALREADY_PRESENT', t => { @@ -162,7 +164,7 @@ test('FST_ERR_CTP_BODY_TOO_LARGE', t => { t.equal(error.code, 'FST_ERR_CTP_BODY_TOO_LARGE') t.equal(error.message, 'Request body is too large') t.equal(error.statusCode, 413) - t.ok(error instanceof Error) + t.ok(error instanceof RangeError) }) test('FST_ERR_CTP_INVALID_MEDIA_TYPE', t => { @@ -182,7 +184,7 @@ test('FST_ERR_CTP_INVALID_CONTENT_LENGTH', t => { t.equal(error.code, 'FST_ERR_CTP_INVALID_CONTENT_LENGTH') t.equal(error.message, 'Request body size did not match Content-Length') t.equal(error.statusCode, 400) - t.ok(error instanceof Error) + t.ok(error instanceof RangeError) }) test('FST_ERR_CTP_EMPTY_JSON_BODY', t => { @@ -222,7 +224,7 @@ test('FST_ERR_DEC_DEPENDENCY_INVALID_TYPE', t => { t.equal(error.code, 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE') t.equal(error.message, "The dependencies of decorator '%s' must be of type Array.") t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_DEC_MISSING_DEPENDENCY', t => { @@ -322,7 +324,7 @@ test('FST_ERR_LOG_INVALID_LOGGER', t => { t.equal(error.code, 'FST_ERR_LOG_INVALID_LOGGER') t.equal(error.message, "Invalid logger object provided. The logger instance should have these functions(s): '%s'.") t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => { @@ -352,7 +354,7 @@ test('FST_ERR_REP_SENT_VALUE', t => { t.equal(error.code, 'FST_ERR_REP_SENT_VALUE') t.equal(error.message, 'The only possible value for reply.sent is true.') t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_SEND_INSIDE_ONERR', t => { @@ -592,7 +594,7 @@ test('FST_ERR_INVALID_URL', t => { t.equal(error.code, 'FST_ERR_INVALID_URL') t.equal(error.message, "URL must be a string. Received '%s'") t.equal(error.statusCode, 400) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_OPTIONS_NOT_OBJ', t => { @@ -602,7 +604,7 @@ test('FST_ERR_ROUTE_OPTIONS_NOT_OBJ', t => { t.equal(error.code, 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ') t.equal(error.message, 'Options for "%s:%s" route must be an object') t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_DUPLICATED_HANDLER', t => { @@ -622,7 +624,7 @@ test('FST_ERR_ROUTE_HANDLER_NOT_FN', t => { t.equal(error.code, 'FST_ERR_ROUTE_HANDLER_NOT_FN') t.equal(error.message, 'Error Handler for %s:%s route, if defined, must be a function') t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_MISSING_HANDLER', t => { @@ -642,7 +644,7 @@ test('FST_ERR_ROUTE_METHOD_INVALID', t => { t.equal(error.code, 'FST_ERR_ROUTE_METHOD_INVALID') t.equal(error.message, 'Provided method is invalid!') t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', t => { @@ -682,7 +684,7 @@ test('FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', t => { t.equal(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') t.equal(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_REWRITE_NOT_STR', t => { @@ -692,7 +694,7 @@ test('FST_ERR_ROUTE_REWRITE_NOT_STR', t => { t.equal(error.code, 'FST_ERR_ROUTE_REWRITE_NOT_STR') t.equal(error.message, 'Rewrite url for "%s" needs to be of type "string" but received "%s"') t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_REOPENED_CLOSE_SERVER', t => { @@ -752,7 +754,7 @@ test('FST_ERR_PLUGIN_CALLBACK_NOT_FN', t => { t.equal(error.code, 'FST_ERR_PLUGIN_CALLBACK_NOT_FN') t.equal(error.message, 'fastify-plugin: %s') t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.ok(error instanceof TypeError) }) test('FST_ERR_PLUGIN_NOT_VALID', t => { @@ -804,3 +806,28 @@ test('FST_ERR_VALIDATION', t => { t.equal(error.statusCode, 400) t.ok(error instanceof Error) }) + +test('Ensure that all errors are in Errors.md documented', t => { + t.plan(77) + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') + + const exportedKeys = Object.keys(errors) + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + t.ok(errorsMd.includes(`#### ${key}\n`), key) + } + } +}) + +test('Ensure that non-existing errors are not in Errors.md documented', t => { + t.plan(77) + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') + + const matchRE = /#### ([0-9a-zA-Z_]+)\n/g + const matches = errorsMd.matchAll(matchRE) + const exportedKeys = Object.keys(errors) + + for (const match of matches) { + t.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) + } +}) From 66bad3e68df5fa32e5fa36274b04a1fd4a266e86 Mon Sep 17 00:00:00 2001 From: Brett Willis Date: Tue, 11 Jul 2023 20:14:25 +1200 Subject: [PATCH 0385/1295] feat: add childLoggerFactory config (#4760) --- docs/Reference/Routes.md | 8 ++ docs/Reference/Server.md | 38 +++++ fastify.d.ts | 4 +- fastify.js | 40 ++++-- lib/context.js | 6 + lib/fourOhFour.js | 14 +- lib/logger.js | 43 +++++- lib/route.js | 19 +-- lib/symbols.js | 1 + test/500s.test.js | 18 +++ test/childLoggerFactory.test.js | 91 ++++++++++++ .../encapsulated-child-logger-factory.test.js | 69 +++++++++ test/fastify-instance.test.js | 34 ++++- test/inject.test.js | 3 +- test/internals/initialConfig.test.js | 11 +- test/internals/request.test.js | 18 ++- test/route.test.js | 135 ++++++++++++++++++ test/types/instance.test-d.ts | 33 +++++ test/types/route.test-d.ts | 3 + types/instance.d.ts | 28 +++- types/logger.d.ts | 25 ++++ types/route.d.ts | 3 +- 22 files changed, 599 insertions(+), 45 deletions(-) create mode 100644 test/childLoggerFactory.test.js create mode 100644 test/encapsulated-child-logger-factory.test.js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 20ca9b8e86e..de2132b9ecf 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -90,6 +90,14 @@ fastify.route(options) To access the default handler, you can access `instance.errorHandler`. Note that this will point to fastify's default `errorHandler` only if a plugin hasn't overridden it already. +* `childLoggerFactory(logger, binding, opts, rawReq)`: a custom factory function + that will be called to produce a child logger instance for every request. + See [`childLoggerFactory`](./Server.md#childloggerfactory) for more info. + Overrides the default logger factory, and anything set by + [`setChildLoggerFactory`](./Server.md#setchildloggerfactory), for requests to + the route. To access the default factory, you can access + `instance.childLoggerFactory`. Note that this will point to Fastify's default + `childLoggerFactory` only if a plugin hasn't overridden it already. * `validatorCompiler({ schema, method, url, httpPart })`: function that builds schemas for request validations. See the [Validation and Serialization](./Validation-and-Serialization.md#schema-validator) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 61f6a8575b1..e41670cd553 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -80,6 +80,7 @@ describes the properties available in that options object. - [schemaController](#schemacontroller) - [setNotFoundHandler](#setnotfoundhandler) - [setErrorHandler](#seterrorhandler) + - [setChildLoggerFactory](#setchildloggerfactory) - [addConstraintStrategy](#addconstraintstrategy) - [hasConstraintStrategy](#hasconstraintstrategy) - [printRoutes](#printroutes) @@ -91,6 +92,7 @@ describes the properties available in that options object. - [getDefaultJsonParser](#getdefaultjsonparser) - [defaultTextParser](#defaulttextparser) - [errorHandler](#errorhandler) + - [childLoggerFactory](#childloggerfactory) - [initialConfig](#initialconfig) ### `http` @@ -1537,6 +1539,35 @@ if (statusCode >= 500) { log.error(error) } ``` +#### setChildLoggerFactory + + +`fastify.setChildLoggerFactory(factory(logger, bindings, opts, rawReq))`: Set a +function that will be called when creating a child logger instance for each request +which allows for modifying or adding child logger bindings and logger options, or +returning a completely custom child logger implementation. + +Child logger bindings have a performance advantage over per-log bindings, because +they are pre-serialised by Pino when the child logger is created. + +The first parameter is the parent logger instance, followed by the default bindings +and logger options which should be passed to the child logger, and finally +the raw request (not a Fastify request object). The function is bound with `this` +being the Fastify instance. + +For example: +```js +const fastify = require('fastify')({ + childLoggerFactory: function (logger, bindings, opts, rawReq) { + // Calculate additional bindings from the request if needed + bindings.traceContext = rawReq.headers['x-cloud-trace-context'] + return logger.child(bindings, opts) + } +}) +``` + +The handler is bound to the Fastify instance and is fully encapsulated, so +different plugins can set different logger factories. #### addConstraintStrategy @@ -1810,6 +1841,13 @@ fastify.get('/', { }, handler) ``` +#### childLoggerFactory + + +`fastify.childLoggerFactory` returns the custom logger factory function for the +Fastify instance. See the [`childLoggerFactory` config option](#setchildloggerfactory) +for more info. + #### initialConfig diff --git a/fastify.d.ts b/fastify.d.ts index 423222049a7..4ea743b84aa 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -14,7 +14,7 @@ import { FastifyContext, FastifyContextConfig } from './types/context' import { FastifyErrorCodes } from './types/errors' import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler } from './types/hooks' import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' -import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel } from './types/logger' +import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel, Bindings, ChildLoggerOptions } from './types/logger' import { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './types/register' import { FastifyReply } from './types/reply' @@ -159,7 +159,7 @@ declare namespace fastify { /** * listener to error events emitted by client connections */ - clientErrorHandler?: (error: ConnectionError, socket: Socket) => void + clientErrorHandler?: (error: ConnectionError, socket: Socket) => void, } export interface ValidationResult { diff --git a/fastify.js b/fastify.js index ae820f14905..7c81c2743ef 100644 --- a/fastify.js +++ b/fastify.js @@ -28,18 +28,19 @@ const { kSchemaErrorFormatter, kErrorHandler, kKeepAliveConnections, - kFourOhFourContext + kChildLoggerFactory } = require('./lib/symbols.js') const { createServer, compileValidateHTTPVersion } = require('./lib/server') const Reply = require('./lib/reply') const Request = require('./lib/request') +const Context = require('./lib/context.js') const { supportedMethods } = require('./lib/httpMethods') const decorator = require('./lib/decorate') const ContentTypeParser = require('./lib/contentTypeParser') const SchemaController = require('./lib/schema-controller') const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks') -const { createLogger } = require('./lib/logger') +const { createLogger, createChildLogger, defaultChildLoggerFactory } = require('./lib/logger') const pluginUtils = require('./lib/pluginUtils') const { reqIdGenFactory } = require('./lib/reqIdGenFactory') const { buildRouting, validateBodyLimitOption } = require('./lib/route') @@ -74,14 +75,6 @@ const { const { buildErrorHandler } = require('./lib/error-handler.js') -const onBadUrlContext = { - config: { - }, - onSend: [], - onError: [], - [kFourOhFourContext]: null -} - function defaultBuildPrettyMeta (route) { // return a shallow copy of route's sanitized context @@ -95,6 +88,9 @@ function defaultBuildPrettyMeta (route) { return Object.assign({}, cleanKeys) } +/** + * @param {import('./fastify.js').FastifyServerOptions} options + */ function fastify (options) { // Options validations options = options || {} @@ -235,6 +231,7 @@ function fastify (options) { [kSchemaController]: schemaController, [kSchemaErrorFormatter]: null, [kErrorHandler]: buildErrorHandler(), + [kChildLoggerFactory]: defaultChildLoggerFactory, [kReplySerializerDefault]: null, [kContentTypeParser]: new ContentTypeParser( bodyLimit, @@ -340,6 +337,8 @@ function fastify (options) { // custom error handling setNotFoundHandler, setErrorHandler, + // child logger + setChildLoggerFactory, // Set fastify initial configuration options read-only object initialConfig, // constraint strategies @@ -379,6 +378,10 @@ function fastify (options) { configurable: true, get () { return this[kSchemaController].getSerializerCompiler() } }, + childLoggerFactory: { + configurable: true, + get () { return this[kChildLoggerFactory] } + }, version: { configurable: true, get () { return VERSION } @@ -465,6 +468,12 @@ function fastify (options) { }) }) + // Create bad URL context + const onBadUrlContext = new Context({ + server: fastify, + config: {} + }) + // Set the default 404 handler fastify.setNotFoundHandler() fourOhFour.arrange404(fastify) @@ -706,7 +715,7 @@ function fastify (options) { function onBadUrl (path, req, res) { if (frameworkErrors) { const id = genReqId(req) - const childLogger = logger.child({ reqId: id }) + const childLogger = createChildLogger(onBadUrlContext, logger, req, id) const request = new Request(id, null, req, null, childLogger, onBadUrlContext) const reply = new Reply(res, request, childLogger) @@ -731,7 +740,7 @@ function fastify (options) { if (err) { if (frameworkErrors) { const id = genReqId(req) - const childLogger = logger.child({ reqId: id }) + const childLogger = createChildLogger(onBadUrlContext, logger, req, id) const request = new Request(id, null, req, null, childLogger, onBadUrlContext) const reply = new Reply(res, request, childLogger) @@ -803,6 +812,13 @@ function fastify (options) { return this } + function setChildLoggerFactory (factory) { + throwIfAlreadyStarted('Cannot call "setChildLoggerFactory"!') + + this[kChildLoggerFactory] = factory + return this + } + function printRoutes (opts = {}) { // includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta diff --git a/lib/context.js b/lib/context.js index 5491df03c4d..e1584711966 100644 --- a/lib/context.js +++ b/lib/context.js @@ -5,6 +5,8 @@ const { kReplySerializerDefault, kSchemaErrorFormatter, kErrorHandler, + kChildLoggerFactory, + kOptions, kReply, kRequest, kBodyLimit, @@ -22,6 +24,8 @@ function Context ({ schema, handler, config, + requestIdLogLabel, + childLoggerFactory, errorHandler, bodyLimit, logLevel, @@ -51,6 +55,8 @@ function Context ({ this.onRequestAbort = null this.config = config this.errorHandler = errorHandler || server[kErrorHandler] + this.requestIdLogLabel = requestIdLogLabel || server[kOptions].requestIdLogLabel + this.childLoggerFactory = childLoggerFactory || server[kChildLoggerFactory] this._middie = null this._parserOptions = { limit: bodyLimit || server[kBodyLimit] diff --git a/lib/fourOhFour.js b/lib/fourOhFour.js index 32921a130a0..1b3eedbf1b7 100644 --- a/lib/fourOhFour.js +++ b/lib/fourOhFour.js @@ -15,16 +15,10 @@ const { } = require('./symbols.js') const { lifecycleHooks } = require('./hooks') const { buildErrorHandler } = require('./error-handler.js') -const fourOhFourContext = { - config: { - }, - onSend: [], - onError: [], - errorHandler: buildErrorHandler() -} const { FST_ERR_NOT_FOUND } = require('./errors') +const { createChildLogger } = require('./logger') /** * Each fastify instance have a: @@ -48,6 +42,7 @@ function fourOhFour (options) { instance[kCanSetNotFoundHandler] = true // we need to bind instance for the context router.onBadUrl = router.onBadUrl.bind(instance) + router.defaultRoute = router.defaultRoute.bind(instance) } function basic404 (request, reply) { @@ -64,8 +59,8 @@ function fourOhFour (options) { function createOnBadUrl () { return function onBadUrl (path, req, res) { const id = genReqId(req) - const childLogger = logger.child({ reqId: id }) const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext] + const childLogger = createChildLogger(fourOhFourContext, logger, req, id) const request = new Request(id, null, req, null, childLogger, fourOhFourContext) const reply = new Reply(res, request, childLogger) @@ -172,7 +167,8 @@ function fourOhFour (options) { // here, let's print out as much info as // we can const id = genReqId(req) - const childLogger = logger.child({ reqId: id }) + const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext] + const childLogger = createChildLogger(fourOhFourContext, logger, req, id) childLogger.info({ req }, 'incoming request') diff --git a/lib/logger.js b/lib/logger.js index a2934d1ee23..1c89af3b555 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -107,27 +107,64 @@ function createLogger (options) { * of a Fastify compatible logger. * * @param {object} logger Object to validate. + * @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing) * * @returns {boolean} `true` when the logger meets the requirements. * * @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is * missing required methods. */ -function validateLogger (logger) { +function validateLogger (logger, strict) { const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child'] - const missingMethods = methods.filter(method => !logger[method] || typeof logger[method] !== 'function') + const missingMethods = logger + ? methods.filter(method => !logger[method] || typeof logger[method] !== 'function') + : methods if (!missingMethods.length) { return true - } else if (missingMethods.length === methods.length) { + } else if ((missingMethods.length === methods.length) && !strict) { return false } else { throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(',')) } } +/** + * Utility for creating a child logger with the appropriate bindings, logger factory + * and validation. + * @param {object} context + * @param {import('../fastify').FastifyBaseLogger} logger + * @param {import('../fastify').RawRequestDefaultExpression} req + * @param {string} reqId + * @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts + */ +function createChildLogger (context, logger, req, reqId, loggerOpts) { + const loggerBindings = { + [context.requestIdLogLabel]: reqId + } + const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req) + + // Optimisation: bypass validation if the factory is our own default factory + if (context.childLoggerFactory !== defaultChildLoggerFactory) { + validateLogger(child, true) // throw if the child is not a valid logger + } + + return child +} + +/** + * @param {import('../fastify.js').FastifyBaseLogger} logger + * @param {import('../types/logger.js').Bindings} bindings + * @param {import('../types/logger.js').ChildLoggerOptions} opts + */ +function defaultChildLoggerFactory (logger, bindings, opts) { + return logger.child(bindings, opts) +} + module.exports = { createLogger, + createChildLogger, + defaultChildLoggerFactory, serializers, now } diff --git a/lib/route.js b/lib/route.js index 610d508d895..aa273f3dc52 100644 --- a/lib/route.js +++ b/lib/route.js @@ -50,13 +50,13 @@ const { kRouteContext } = require('./symbols.js') const { buildErrorHandler } = require('./error-handler') +const { createChildLogger } = require('./logger') function buildRouting (options) { const router = FindMyWay(options.config) let avvio let fourOhFour - let requestIdLogLabel let logger let hasLogger let setupResponseListeners @@ -73,6 +73,10 @@ function buildRouting (options) { let closing = false return { + /** + * @param {import('../fastify').FastifyServerOptions} options + * @param {*} fastifyArgs + */ setup (options, fastifyArgs) { avvio = fastifyArgs.avvio fourOhFour = fastifyArgs.fourOhFour @@ -83,7 +87,6 @@ function buildRouting (options) { validateHTTPVersion = fastifyArgs.validateHTTPVersion globalExposeHeadRoutes = options.exposeHeadRoutes - requestIdLogLabel = options.requestIdLogLabel genReqId = options.genReqId disableRequestLogging = options.disableRequestLogging ignoreTrailingSlash = options.ignoreTrailingSlash @@ -167,7 +170,10 @@ function buildRouting (options) { ) !== null } - // Route management + /** + * Route management + * @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }} + */ function route ({ options, isFastify }) { // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator const opts = { ...options } @@ -281,6 +287,7 @@ function buildRouting (options) { handler: opts.handler.bind(this), config, errorHandler: opts.errorHandler, + childLoggerFactory: opts.childLoggerFactory, bodyLimit: opts.bodyLimit, logLevel: opts.logLevel, logSerializers: opts.logSerializers, @@ -398,10 +405,6 @@ function buildRouting (options) { function routeHandler (req, res, params, context, query) { const id = genReqId(req) - const loggerBinding = { - [requestIdLogLabel]: id - } - const loggerOpts = { level: context.logLevel } @@ -409,7 +412,7 @@ function buildRouting (options) { if (context.logSerializers) { loggerOpts.serializers = context.logSerializers } - const childLogger = logger.child(loggerBinding, loggerOpts) + const childLogger = createChildLogger(context, logger, req, id, loggerOpts) childLogger[kDisableRequestLogging] = disableRequestLogging // TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core. diff --git a/lib/symbols.js b/lib/symbols.js index c5e2baab413..9a27f86f861 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -55,6 +55,7 @@ const keys = { // This symbol is only meant to be used for fastify tests and should not be used for any other purpose kTestInternals: Symbol('fastify.testInternals'), kErrorHandler: Symbol('fastify.errorHandler'), + kChildLoggerFactory: Symbol('fastify.childLoggerFactory'), kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'), kKeepAliveConnections: Symbol('fastify.keepAliveConnections'), kRouteByFastify: Symbol('fastify.routeByFastify') diff --git a/test/500s.test.js b/test/500s.test.js index 857e29607aa..b7b4eac9bc4 100644 --- a/test/500s.test.js +++ b/test/500s.test.js @@ -166,3 +166,21 @@ test('cannot set errorHandler after binding', t => { } }) }) + +test('cannot set childLoggerFactory after binding', t => { + t.plan(2) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + try { + fastify.setChildLoggerFactory(() => { }) + t.fail() + } catch (e) { + t.pass() + } + }) +}) diff --git a/test/childLoggerFactory.test.js b/test/childLoggerFactory.test.js new file mode 100644 index 00000000000..38817c81920 --- /dev/null +++ b/test/childLoggerFactory.test.js @@ -0,0 +1,91 @@ +'use strict' + +const { test } = require('tap') +const Fastify = require('..') + +test('Should accept a custom childLoggerFactory function', t => { + t.plan(4) + + const fastify = Fastify() + fastify.setChildLoggerFactory(function (logger, bindings, opts) { + t.ok(bindings.reqId) + t.ok(opts) + this.log.debug(bindings, 'created child logger') + return logger.child(bindings, opts) + }) + + fastify.get('/', (req, reply) => { + req.log.info('log message') + reply.send() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.inject({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, res) => { + t.error(err) + fastify.close() + }) + }) +}) + +test('req.log should be the instance returned by the factory', t => { + t.plan(3) + + const fastify = Fastify() + fastify.setChildLoggerFactory(function (logger, bindings, opts) { + this.log.debug('using root logger') + return this.log + }) + + fastify.get('/', (req, reply) => { + t.equal(req.log, fastify.log) + req.log.info('log message') + reply.send() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.inject({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, res) => { + t.error(err) + fastify.close() + }) + }) +}) + +test('should throw error if invalid logger is returned', t => { + t.plan(2) + + const fastify = Fastify() + fastify.setChildLoggerFactory(function () { + this.log.debug('returning an invalid logger, expect error') + return undefined + }) + + fastify.get('/', (req, reply) => { + reply.send() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.throws(() => { + try { + fastify.inject({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err) => { + t.fail('request should have failed but did not') + t.error(err) + fastify.close() + }) + } finally { + fastify.close() + } + }, { code: 'FST_ERR_LOG_INVALID_LOGGER' }) + }) +}) diff --git a/test/encapsulated-child-logger-factory.test.js b/test/encapsulated-child-logger-factory.test.js new file mode 100644 index 00000000000..1f857320471 --- /dev/null +++ b/test/encapsulated-child-logger-factory.test.js @@ -0,0 +1,69 @@ +'use strict' + +const { test } = require('tap') +const Fastify = require('..') +const fp = require('fastify-plugin') + +test('encapsulates an child logger factory', async t => { + t.plan(4) + + const fastify = Fastify() + fastify.register(async function (fastify) { + fastify.setChildLoggerFactory(function pluginFactory (logger, bindings, opts) { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.equal(message, 'custom') + } + return child + }) + fastify.get('/encapsulated', async (req) => { + req.log.customLog('custom') + }) + }) + + fastify.setChildLoggerFactory(function globalFactory (logger, bindings, opts) { + const child = logger.child(bindings, opts) + child.globalLog = function (message) { + t.equal(message, 'global') + } + return child + }) + fastify.get('/not-encapsulated', async (req) => { + req.log.globalLog('global') + }) + + const res1 = await fastify.inject('/encapsulated') + t.equal(res1.statusCode, 200) + + const res2 = await fastify.inject('/not-encapsulated') + t.equal(res2.statusCode, 200) +}) + +test('child logger factory set on root scope when using fastify-plugin', async t => { + t.plan(4) + + const fastify = Fastify() + fastify.register(fp(async function (fastify) { + // Using fastify-plugin, the factory should be set on the root scope + fastify.setChildLoggerFactory(function pluginFactory (logger, bindings, opts) { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.equal(message, 'custom') + } + return child + }) + fastify.get('/not-encapsulated-1', async (req) => { + req.log.customLog('custom') + }) + })) + + fastify.get('/not-encapsulated-2', async (req) => { + req.log.customLog('custom') + }) + + const res1 = await fastify.inject('/not-encapsulated-1') + t.equal(res1.statusCode, 200) + + const res2 = await fastify.inject('/not-encapsulated-2') + t.equal(res2.statusCode, 200) +}) diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index 3b347ac8ca3..32bfa80fef5 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -7,7 +7,8 @@ const os = require('os') const { kOptions, - kErrorHandler + kErrorHandler, + kChildLoggerFactory } = require('../lib/symbols') test('root fastify instance is an object', t => { @@ -100,6 +101,37 @@ test('errorHandler in plugin should be separate from the external one', async t t.same(fastify.errorHandler, fastify[kErrorHandler].func) }) +test('fastify instance should contain default childLoggerFactory', t => { + t.plan(3) + const fastify = Fastify() + t.ok(fastify[kChildLoggerFactory] instanceof Function) + t.same(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) + t.same(Object.getOwnPropertyDescriptor(fastify, 'childLoggerFactory').set, undefined) +}) + +test('childLoggerFactory in plugin should be separate from the external one', async t => { + t.plan(4) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + const inPluginLoggerFactory = function (logger, bindings, opts) { + return logger.child(bindings, opts) + } + + instance.setChildLoggerFactory(inPluginLoggerFactory) + + t.notSame(instance.childLoggerFactory, fastify.childLoggerFactory) + t.equal(instance.childLoggerFactory.name, 'inPluginLoggerFactory') + + done() + }) + + await fastify.ready() + + t.ok(fastify[kChildLoggerFactory] instanceof Function) + t.same(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) +}) + test('fastify instance should contains listeningOrigin property (with port and host)', async t => { t.plan(1) const port = 3000 diff --git a/test/inject.test.js b/test/inject.test.js index e728e4fd908..23810e23615 100644 --- a/test/inject.test.js +++ b/test/inject.test.js @@ -458,14 +458,13 @@ test('should handle errors in builder-style injection correctly', async (t) => { }) test('Should not throw on access to routeConfig frameworkErrors handler - FST_ERR_BAD_URL', t => { - t.plan(6) + t.plan(5) const fastify = Fastify({ frameworkErrors: function (err, req, res) { t.ok(typeof req.id === 'string') t.ok(req.raw instanceof Readable) t.same(req.routerPath, undefined) - t.same(req.routeConfig, undefined) res.send(`${err.message} - ${err.code}`) } }) diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index c90b91c552b..e58219f37ca 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -55,7 +55,7 @@ test('without options passed to Fastify, initialConfig should expose default val }) test('Fastify.initialConfig should expose all options', t => { - t.plan(20) + t.plan(21) const serverFactory = (handler, opts) => { const server = http.createServer((req, res) => { @@ -132,6 +132,7 @@ test('Fastify.initialConfig should expose all options', t => { t.equal(fastify.initialConfig.serverFactory, undefined) t.equal(fastify.initialConfig.trustProxy, undefined) t.equal(fastify.initialConfig.genReqId, undefined) + t.equal(fastify.initialConfig.childLoggerFactory, undefined) t.equal(fastify.initialConfig.querystringParser, undefined) t.equal(fastify.initialConfig.logger, undefined) t.equal(fastify.initialConfig.trustProxy, undefined) @@ -243,7 +244,7 @@ test('Original options must not be altered (test deep cloning)', t => { }) test('Should not have issues when passing stream options to Pino.js', t => { - t.plan(15) + t.plan(17) const stream = split(JSON.parse) @@ -259,6 +260,10 @@ test('Should not have issues when passing stream options to Pino.js', t => { try { fastify = Fastify(originalOptions) + fastify.setChildLoggerFactory(function (logger, bindings, opts) { + bindings.someBinding = 'value' + return logger.child(bindings, opts) + }) t.type(fastify, 'object') t.same(fastify.initialConfig, { @@ -297,6 +302,7 @@ test('Should not have issues when passing stream options to Pino.js', t => { stream.once('data', line => { const id = line.reqId t.ok(line.reqId, 'reqId is defined') + t.equal(line.someBinding, 'value', 'child logger binding is set') t.ok(line.req, 'req is defined') t.equal(line.msg, 'incoming request', 'message is set') t.equal(line.req.method, 'GET', 'method is get') @@ -304,6 +310,7 @@ test('Should not have issues when passing stream options to Pino.js', t => { stream.once('data', line => { t.equal(line.reqId, id) t.ok(line.reqId, 'reqId is defined') + t.equal(line.someBinding, 'value', 'child logger binding is set') t.ok(line.res, 'res is defined') t.equal(line.msg, 'request completed', 'message is set') t.equal(line.res.statusCode, 200, 'statusCode is 200') diff --git a/test/internals/request.test.js b/test/internals/request.test.js index f0173cbd5bb..425a72cf819 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -7,7 +7,8 @@ const Context = require('../../lib/context') const { kPublicRouteContext, kReply, - kRequest + kRequest, + kOptions } = require('../../lib/symbols') process.removeAllListeners('warning') @@ -39,7 +40,10 @@ test('Regular request', t => { }, server: { [kReply]: {}, - [kRequest]: Request + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + } } }) req.connection = req.socket @@ -97,7 +101,10 @@ test('Request with undefined config', t => { }, server: { [kReply]: {}, - [kRequest]: Request + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + } } }) req.connection = req.socket @@ -197,7 +204,10 @@ test('Request with trust proxy', t => { }, server: { [kReply]: {}, - [kRequest]: Request + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + } } }) diff --git a/test/route.test.js b/test/route.test.js index 6f403593b17..9fce94d8ed6 100644 --- a/test/route.test.js +++ b/test/route.test.js @@ -708,6 +708,141 @@ test('throws when route-level error handler is not a function', t => { } }) +test('route child logger factory overrides default child logger factory', t => { + t.plan(3) + + const fastify = Fastify() + + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.equal(message, 'custom') + } + return child + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + + fastify.inject({ + method: 'GET', + url: '/coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) +}) + +test('route child logger factory does not affect other routes', t => { + t.plan(6) + + const fastify = Fastify() + + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.equal(message, 'custom') + } + return child + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + + fastify.route({ + method: 'GET', + path: '/tea', + handler: (req, res) => { + t.notMatch(req.log.customLog instanceof Function) + res.send() + } + }) + + fastify.inject({ + method: 'GET', + url: '/coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) + fastify.inject({ + method: 'GET', + url: '/tea' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) +}) +test('route child logger factory overrides global custom error handler', t => { + t.plan(6) + + const fastify = Fastify() + + const customGlobalChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.globalLog = function (message) { + t.equal(message, 'global') + } + return child + } + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.equal(message, 'custom') + } + return child + } + + fastify.setChildLoggerFactory(customGlobalChildLogger) + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, res) => { + req.log.globalLog('global') + res.send() + } + }) + + fastify.inject({ + method: 'GET', + url: '/coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) + fastify.inject({ + method: 'GET', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) +}) + test('Creates a HEAD route for each GET one (default)', t => { t.plan(8) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 0a0b60192cc..80fe1e36339 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -1,5 +1,6 @@ import { expectAssignable, expectDeprecated, expectError, expectNotDeprecated, expectType } from 'tsd' import fastify, { + FastifyBaseLogger, FastifyBodyParser, FastifyError, FastifyInstance, @@ -13,6 +14,7 @@ import { FastifyRequest } from '../../types/request' import { DefaultRoute } from '../../types/route' import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema' import { AddressInfo } from 'net' +import { Bindings, ChildLoggerOptions } from '../../types/logger' const server = fastify() @@ -258,6 +260,37 @@ expectType(fastify().get('/', { } })) +expectType(fastify().get('/', { + handler: () => {}, + childLoggerFactory: (logger, bindings, opts, req) => { + expectAssignable(server.childLoggerFactory(logger, bindings, opts, req)) + return server.childLoggerFactory(logger, bindings, opts, req) + } +})) + +expectAssignable( + server.setChildLoggerFactory(function (logger, bindings, opts, req) { + expectType(logger) + expectType(bindings) + expectType(opts) + expectType(req) + expectAssignable(this) + return logger.child(bindings, opts) + }) +) + +expectAssignable( + server.setErrorHandler(function (error, request, reply) { + expectType(error) + }) +) + +function childLoggerFactory (this: FastifyInstance, logger: FastifyBaseLogger, bindings: Bindings, opts: ChildLoggerOptions, req: RawRequestDefaultExpression) { + return logger.child(bindings, opts) +} +server.setChildLoggerFactory(childLoggerFactory) +server.setChildLoggerFactory(server.childLoggerFactory) + type InitialConfig = Readonly<{ connectionTimeout?: number, keepAliveTimeout?: number, diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index eae73058d85..5ae041599ae 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -57,6 +57,9 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' errorHandler: (error, request, reply) => { expectType(error) reply.send('error') + }, + childLoggerFactory: function (logger, bindings, opts) { + return logger.child(bindings, opts) } })) diff --git a/types/instance.d.ts b/types/instance.d.ts index 1a2afc9d5b1..a387e902e3e 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -4,7 +4,7 @@ import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, preCloseHookHandler, preCloseAsyncHookHandler } from './hooks' -import { FastifyBaseLogger } from './logger' +import { FastifyBaseLogger, FastifyChildLoggerFactory } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' import { FastifyRequest } from './request' @@ -552,6 +552,32 @@ export interface FastifyInstance< handler: (this: FastifyInstance, error: TError, request: FastifyRequest, reply: FastifyReply) => any | Promise ): FastifyInstance; + /** + * Hook function that is called when creating a child logger instance for each request + * which allows for modifying or adding child logger bindings and logger options, or + * returning a completely custom child logger implementation. + */ + childLoggerFactory: FastifyChildLoggerFactory; + + /** + * Hook function that is called when creating a child logger instance for each request + * which allows for modifying or adding child logger bindings and logger options, or + * returning a completely custom child logger implementation. + * + * Child logger bindings have a performance advantage over per-log bindings, because + * they are pre-serialised by Pino when the child logger is created. + * + * For example: + * ``` + * function childLoggerFactory(logger, bindings, opts, rawReq) { + * // Calculate additional bindings from the request + * bindings.traceContext = rawReq.headers['x-cloud-trace-context'] + * return logger.child(bindings, opts); + * } + * ``` + */ + setChildLoggerFactory(factory: FastifyChildLoggerFactory): FastifyInstance; + /** * Fastify schema validator for all routes. */ diff --git a/types/logger.d.ts b/types/logger.d.ts index 6b698b31aaf..34647182a96 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -5,6 +5,7 @@ import { FastifyReply } from './reply' import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' import { FastifySchema } from './schema' +import { FastifyInstance } from './instance' import pino from 'pino' @@ -78,3 +79,27 @@ export interface FastifyLoggerOptions< genReqId?: (req: RawRequest) => string; stream?: FastifyLoggerStreamDestination; } + +export interface FastifyChildLoggerFactory< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + /** + * @param logger The parent logger + * @param bindings The bindings object that will be passed to the child logger + * @param childLoggerOpts The logger options that will be passed to the child logger + * @param rawReq The raw request + * @this The fastify instance + * @returns The child logger instance + */ + ( + this: FastifyInstance, + logger: Logger, + bindings: Bindings, + childLoggerOpts: ChildLoggerOptions, + rawReq: RawRequest + ): Logger +} diff --git a/types/route.d.ts b/types/route.d.ts index 8c511a37d82..1ded2c6a731 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -2,7 +2,7 @@ import { FastifyError } from '@fastify/error' import { FastifyContext } from './context' import { onErrorHookHandler, onRequestAbortHookHandler, onRequestHookHandler, onResponseHookHandler, onSendHookHandler, onTimeoutHookHandler, preHandlerHookHandler, preParsingHookHandler, preSerializationHookHandler, preValidationHookHandler } from './hooks' import { FastifyInstance } from './instance' -import { FastifyBaseLogger, LogLevel } from './logger' +import { FastifyBaseLogger, FastifyChildLoggerFactory, LogLevel } from './logger' import { FastifyReply, ReplyGenericInterface } from './reply' import { FastifyRequest, RequestGenericInterface } from './request' import { FastifySchema, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorFormatter } from './schema' @@ -46,6 +46,7 @@ export interface RouteShorthandOptions< constraints?: { [name: string]: any }, prefixTrailingSlash?: 'slash'|'no-slash'|'both'; errorHandler?: (this: FastifyInstance, error: FastifyError, request: FastifyRequest, reply: FastifyReply) => void; + childLoggerFactory?: FastifyChildLoggerFactory; schemaErrorFormatter?: SchemaErrorFormatter; // hooks From 59c5b273dad30821d03c952bbe073a976f92a325 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Wed, 12 Jul 2023 01:29:39 -0600 Subject: [PATCH 0386/1295] Improve setNotFoundHandler handler type (#4897) --- test/types/instance.test-d.ts | 4 ++++ types/instance.d.ts | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 80fe1e36339..7ff26287bc2 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -131,6 +131,10 @@ server.setNotFoundHandler({ preValidation: notFoundpreValidationHandler }, notFo server.setNotFoundHandler({ preValidation: notFoundpreValidationAsyncHandler }, notFoundAsyncHandler) server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler }, notFoundAsyncHandler) +server.setNotFoundHandler(function (_, reply) { + return reply.send('') +}) + function invalidErrorHandler (error: number) { if (error) throw error } diff --git a/types/instance.d.ts b/types/instance.d.ts index a387e902e3e..bac771fac3f 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -8,7 +8,7 @@ import { FastifyBaseLogger, FastifyChildLoggerFactory } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' import { FastifyRequest } from './request' -import { DefaultRoute, RouteGenericInterface, RouteOptions, RouteShorthandMethod } from './route' +import { DefaultRoute, RouteGenericInterface, RouteOptions, RouteShorthandMethod, RouteHandlerMethod } from './route' import { FastifySchema, FastifySchemaCompiler, @@ -526,10 +526,10 @@ export interface FastifyInstance< ): FastifyInstance; /** - * Set the 404 handler - */ - setNotFoundHandler ( - handler: (request: FastifyRequest, reply: FastifyReply) => void | Promise + * Set the 404 handler + */ + setNotFoundHandler ( + handler: RouteHandlerMethod ): FastifyInstance; setNotFoundHandler ( @@ -537,7 +537,7 @@ export interface FastifyInstance< preValidation?: preValidationHookHandler | preValidationHookHandler[]; preHandler?: preHandlerHookHandler | preHandlerHookHandler[]; }, - handler: (request: FastifyRequest, reply: FastifyReply) => void | Promise + handler: RouteHandlerMethod ): FastifyInstance /** From 7381195d930ca40e3413251fbb0cdaad2d7638c8 Mon Sep 17 00:00:00 2001 From: Philipp Viereck <105976309+philippviereck@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:16:56 +0200 Subject: [PATCH 0387/1295] Change request id header default value to false (#4194) * add option to disable/ignore request-id header fixes #4192 * add more restrictive schema definition * update configValidator generated by running 'node build/build-validation.js' after change on 'build-validation.js' * move logic into reqIdGenFactory * change default value from requestIdHeader Changes default from 'request-id' to `false`. Making it opt-in instead of opt-out. NOTE: when user sets requestIdHeader to `true` the behavior is the same as if it was set to `false` because `req.headers[true] || genReqId(req)` is equivalent to `genReqId(req)` (req.headers[true] is undefined). TL;DR requestIdHeader must be a string, else it will be 'ignored'. Solution: Provide a default value for requestIdHeader when `true` or throw. * remove comment * adapt --------- Co-authored-by: Uzlopak --- build/build-validation.js | 4 +- docs/Reference/Logging.md | 8 +- docs/Reference/Server.md | 10 +- fastify.d.ts | 2 +- fastify.js | 2 +- lib/configValidator.js | 147 +++++++++++++++------------ lib/reqIdGenFactory.js | 1 - test/genReqId.test.js | 9 ++ test/internals/initialConfig.test.js | 4 +- test/internals/logger.test.js | 31 +++++- 10 files changed, 139 insertions(+), 79 deletions(-) diff --git a/build/build-validation.js b/build/build-validation.js index 2ed914f69b1..a9c6a39a104 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -38,7 +38,7 @@ const defaultInitOptions = { onProtoPoisoning: 'error', onConstructorPoisoning: 'error', pluginTimeout: 10000, - requestIdHeader: 'request-id', + requestIdHeader: false, requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, // 72 seconds exposeHeadRoutes: true @@ -97,7 +97,7 @@ const schema = { onProtoPoisoning: { type: 'string', default: defaultInitOptions.onProtoPoisoning }, onConstructorPoisoning: { type: 'string', default: defaultInitOptions.onConstructorPoisoning }, pluginTimeout: { type: 'integer', default: defaultInitOptions.pluginTimeout }, - requestIdHeader: { anyOf: [{ enum: [false] }, { type: 'string' }], default: defaultInitOptions.requestIdHeader }, + requestIdHeader: { anyOf: [{ type: 'boolean' }, { type: 'string' }], default: defaultInitOptions.requestIdHeader }, requestIdLogLabel: { type: 'string', default: defaultInitOptions.requestIdLogLabel }, http2SessionTimeout: { type: 'integer', default: defaultInitOptions.http2SessionTimeout }, exposeHeadRoutes: { type: 'boolean', default: defaultInitOptions.exposeHeadRoutes }, diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 7a8fc285434..b0ef573ffd4 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -98,10 +98,10 @@ const fastify = require('fastify')({ By default, Fastify adds an ID to every request for easier tracking. If the -"request-id" header is present its value is used, otherwise a new incremental ID -is generated. See Fastify Factory -[`requestIdHeader`](./Server.md#factory-request-id-header) and Fastify Factory -[`genReqId`](./Server.md#genreqid) for customization options. +requestIdHeader-option is set and the corresponding header is present than +its value is used, otherwise a new incremental ID is generated. See Fastify +Factory [`requestIdHeader`](./Server.md#factory-request-id-header) and Fastify +Factory [`genReqId`](./Server.md#genreqid) for customization options. The default logger is configured with a set of standard serializers that serialize objects with `req`, `res`, and `err` properties. The object received diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index a67606ada0e..65da58d38d3 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -505,9 +505,15 @@ about safe regexp: [Safe-regex2](https://www.npmjs.com/package/safe-regex2) The header name used to set the request-id. See [the request-id](./Logging.md#logging-request-id) section. -Setting `requestIdHeader` to `false` will always use [genReqId](#genreqid) +Setting `requestIdHeader` to `true` will set the `requestIdHeader` to +`"request-id"`. +Setting `requestIdHeader` to a String other than empty string will set the +`requestIdHeader` to `false`. +By default `requestIdHeader` is set to `false` and will immediately use [genReqId](#genreqid). +Setting `requestIdHeader` to an empty String (`""`) will set the +requestIdHeader to `false`. -+ Default: `'request-id'` ++ Default: `false` ```js const fastify = require('fastify')({ diff --git a/fastify.d.ts b/fastify.d.ts index 750ebc3c09b..66e570ffa7c 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -106,7 +106,7 @@ declare namespace fastify { serializerOpts?: FJSOptions | Record, serverFactory?: FastifyServerFactory, caseSensitive?: boolean, - requestIdHeader?: string | false, + requestIdHeader?: string | boolean, requestIdLogLabel?: string; jsonShorthand?: boolean; genReqId?: (req: RawRequestDefaultExpression) => string, diff --git a/fastify.js b/fastify.js index 669b85416e5..a0b33d3ba26 100644 --- a/fastify.js +++ b/fastify.js @@ -113,7 +113,7 @@ function fastify (options) { validateBodyLimitOption(options.bodyLimit) - const requestIdHeader = (options.requestIdHeader === false) ? false : (options.requestIdHeader || defaultInitOptions.requestIdHeader) + const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader : (options.requestIdHeader === true && 'request-id') const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId) const requestIdLogLabel = options.requestIdLogLabel || 'reqId' const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit diff --git a/lib/configValidator.js b/lib/configValidator.js index f7ded6173c4..de842d1f852 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -3,7 +3,7 @@ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -58,7 +58,7 @@ if(data.pluginTimeout === undefined){ data.pluginTimeout = 10000; } if(data.requestIdHeader === undefined){ -data.requestIdHeader = "request-id"; +data.requestIdHeader = false; } if(data.requestIdLogLabel === undefined){ data.requestIdLogLabel = "reqId"; @@ -840,8 +840,17 @@ const _errs57 = errors; const _errs58 = errors; let valid6 = false; const _errs59 = errors; -if(!(data19 === false)){ -const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/enum",keyword:"enum",params:{allowedValues: schema11.properties.requestIdHeader.anyOf[0].enum},message:"must be equal to one of the allowed values"}; +if(typeof data19 !== "boolean"){ +let coerced21 = undefined; +if(!(coerced21 !== undefined)){ +if(data19 === "false" || data19 === 0 || data19 === null){ +coerced21 = false; +} +else if(data19 === "true" || data19 === 1){ +coerced21 = true; +} +else { +const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; if(vErrors === null){ vErrors = [err12]; } @@ -850,19 +859,27 @@ vErrors.push(err12); } errors++; } +} +if(coerced21 !== undefined){ +data19 = coerced21; +if(data !== undefined){ +data["requestIdHeader"] = coerced21; +} +} +} var _valid3 = _errs59 === errors; valid6 = valid6 || _valid3; if(!valid6){ -const _errs60 = errors; +const _errs61 = errors; if(typeof data19 !== "string"){ -let dataType21 = typeof data19; -let coerced21 = undefined; -if(!(coerced21 !== undefined)){ -if(dataType21 == "number" || dataType21 == "boolean"){ -coerced21 = "" + data19; +let dataType22 = typeof data19; +let coerced22 = undefined; +if(!(coerced22 !== undefined)){ +if(dataType22 == "number" || dataType22 == "boolean"){ +coerced22 = "" + data19; } else if(data19 === null){ -coerced21 = ""; +coerced22 = ""; } else { const err13 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"}; @@ -875,14 +892,14 @@ vErrors.push(err13); errors++; } } -if(coerced21 !== undefined){ -data19 = coerced21; +if(coerced22 !== undefined){ +data19 = coerced22; if(data !== undefined){ -data["requestIdHeader"] = coerced21; +data["requestIdHeader"] = coerced22; } } } -var _valid3 = _errs60 === errors; +var _valid3 = _errs61 === errors; valid6 = valid6 || _valid3; } if(!valid6){ @@ -911,84 +928,84 @@ vErrors = null; var valid0 = _errs57 === errors; if(valid0){ let data20 = data.requestIdLogLabel; -const _errs62 = errors; +const _errs63 = errors; if(typeof data20 !== "string"){ -let dataType22 = typeof data20; -let coerced22 = undefined; -if(!(coerced22 !== undefined)){ -if(dataType22 == "number" || dataType22 == "boolean"){ -coerced22 = "" + data20; +let dataType23 = typeof data20; +let coerced23 = undefined; +if(!(coerced23 !== undefined)){ +if(dataType23 == "number" || dataType23 == "boolean"){ +coerced23 = "" + data20; } else if(data20 === null){ -coerced22 = ""; +coerced23 = ""; } else { validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } -if(coerced22 !== undefined){ -data20 = coerced22; +if(coerced23 !== undefined){ +data20 = coerced23; if(data !== undefined){ -data["requestIdLogLabel"] = coerced22; +data["requestIdLogLabel"] = coerced23; } } } -var valid0 = _errs62 === errors; +var valid0 = _errs63 === errors; if(valid0){ let data21 = data.http2SessionTimeout; -const _errs64 = errors; +const _errs65 = errors; if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){ -let dataType23 = typeof data21; -let coerced23 = undefined; -if(!(coerced23 !== undefined)){ -if(dataType23 === "boolean" || data21 === null - || (dataType23 === "string" && data21 && data21 == +data21 && !(data21 % 1))){ -coerced23 = +data21; +let dataType24 = typeof data21; +let coerced24 = undefined; +if(!(coerced24 !== undefined)){ +if(dataType24 === "boolean" || data21 === null + || (dataType24 === "string" && data21 && data21 == +data21 && !(data21 % 1))){ +coerced24 = +data21; } else { validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } -if(coerced23 !== undefined){ -data21 = coerced23; +if(coerced24 !== undefined){ +data21 = coerced24; if(data !== undefined){ -data["http2SessionTimeout"] = coerced23; +data["http2SessionTimeout"] = coerced24; } } } -var valid0 = _errs64 === errors; +var valid0 = _errs65 === errors; if(valid0){ let data22 = data.exposeHeadRoutes; -const _errs66 = errors; +const _errs67 = errors; if(typeof data22 !== "boolean"){ -let coerced24 = undefined; -if(!(coerced24 !== undefined)){ +let coerced25 = undefined; +if(!(coerced25 !== undefined)){ if(data22 === "false" || data22 === 0 || data22 === null){ -coerced24 = false; +coerced25 = false; } else if(data22 === "true" || data22 === 1){ -coerced24 = true; +coerced25 = true; } else { validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } -if(coerced24 !== undefined){ -data22 = coerced24; +if(coerced25 !== undefined){ +data22 = coerced25; if(data !== undefined){ -data["exposeHeadRoutes"] = coerced24; +data["exposeHeadRoutes"] = coerced25; } } } -var valid0 = _errs66 === errors; +var valid0 = _errs67 === errors; if(valid0){ if(data.versioning !== undefined){ let data23 = data.versioning; -const _errs68 = errors; -if(errors === _errs68){ +const _errs69 = errors; +if(errors === _errs69){ if(data23 && typeof data23 == "object" && !Array.isArray(data23)){ let missing1; if(((data23.storage === undefined) && (missing1 = "storage")) || ((data23.deriveVersion === undefined) && (missing1 = "deriveVersion"))){ @@ -1001,7 +1018,7 @@ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/prop return false; } } -var valid0 = _errs68 === errors; +var valid0 = _errs69 === errors; } else { var valid0 = true; @@ -1009,13 +1026,13 @@ var valid0 = true; if(valid0){ if(data.constraints !== undefined){ let data24 = data.constraints; -const _errs71 = errors; -if(errors === _errs71){ +const _errs72 = errors; +if(errors === _errs72){ if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ for(const key2 in data24){ let data25 = data24[key2]; -const _errs74 = errors; -if(errors === _errs74){ +const _errs75 = errors; +if(errors === _errs75){ if(data25 && typeof data25 == "object" && !Array.isArray(data25)){ let missing2; if(((((data25.name === undefined) && (missing2 = "name")) || ((data25.storage === undefined) && (missing2 = "storage"))) || ((data25.validate === undefined) && (missing2 = "validate"))) || ((data25.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){ @@ -1026,24 +1043,24 @@ else { if(data25.name !== undefined){ let data26 = data25.name; if(typeof data26 !== "string"){ -let dataType25 = typeof data26; -let coerced25 = undefined; -if(!(coerced25 !== undefined)){ -if(dataType25 == "number" || dataType25 == "boolean"){ -coerced25 = "" + data26; +let dataType26 = typeof data26; +let coerced26 = undefined; +if(!(coerced26 !== undefined)){ +if(dataType26 == "number" || dataType26 == "boolean"){ +coerced26 = "" + data26; } else if(data26 === null){ -coerced25 = ""; +coerced26 = ""; } else { validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } -if(coerced25 !== undefined){ -data26 = coerced25; +if(coerced26 !== undefined){ +data26 = coerced26; if(data25 !== undefined){ -data25["name"] = coerced25; +data25["name"] = coerced26; } } } @@ -1055,7 +1072,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/ return false; } } -var valid7 = _errs74 === errors; +var valid7 = _errs75 === errors; if(!valid7){ break; } @@ -1066,7 +1083,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs71 === errors; +var valid0 = _errs72 === errors; } else { var valid0 = true; @@ -1106,4 +1123,4 @@ return errors === 0; } -module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true} +module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true} diff --git a/lib/reqIdGenFactory.js b/lib/reqIdGenFactory.js index c8c36d0bea1..eee8a3f0133 100644 --- a/lib/reqIdGenFactory.js +++ b/lib/reqIdGenFactory.js @@ -16,7 +16,6 @@ module.exports = function (requestIdHeader, optGenReqId) { const genReqId = optGenReqId || defaultGenReqId if (requestIdHeader) { - // requestIdHeader = typeof requestIdHeader === 'string' ? requestIdHeader : 'request-id' return function (req) { return req.headers[requestIdHeader] || genReqId(req) } diff --git a/test/genReqId.test.js b/test/genReqId.test.js index 7dce2b8ac87..cf5ced89908 100644 --- a/test/genReqId.test.js +++ b/test/genReqId.test.js @@ -72,3 +72,12 @@ test('Custom genReqId function gets raw request as argument', t => { }) }) }) + +test('Should handle properly requestIdHeader option', t => { + t.plan(4) + + t.equal(Fastify({ requestIdHeader: '' }).initialConfig.requestIdHeader, false) + t.equal(Fastify({ requestIdHeader: false }).initialConfig.requestIdHeader, false) + t.equal(Fastify({ requestIdHeader: true }).initialConfig.requestIdHeader, 'request-id') + t.equal(Fastify({ requestIdHeader: 'x-request-id' }).initialConfig.requestIdHeader, 'x-request-id') +}) diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index c90b91c552b..cafe78c753d 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -45,7 +45,7 @@ test('without options passed to Fastify, initialConfig should expose default val onProtoPoisoning: 'error', onConstructorPoisoning: 'error', pluginTimeout: 10000, - requestIdHeader: 'request-id', + requestIdHeader: false, requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, exposeHeadRoutes: true @@ -277,7 +277,7 @@ test('Should not have issues when passing stream options to Pino.js', t => { onProtoPoisoning: 'error', onConstructorPoisoning: 'error', pluginTimeout: 10000, - requestIdHeader: 'request-id', + requestIdHeader: false, requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, exposeHeadRoutes: true diff --git a/test/internals/logger.test.js b/test/internals/logger.test.js index 6f3246fdda0..3d858b6670b 100644 --- a/test/internals/logger.test.js +++ b/test/internals/logger.test.js @@ -46,13 +46,42 @@ test('The logger should add a unique id for every request', t => { } }) -test('The logger should reuse request id header for req.id', t => { +test('The logger should not reuse request id header for req.id', t => { const fastify = Fastify() fastify.get('/', (req, reply) => { t.ok(req.id) reply.send({ id: req.id }) }) + fastify.listen({ port: 0 }, err => { + t.error(err) + + fastify.inject({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port, + headers: { + 'Request-Id': 'request-id-1' + } + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.ok(payload.id !== 'request-id-1', 'the request id from the header should not be returned with default configuration') + t.ok(payload.id === 'req-1') // first request id when using the default configuration + fastify.close() + t.end() + }) + }) +}) + +test('The logger should reuse request id header for req.id if requestIdHeader is set', t => { + const fastify = Fastify({ + requestIdHeader: 'request-id' + }) + fastify.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + fastify.listen({ port: 0 }, err => { t.error(err) From 53164c16f640bfe936b61757bb15dbc29fee99c2 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 12 Jul 2023 18:43:29 +0200 Subject: [PATCH 0388/1295] chore(ci): restore code coverage (#4841) --- .c8rc.json | 8 +++ .github/workflows/ci.yml | 2 - .github/workflows/coverage-nix.yml | 2 - .github/workflows/coverage-win.yml | 2 - .taprc | 5 +- fastify.js | 4 +- lib/server.js | 7 +-- package.json | 11 ++-- test/500s.test.js | 4 ++ test/internals/reply.test.js | 92 ++++++++++++++++-------------- test/reply-error.test.js | 25 ++++++++ 11 files changed, 101 insertions(+), 61 deletions(-) create mode 100644 .c8rc.json diff --git a/.c8rc.json b/.c8rc.json new file mode 100644 index 00000000000..abfd9f3b0b0 --- /dev/null +++ b/.c8rc.json @@ -0,0 +1,8 @@ +{ + "exclude": [ + "lib/configValidator.js", + "lib/error-serializer.js", + "build/build-error-serializer.js", + "test/*" + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19e1cb9d214..06e39d9fac7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,8 +135,6 @@ jobs: - name: Run tests run: | npm run unit - env: - NODE_OPTIONS: no-network-family-autoselection test-typescript: needs: diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 867014481be..ba3bfb422a2 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -28,8 +28,6 @@ jobs: - name: Generate coverage report run: | npm run coverage:ci - env: - NODE_OPTIONS: no-network-family-autoselection - uses: actions/upload-artifact@v3 if: ${{ success() }} diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 42bdcefd971..776410d7946 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -28,8 +28,6 @@ jobs: - name: Generate coverage report run: | npm run coverage:ci - env: - NODE_OPTIONS: no-network-family-autoselection - uses: actions/upload-artifact@v3 if: ${{ success() }} diff --git a/.taprc b/.taprc index dd0fbcce796..3c1b6a03f8b 100644 --- a/.taprc +++ b/.taprc @@ -1,8 +1,9 @@ ts: false jsx: false flow: false -check-coverage: true -coverage: true +# the coverage is performed by c8 +check-coverage: false +coverage: false node-arg: --allow-natives-syntax files: diff --git a/fastify.js b/fastify.js index 7c81c2743ef..298f9d2021f 100644 --- a/fastify.js +++ b/fastify.js @@ -350,7 +350,8 @@ function fastify (options) { listeningOrigin: { get () { const address = this.addresses().slice(-1).pop() - /* istanbul ignore if windows: unix socket is not testable on Windows platform */ + /* ignore if windows: unix socket is not testable on Windows platform */ + /* c8 ignore next 3 */ if (typeof address === 'string') { return address } @@ -455,6 +456,7 @@ function fastify (options) { // https://github.com/nodejs/node/issues/48604 if (!options.serverFactory || fastify[kState].listening) { instance.server.close(function (err) { + /* c8 ignore next 6 */ if (err && err.code !== 'ERR_SERVER_NOT_RUNNING') { done(null) } else { diff --git a/lib/server.js b/lib/server.js index 384ac4de6c0..855de3f4ea8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -22,8 +22,6 @@ function defaultResolveServerListeningText (address) { function createServer (options, httpHandler) { const server = getServerInstance(options, httpHandler) - return { server, listen } - // `this` is the Fastify object function listen (listenOptions, ...args) { let cb = args.slice(-1).pop() @@ -99,6 +97,8 @@ function createServer (options, httpHandler) { this.ready(listenCallback.call(this, server, listenOptions)) } + + return { server, listen } } function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, onListen) { @@ -138,17 +138,16 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o // pace through the `onClose` hook. // It also allows them to handle possible connections already // attached to them if any. + /* c8 ignore next 20 */ this.onClose((instance, done) => { if (instance[kState].listening) { // No new TCP connections are accepted // We swallow any error from the secondary // server secondaryServer.close(() => done()) - /* istanbul ignore next: Cannot test this without Node.js core support */ if (serverOpts.forceCloseConnections === 'idle') { // Not needed in Node 19 secondaryServer.closeIdleConnections() - /* istanbul ignore next: Cannot test this without Node.js core support */ } else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) { secondaryServer.closeAllConnections() } diff --git a/package.json b/package.json index ed2c97b5eae..d291b4db428 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"", "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", - "coverage": "cross-env NODE_OPTIONS=no-network-family-autoselection npm run unit -- --cov --coverage-report=html", - "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage", - "coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100", + "coverage": "npm run unit -- --coverage-report=html", + "coverage:ci": "npm run unit -- --coverage-report=html --no-browser --no-check-coverage", + "coverage:ci-check-coverage": "c8 check-coverage --branches 100 --functions 100 --lines 100 --statements 100", "license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"", "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown", "lint:fix": "standard --fix", @@ -20,13 +20,13 @@ "lint:standard": "standard | snazzy", "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", "prepublishOnly": "cross-env PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", - "test": "npm run lint && cross-env NODE_OPTIONS=no-network-family-autoselection npm run unit && npm run test:typescript", + "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", "test:typescript": "tsc test/types/import.ts && tsd", "test:watch": "npm run unit -- -w --no-coverage-report -R terse", - "unit": "tap", + "unit": "c8 tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap" }, @@ -145,6 +145,7 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "branch-comparer": "^1.1.0", + "c8": "^8.0.0", "cross-env": "^7.0.3", "eslint": "^8.39.0", "eslint-config-standard": "^17.0.0", diff --git a/test/500s.test.js b/test/500s.test.js index b7b4eac9bc4..3422fcfec4c 100644 --- a/test/500s.test.js +++ b/test/500s.test.js @@ -9,6 +9,7 @@ test('default 500', t => { t.plan(4) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', function (req, reply) { reply.send(new Error('kaboom')) @@ -33,6 +34,7 @@ test('custom 500', t => { t.plan(6) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', function (req, reply) { reply.send(new Error('kaboom')) @@ -62,6 +64,7 @@ test('encapsulated 500', t => { t.plan(10) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', function (req, reply) { reply.send(new Error('kaboom')) @@ -113,6 +116,7 @@ test('custom 500 with hooks', t => { t.plan(7) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', function (req, reply) { reply.send(new Error('kaboom')) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index f80f2146f7b..4d6ca401d6b 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -6,6 +6,7 @@ const sget = require('simple-get').concat const http = require('http') const NotFound = require('http-errors').NotFound const Reply = require('../../lib/reply') +const Fastify = require('../..') const { Readable, Writable } = require('stream') const { kReplyErrorHandlerCalled, @@ -19,9 +20,11 @@ const fs = require('fs') const path = require('path') const warning = require('../../lib/warnings') +const agent = new http.Agent({ keepAlive: false }) + const doGet = function (url) { return new Promise((resolve, reject) => { - sget({ method: 'GET', url, followRedirects: false }, (err, response, body) => { + sget({ method: 'GET', url, followRedirects: false, agent }, (err, response, body) => { if (err) { reject(err) } else { @@ -128,7 +131,8 @@ test('reply.serializer should set a custom serializer', t => { test('reply.serializer should support running preSerialization hooks', t => { t.plan(3) - const fastify = require('../..')() + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.addHook('preSerialization', async (request, reply, payload) => { t.ok('called', 'preSerialization') }) fastify.route({ @@ -182,7 +186,8 @@ test('reply.serialize should serialize payload with a context default serializer test('reply.serialize should serialize payload with Fastify instance', t => { t.plan(2) - const fastify = require('../..')() + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.route({ method: 'GET', url: '/', @@ -213,7 +218,8 @@ test('reply.serialize should serialize payload with Fastify instance', t => { }) test('within an instance', t => { - const fastify = require('../..')() + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const test = t.test fastify.get('/', function (req, reply) { @@ -434,7 +440,7 @@ test('within an instance', t => { test('buffer without content type should send a application/octet-stream and raw buffer', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send(Buffer.alloc(1024)) @@ -457,7 +463,7 @@ test('buffer without content type should send a application/octet-stream and raw test('Uint8Array without content type should send a application/octet-stream and raw buffer', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send(new Uint8Array(1024).fill(0xff)) @@ -480,7 +486,7 @@ test('Uint8Array without content type should send a application/octet-stream and test('Uint16Array without content type should send a application/octet-stream and raw buffer', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send(new Uint16Array(50).fill(0xffffffff)) @@ -503,7 +509,7 @@ test('Uint16Array without content type should send a application/octet-stream an test('TypedArray with content type should not send application/octet-stream', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.header('Content-Type', 'text/plain') @@ -527,7 +533,7 @@ test('TypedArray with content type should not send application/octet-stream', t test('buffer with content type should not send application/octet-stream', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.header('Content-Type', 'text/plain') @@ -552,7 +558,7 @@ test('buffer with content type should not send application/octet-stream', t => { test('stream with content type should not send application/octet-stream', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() const streamPath = path.join(__dirname, '..', '..', 'package.json') const stream = fs.createReadStream(streamPath) @@ -579,7 +585,7 @@ test('stream with content type should not send application/octet-stream', t => { test('stream without content type should not send application/octet-stream', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() const stream = fs.createReadStream(__filename) const buf = fs.readFileSync(__filename) @@ -605,7 +611,7 @@ test('stream without content type should not send application/octet-stream', t = test('stream using reply.raw.writeHead should return customize headers', t => { t.plan(6) - const fastify = require('../..')() + const fastify = Fastify() const fs = require('fs') const path = require('path') @@ -641,7 +647,7 @@ test('stream using reply.raw.writeHead should return customize headers', t => { test('plain string without content type should send a text/plain', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send('hello world!') @@ -665,7 +671,7 @@ test('plain string without content type should send a text/plain', t => { test('plain string with content type should be sent unmodified', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.type('text/css').send('hello world!') @@ -689,7 +695,7 @@ test('plain string with content type should be sent unmodified', t => { test('plain string with content type and custom serializer should be serialized', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply @@ -716,7 +722,7 @@ test('plain string with content type and custom serializer should be serialized' test('plain string with content type application/json should NOT be serialized as json', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.type('application/json').send('{"key": "hello world!"}') @@ -740,7 +746,7 @@ test('plain string with content type application/json should NOT be serialized a test('plain string with custom json content type should NOT be serialized as json', t => { t.plan(19) - const fastify = require('../..')() + const fastify = Fastify() const customSamples = { collectionjson: { @@ -795,7 +801,7 @@ test('plain string with custom json content type should NOT be serialized as jso test('non-string with content type application/json SHOULD be serialized as json', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.type('application/json').send({ key: 'hello world!' }) @@ -819,7 +825,7 @@ test('non-string with content type application/json SHOULD be serialized as json test('non-string with custom json\'s content-type SHOULD be serialized as json', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.type('application/json; version=2; ').send({ key: 'hello world!' }) @@ -843,7 +849,7 @@ test('non-string with custom json\'s content-type SHOULD be serialized as json', test('non-string with custom json content type SHOULD be serialized as json', t => { t.plan(16) - const fastify = require('../..')() + const fastify = Fastify() const customSamples = { collectionjson: { @@ -894,7 +900,7 @@ test('non-string with custom json content type SHOULD be serialized as json', t test('error object with a content type that is not application/json should work', t => { t.plan(6) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/text', function (req, reply) { reply.type('text/plain') @@ -928,7 +934,7 @@ test('error object with a content type that is not application/json should work' test('undefined payload should be sent as-is', t => { t.plan(6) - const fastify = require('../..')() + const fastify = Fastify() fastify.addHook('onSend', function (request, reply, payload, done) { t.equal(payload, undefined) @@ -958,7 +964,7 @@ test('undefined payload should be sent as-is', t => { test('for HEAD method, no body should be sent but content-length should be', t => { t.plan(11) - const fastify = require('../..')() + const fastify = Fastify() const contentType = 'application/json; charset=utf-8' const bodySize = JSON.stringify({ foo: 'bar' }).length @@ -1013,7 +1019,7 @@ test('for HEAD method, no body should be sent but content-length should be', t = test('reply.send(new NotFound()) should not invoke the 404 handler', t => { t.plan(9) - const fastify = require('../..')() + const fastify = Fastify() fastify.setNotFoundHandler((req, reply) => { t.fail('Should not be called') @@ -1389,7 +1395,7 @@ test('Content type and charset set previously', t => { test('.status() is an alias for .code()', t => { t.plan(2) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.status(418).send() @@ -1403,7 +1409,7 @@ test('.status() is an alias for .code()', t => { test('.statusCode is getter and setter', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { t.ok(reply.statusCode, 200, 'default status value') @@ -1455,7 +1461,7 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t = test('should emit deprecation warning when trying to modify the reply.sent property', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() const deprecationCode = 'FSTDEP010' warning.emitted.delete(deprecationCode) @@ -1483,7 +1489,7 @@ test('should emit deprecation warning when trying to modify the reply.sent prope test('should throw error when passing falsy value to reply.sent', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { try { @@ -1503,7 +1509,7 @@ test('should throw error when passing falsy value to reply.sent', t => { test('should throw error when attempting to set reply.sent more than once', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.sent = true @@ -1525,7 +1531,7 @@ test('should throw error when attempting to set reply.sent more than once', t => test('should not throw error when attempting to set reply.sent if the underlining request was sent', t => { t.plan(3) - const fastify = require('../..')() + const fastify = Fastify() fastify.get('/', function (req, reply) { reply.raw.end() @@ -1549,7 +1555,7 @@ test('reply.getResponseTime() should return 0 before the timer is initialised on test('reply.getResponseTime() should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', t => { t.plan(1) - const fastify = require('../..')() + const fastify = Fastify() fastify.route({ method: 'GET', url: '/', @@ -1568,7 +1574,7 @@ test('reply.getResponseTime() should return a number greater than 0 after the ti test('reply.getResponseTime() should return the time since a request started while inflight', t => { t.plan(1) - const fastify = require('../..')() + const fastify = Fastify() fastify.route({ method: 'GET', url: '/', @@ -1591,7 +1597,7 @@ test('reply.getResponseTime() should return the time since a request started whi test('reply.getResponseTime() should return the same value after a request is finished', t => { t.plan(1) - const fastify = require('../..')() + const fastify = Fastify() fastify.route({ method: 'GET', url: '/', @@ -1610,7 +1616,7 @@ test('reply.getResponseTime() should return the same value after a request is fi test('reply should use the custom serializer', t => { t.plan(4) - const fastify = require('../..')() + const fastify = Fastify() fastify.setReplySerializer((payload, statusCode) => { t.same(payload, { foo: 'bar' }) t.equal(statusCode, 200) @@ -1638,7 +1644,7 @@ test('reply should use the custom serializer', t => { test('reply should use the right serializer in encapsulated context', t => { t.plan(9) - const fastify = require('../..')() + const fastify = Fastify() fastify.setReplySerializer((payload) => { t.same(payload, { foo: 'bar' }) payload.foo = 'bar bar' @@ -1707,7 +1713,7 @@ test('reply should use the right serializer in encapsulated context', t => { test('reply should use the right serializer in deep encapsulated context', t => { t.plan(8) - const fastify = require('../..')() + const fastify = Fastify() fastify.route({ method: 'GET', @@ -1771,7 +1777,7 @@ test('reply should use the right serializer in deep encapsulated context', t => test('reply should use the route serializer', t => { t.plan(3) - const fastify = require('../..')() + const fastify = Fastify() fastify.setReplySerializer(() => { t.fail('this serializer should not be executed') }) @@ -1802,7 +1808,7 @@ test('reply should use the route serializer', t => { test('cannot set the replySerializer when the server is running', t => { t.plan(2) - const fastify = require('../..')() + const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) fastify.listen({ port: 0 }, err => { @@ -1819,7 +1825,7 @@ test('cannot set the replySerializer when the server is running', t => { test('reply should not call the custom serializer for errors and not found', t => { t.plan(9) - const fastify = require('../..')() + const fastify = Fastify() fastify.setReplySerializer((payload, statusCode) => { t.same(payload, { foo: 'bar' }) t.equal(statusCode, 200) @@ -1935,7 +1941,7 @@ test('reply.sent should read from response.writableEnded if it is defined', t => }) test('redirect to an invalid URL should not crash the server', async t => { - const fastify = require('../..')() + const fastify = Fastify() fastify.route({ method: 'GET', url: '/redirect', @@ -1988,7 +1994,7 @@ test('redirect to an invalid URL should not crash the server', async t => { }) test('invalid response headers should not crash the server', async t => { - const fastify = require('../..')() + const fastify = Fastify() fastify.route({ method: 'GET', url: '/bad-headers', @@ -2021,7 +2027,7 @@ test('invalid response headers should not crash the server', async t => { }) test('invalid response headers when sending back an error', async t => { - const fastify = require('../..')() + const fastify = Fastify() fastify.route({ method: 'GET', url: '/bad-headers', @@ -2050,7 +2056,7 @@ test('invalid response headers when sending back an error', async t => { }) test('invalid response headers and custom error handler', async t => { - const fastify = require('../..')() + const fastify = Fastify() fastify.route({ method: 'GET', url: '/bad-headers', diff --git a/test/reply-error.test.js b/test/reply-error.test.js index 65b10446548..7afb49e77f1 100644 --- a/test/reply-error.test.js +++ b/test/reply-error.test.js @@ -18,6 +18,7 @@ function helper (code) { test('Reply error handling - code: ' + code, t => { t.plan(4) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const err = new Error('winter is coming') fastify.get('/', (req, reply) => { @@ -48,6 +49,7 @@ function helper (code) { test('preHandler hook error handling with external code', t => { t.plan(3) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const err = new Error('winter is coming') fastify.addHook('preHandler', (req, reply, done) => { @@ -77,6 +79,7 @@ test('preHandler hook error handling with external code', t => { test('onRequest hook error handling with external done', t => { t.plan(3) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const err = new Error('winter is coming') fastify.addHook('onRequest', (req, reply, done) => { @@ -182,6 +185,7 @@ test('Should set the response from client error handler', t => { test('Error instance sets HTTP status code', t => { t.plan(3) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const err = new Error('winter is coming') err.statusCode = 418 @@ -209,6 +213,7 @@ test('Error instance sets HTTP status code', t => { test('Error status code below 400 defaults to 500', t => { t.plan(3) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const err = new Error('winter is coming') err.statusCode = 399 @@ -236,6 +241,7 @@ test('Error status code below 400 defaults to 500', t => { test('Error.status property support', t => { t.plan(3) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const err = new Error('winter is coming') err.status = 418 @@ -279,6 +285,7 @@ test('Support rejection with values that are not Error instances', t => { t.test('Type: ' + typeof nonErr, t => { t.plan(4) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', () => { return Promise.reject(nonErr) @@ -309,6 +316,7 @@ test('invalid schema - ajv', t => { t.plan(4) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', { schema: { querystring: { @@ -340,6 +348,7 @@ test('invalid schema - ajv', t => { test('should set the status code and the headers from the error object (from route handler) (no custom error handler)', t => { t.plan(4) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (req, reply) => { const error = new Error('kaboom') @@ -366,6 +375,7 @@ test('should set the status code and the headers from the error object (from rou test('should set the status code and the headers from the error object (from custom error handler)', t => { t.plan(6) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (req, reply) => { const error = new Error('ouch') @@ -401,6 +411,7 @@ test('should set the status code and the headers from the error object (from cus test('\'*\' should throw an error due to serializer can not handle the payload type', t => { t.plan(3) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (req, reply) => { reply.type('text/html') @@ -424,6 +435,7 @@ test('\'*\' should throw an error due to serializer can not handle the payload t test('should throw an error if the custom serializer does not serialize the payload to a valid type', t => { t.plan(3) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (req, reply) => { try { @@ -450,6 +462,7 @@ test('should not set headers or status code for custom error handler', t => { t.plan(7) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', function (req, reply) { const err = new Error('kaboom') err.headers = { @@ -480,6 +493,7 @@ test('error thrown by custom error handler routes to default error handler', t = t.plan(6) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const error = new Error('kaboom') error.headers = { @@ -518,6 +532,7 @@ test('error thrown by custom error handler routes to default error handler', t = test('allow re-thrown error to default error handler when route handler is async and error handler is sync', t => { t.plan(4) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.setErrorHandler(function (error) { t.equal(error.message, 'kaboom') @@ -560,6 +575,7 @@ invalidErrorCodes.forEach((invalidCode) => { test(`should throw error if error code is ${invalidCode}`, t => { t.plan(2) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (request, reply) => { try { return reply.code(invalidCode).send('You should not read this') @@ -581,6 +597,7 @@ test('error handler is triggered when a string is thrown from sync handler', t = t.plan(3) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const throwable = 'test' const payload = 'error' @@ -608,6 +625,7 @@ test('error handler is triggered when a string is thrown from sync handler', t = test('status code should be set to 500 and return an error json payload if route handler throws any non Error object expression', async t => { t.plan(2) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', () => { /* eslint-disable-next-line */ @@ -623,6 +641,7 @@ test('status code should be set to 500 and return an error json payload if route test('should preserve the status code set by the user if an expression is thrown in a sync route', async t => { t.plan(2) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (_, rep) => { rep.status(501) @@ -641,6 +660,7 @@ test('should trigger error handlers if a sync route throws any non-error object' t.plan(2) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) const throwable = 'test' const payload = 'error' @@ -663,6 +683,7 @@ test('should trigger error handlers if a sync route throws undefined', async t = t.plan(1) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', function async (req, reply) { // eslint-disable-next-line no-throw-literal @@ -676,6 +697,7 @@ test('should trigger error handlers if a sync route throws undefined', async t = test('setting content-type on reply object should not hang the server case 1', t => { t.plan(2) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (req, reply) => { reply @@ -696,6 +718,7 @@ test('setting content-type on reply object should not hang the server case 1', t test('setting content-type on reply object should not hang the server case 2', async t => { t.plan(1) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (req, reply) => { reply @@ -727,6 +750,7 @@ test('setting content-type on reply object should not hang the server case 2', a test('setting content-type on reply object should not hang the server case 3', t => { t.plan(2) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.get('/', (req, reply) => { reply @@ -750,6 +774,7 @@ test('pipe stream inside error handler should not cause error', t => { const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8')) const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) fastify.setErrorHandler((_error, _request, reply) => { const stream = fs.createReadStream(location) From 1c50da371c1ede2bd344cf5b52bbd9f3ebdd12a0 Mon Sep 17 00:00:00 2001 From: shayff <39059628+shayff@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:53:38 +0300 Subject: [PATCH 0389/1295] fix: handle abort signal before server is ready (#4886) Co-authored-by: Carlos Fuentes Co-authored-by: shaypepper Co-authored-by: Uzlopak --- docs/Reference/Errors.md | 5 ++++ lib/errors.js | 6 ++++ lib/server.js | 24 +++++++++++++--- test/internals/errors.test.js | 20 +++++++++---- test/server.test.js | 54 +++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 9 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 8ce6f156207..6648d825521 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -521,3 +521,8 @@ The decorator is not present in the instance. The Request failed the payload validation. + +#### FST_ERR_LISTEN_OPTIONS_INVALID + + +Invalid listen options. diff --git a/lib/errors.js b/lib/errors.js index 3a4df61d410..1f44188521a 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -58,6 +58,12 @@ const codes = { '%s', 400 ), + FST_ERR_LISTEN_OPTIONS_INVALID: createError( + 'FST_ERR_LISTEN_OPTIONS_INVALID', + "Invalid listen options: '%s'", + 500, + TypeError + ), /** * ContentTypeParser diff --git a/lib/server.js b/lib/server.js index 855de3f4ea8..57dda785d65 100644 --- a/lib/server.js +++ b/lib/server.js @@ -9,7 +9,8 @@ const { kState, kOptions, kServerBindings } = require('./symbols') const { FST_ERR_HTTP2_INVALID_VERSION, FST_ERR_REOPENED_CLOSE_SERVER, - FST_ERR_REOPENED_SERVER + FST_ERR_REOPENED_SERVER, + FST_ERR_LISTEN_OPTIONS_INVALID } = require('./errors') module.exports.createServer = createServer @@ -46,6 +47,20 @@ function createServer (options, httpHandler) { } else { listenOptions.cb = cb } + if (listenOptions.signal) { + if (typeof listenOptions.signal.on !== 'function' && typeof listenOptions.signal.addEventListener !== 'function') { + throw new FST_ERR_LISTEN_OPTIONS_INVALID('Invalid options.signal') + } + + if (listenOptions.signal.aborted) { + this.close() + } else { + const onAborted = () => { + this.close() + } + listenOptions.signal.addEventListener('abort', onAborted, { once: true }) + } + } // If we have a path specified, don't default host to 'localhost' so we don't end up listening // on both path and host @@ -215,9 +230,10 @@ function listenCallback (server, listenOptions) { } server.once('error', wrap) - server.listen(listenOptions, wrap) - - this[kState].listening = true + if (!this[kState].closing) { + server.listen(listenOptions, wrap) + this[kState].listening = true + } } } diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index fd0b7b40f8e..d158b1ad34b 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('fs') const { resolve } = require('path') -test('should expose 77 errors', t => { +test('should expose 78 errors', t => { t.plan(1) const exportedKeys = Object.keys(errors) let counter = 0 @@ -14,11 +14,11 @@ test('should expose 77 errors', t => { counter++ } } - t.equal(counter, 77) + t.equal(counter, 78) }) test('ensure name and codes of Errors are identical', t => { - t.plan(77) + t.plan(78) const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -807,8 +807,18 @@ test('FST_ERR_VALIDATION', t => { t.ok(error instanceof Error) }) +test('FST_ERR_LISTEN_OPTIONS_INVALID', t => { + t.plan(5) + const error = new errors.FST_ERR_LISTEN_OPTIONS_INVALID() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_LISTEN_OPTIONS_INVALID') + t.equal(error.message, "Invalid listen options: '%s'") + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + test('Ensure that all errors are in Errors.md documented', t => { - t.plan(77) + t.plan(78) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -820,7 +830,7 @@ test('Ensure that all errors are in Errors.md documented', t => { }) test('Ensure that non-existing errors are not in Errors.md documented', t => { - t.plan(77) + t.plan(78) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /#### ([0-9a-zA-Z_]+)\n/g diff --git a/test/server.test.js b/test/server.test.js index f5f5fb77021..f662fc8443f 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -3,6 +3,7 @@ const t = require('tap') const test = t.test const Fastify = require('..') +const semver = require('semver') test('listen should accept null port', t => { t.plan(1) @@ -69,3 +70,56 @@ test('listen should reject string port', async (t) => { t.equal(error.code, 'ERR_SOCKET_BAD_PORT') } }) + +test('abort signal', { skip: semver.lt(process.version, '16.0.0') }, t => { + t.test('listen should not start server', t => { + t.plan(2) + function onClose (instance, done) { + t.type(fastify, instance) + done() + } + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + fastify.listen({ port: 1234, signal: controller.signal }, (err) => { + t.error(err) + }) + controller.abort() + t.equal(fastify.server.listening, false) + }) + + t.test('listen should not start server if already aborted', t => { + t.plan(2) + function onClose (instance, done) { + t.type(fastify, instance) + done() + } + + const controller = new AbortController() + controller.abort() + const fastify = Fastify() + fastify.addHook('onClose', onClose) + fastify.listen({ port: 1234, signal: controller.signal }, (err) => { + t.error(err) + }) + t.equal(fastify.server.listening, false) + }) + + t.test('listen should throw if received invalid signal', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.listen({ port: 1234, signal: {} }, (err) => { + t.error(err) + }) + t.fail() + } catch (e) { + t.equal(e.code, 'FST_ERR_LISTEN_OPTIONS_INVALID') + t.equal(e.message, 'Invalid listen options: \'Invalid options.signal\'') + } + }) + + t.end() +}) From c53b75b78faed4c49c373b01c2130430926c0ac6 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Fri, 14 Jul 2023 03:41:15 -0400 Subject: [PATCH 0390/1295] Remove deprecated variadic listen (#4900) --- .github/workflows/ci.yml | 8 +- lib/server.js | 60 +-------- lib/warnings.js | 2 - test/listen.deprecated.test.js | 235 --------------------------------- 4 files changed, 7 insertions(+), 298 deletions(-) delete mode 100644 test/listen.deprecated.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21cbae700b4..ab133fc6ffa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,12 +77,8 @@ jobs: strategy: matrix: - node-version: [14, 16, 18, 20] + node-version: [16, 18, 20] os: [macos-latest, ubuntu-latest, windows-latest] - exclude: - # excludes node 14 on Windows - - os: windows-latest - node-version: 14 steps: - uses: actions/checkout@v3 @@ -114,7 +110,7 @@ jobs: run: | npm run test:ci env: - NODE_OPTIONS: no-network-family-autoselection + NODE_OPTIONS: no-network-family-autoselection automerge: if: > diff --git a/lib/server.js b/lib/server.js index 384ac4de6c0..7cad57b66ff 100644 --- a/lib/server.js +++ b/lib/server.js @@ -4,7 +4,6 @@ const http = require('http') const https = require('https') const dns = require('dns') -const warnings = require('./warnings') const { kState, kOptions, kServerBindings } = require('./symbols') const { FST_ERR_HTTP2_INVALID_VERSION, @@ -25,27 +24,11 @@ function createServer (options, httpHandler) { return { server, listen } // `this` is the Fastify object - function listen (listenOptions, ...args) { - let cb = args.slice(-1).pop() - // When the variadic signature deprecation is complete, the function - // declaration should become: - // function listen (listenOptions = { port: 0, host: 'localhost' }, cb = undefined) - // Upon doing so, the `normalizeListenArgs` function is no longer needed, - // and all of this preamble to feed it correctly also no longer needed. - const firstArgType = Object.prototype.toString.call(arguments[0]) - if (arguments.length === 0) { - listenOptions = normalizeListenArgs([]) - } else if (arguments.length > 0 && (firstArgType !== '[object Object]' && firstArgType !== '[object Function]')) { - warnings.emit('FSTDEP011') - listenOptions = normalizeListenArgs(Array.from(arguments)) - cb = listenOptions.cb - } else if (args.length > 1) { - // `.listen(obj, a, ..., n, callback )` - warnings.emit('FSTDEP011') - // Deal with `.listen(port, host, backlog, [cb])` - const hostPath = listenOptions.path ? [listenOptions.path] : [listenOptions.port ?? 0, listenOptions.host ?? 'localhost'] - Object.assign(listenOptions, normalizeListenArgs([...hostPath, ...args])) - } else { + function listen ( + listenOptions = { port: 0, host: 'localhost' }, + cb = undefined + ) { + if (typeof cb === 'function') { listenOptions.cb = cb } @@ -341,39 +324,6 @@ function getServerInstance (options, httpHandler) { return server } -function normalizeListenArgs (args) { - if (args.length === 0) { - return { port: 0, host: 'localhost' } - } - - const cb = typeof args[args.length - 1] === 'function' ? args.pop() : undefined - const options = { cb } - - const firstArg = args[0] - const argsLength = args.length - const lastArg = args[argsLength - 1] - if (typeof firstArg === 'string' && isNaN(firstArg)) { - /* Deal with listen (pipe[, backlog]) */ - options.path = firstArg - options.backlog = argsLength > 1 ? lastArg : undefined - } else { - /* Deal with listen ([port[, host[, backlog]]]) */ - options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : normalizePort(firstArg) - // This will listen to what localhost is. - // It can be 127.0.0.1 or ::1, depending on the operating system. - // Fixes https://github.com/fastify/fastify/issues/1022. - options.host = argsLength >= 2 && args[1] ? args[1] : 'localhost' - options.backlog = argsLength >= 3 ? args[2] : undefined - } - - return options -} - -function normalizePort (firstArg) { - const port = Number(firstArg) - return port >= 0 && !Number.isNaN(port) && Number.isInteger(port) ? port : 0 -} - function logServerAddress (server, listenTextResolver) { let address = server.address() const isUnixSocket = typeof address === 'string' diff --git a/lib/warnings.js b/lib/warnings.js index eb9e653a1cb..adb24293b6f 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -19,8 +19,6 @@ warning.create('FastifyDeprecation', 'FSTDEP009', 'You are using a custom route warning.create('FastifyDeprecation', 'FSTDEP010', 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.') -warning.create('FastifyDeprecation', 'FSTDEP011', 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.') - warning.create('FastifyDeprecation', 'FSTDEP012', 'Request#context property access is deprecated. Please use "Request#routeConfig" or "Request#routeSchema" instead for accessing Route settings. The "Request#context" will be removed in `fastify@5`.') 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`.') diff --git a/test/listen.deprecated.test.js b/test/listen.deprecated.test.js deleted file mode 100644 index 01f6cc7aa94..00000000000 --- a/test/listen.deprecated.test.js +++ /dev/null @@ -1,235 +0,0 @@ -'use strict' - -// Tests for deprecated `.listen` signature. This file should be -// removed when the deprecation is complete. - -const { test, before } = require('tap') -const dns = require('dns').promises -const Fastify = require('..') - -let localhost -let localhostForURL - -process.removeAllListeners('warning') - -before(async function (t) { - const lookup = await dns.lookup('localhost') - localhost = lookup.address - if (lookup.family === 6) { - localhostForURL = `[${lookup.address}]` - } else { - localhostForURL = localhost - } -}) - -test('listen accepts a port and a callback', t => { - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(0, (err) => { - t.equal(fastify.server.address().address, localhost) - t.error(err) - }) -}) - -test('listen accepts a port and a callback with (err, address)', t => { - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(0, (err, address) => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - t.error(err) - }) -}) - -test('listen accepts a port, address, and callback', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(0, localhost, (err) => { - t.error(err) - }) -}) - -test('listen accepts options, backlog and a callback', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ - port: 0, - host: 'localhost' - }, 511, (err) => { - t.error(err) - }) -}) - -test('listen accepts options (no port), backlog and a callback', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ - host: 'localhost' - }, 511, (err) => { - t.error(err) - }) -}) - -test('listen accepts options (no host), backlog and a callback', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ - port: 0 - }, 511, (err) => { - t.error(err) - }) -}) - -test('listen accepts options (no port, no host), backlog and a callback', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ - ipv6Only: false - }, 511, (err) => { - t.error(err) - }) -}) - -test('listen accepts a port, address and a callback with (err, address)', t => { - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(0, localhost, (err, address) => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - t.error(err) - }) -}) - -test('listen accepts a port, address, backlog and callback', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(0, localhost, 511, (err) => { - t.error(err) - }) -}) - -test('listen accepts a port, address, backlog and callback with (err, address)', t => { - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(0, localhost, 511, (err, address) => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - t.error(err) - }) -}) - -test('listen without callback (port zero)', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(0) - .then(() => { - t.equal(fastify.server.address().address, localhost) - }) -}) - -test('listen without callback (port not given)', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen() - .then(() => { - t.equal(fastify.server.address().address, localhost) - }) -}) - -test('listen null without callback with (address)', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(null) - .then(address => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - }) -}) - -test('listen without port without callback with (address)', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen() - .then(address => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - }) -}) - -test('listen with undefined without callback with (address)', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen(undefined) - .then(address => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - }) -}) - -test('listen when firstArg is string(pipe) and without backlog', async t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - const address = await fastify.listen('\\\\.\\pipe\\testPipe') - t.equal(address, '\\\\.\\pipe\\testPipe') -}) - -test('listen when firstArg is string(pipe) and with backlog', async t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - const address = await fastify.listen('\\\\.\\pipe\\testPipe2', 511) - t.equal(address, '\\\\.\\pipe\\testPipe2') -}) - -test('listen when firstArg is { path: string(pipe) } and with backlog and callback', t => { - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ path: '\\\\.\\pipe\\testPipe3' }, 511, (err, address) => { - t.error(err) - t.equal(address, '\\\\.\\pipe\\testPipe3') - }) -}) - -test('listen accepts a port as string, and callback', t => { - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - const port = 3000 - fastify.listen(port.toString(), localhost, (err) => { - t.equal(fastify.server.address().port, port) - t.error(err) - }) -}) - -test('listen accepts a port as string, address and callback', t => { - t.plan(3) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - const port = 3000 - fastify.listen(port.toString(), localhost, (err) => { - t.equal(fastify.server.address().port, port) - t.equal(fastify.server.address().address, localhost) - t.error(err) - }) -}) - -test('listen with invalid port string without callback with (address)', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen('-1') - .then(address => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - }) -}) From cc347d7c0b4266097b61b126158b797878668353 Mon Sep 17 00:00:00 2001 From: Yevhen Badorov Date: Fri, 14 Jul 2023 11:10:25 +0300 Subject: [PATCH 0391/1295] docs(client-aborted): remove deprecated function (#4898) [request.aborted](https://nodejs.org/docs/latest-v16.x/api/http.html#requestaborted) has been deprecated since node 16. It seems like [request.destroyed](https://nodejs.org/docs/latest-v16.x/api/http.html#requestdestroyed) should be used instead --- docs/Guides/Detecting-When-Clients-Abort.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md index 1bdedec3a99..9d4861a2627 100644 --- a/docs/Guides/Detecting-When-Clients-Abort.md +++ b/docs/Guides/Detecting-When-Clients-Abort.md @@ -55,7 +55,7 @@ const app = Fastify({ app.addHook('onRequest', async (request, reply) => { request.raw.on('close', () => { - if (request.raw.aborted) { + if (request.raw.destroyed) { app.log.info('request closed') } }) @@ -85,7 +85,7 @@ functionality: of `{ ok: true }`. - An onRequest hook that triggers when every request is received. - Logic that triggers in the hook when the request is closed. -- Logging that occurs when the closed request property `aborted` is true. +- Logging that occurs when the closed request property `destroyed` is true. In the request close event, you should examine the diff between a successful request and one aborted by the client to determine the best property for your @@ -97,7 +97,7 @@ You can also perform this logic outside of a hook, directly in a specific route. ```js app.get('/', async (request, reply) => { request.raw.on('close', () => { - if (request.raw.aborted) { + if (request.raw.destroyed) { app.log.info('request closed') } }) @@ -112,7 +112,7 @@ aborted and perform alternative actions. ```js app.get('/', async (request, reply) => { await sleep(3000) - if (request.raw.aborted) { + if (request.raw.destroyed) { // do something here } await sleep(3000) From 4e081d420b109c39a6c3cba62989c04eda31d2d6 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Sat, 15 Jul 2023 14:25:17 +0800 Subject: [PATCH 0392/1295] Revert "docs(client-aborted): remove deprecated function (#4898)" (#4901) --- docs/Guides/Detecting-When-Clients-Abort.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md index 9d4861a2627..1bdedec3a99 100644 --- a/docs/Guides/Detecting-When-Clients-Abort.md +++ b/docs/Guides/Detecting-When-Clients-Abort.md @@ -55,7 +55,7 @@ const app = Fastify({ app.addHook('onRequest', async (request, reply) => { request.raw.on('close', () => { - if (request.raw.destroyed) { + if (request.raw.aborted) { app.log.info('request closed') } }) @@ -85,7 +85,7 @@ functionality: of `{ ok: true }`. - An onRequest hook that triggers when every request is received. - Logic that triggers in the hook when the request is closed. -- Logging that occurs when the closed request property `destroyed` is true. +- Logging that occurs when the closed request property `aborted` is true. In the request close event, you should examine the diff between a successful request and one aborted by the client to determine the best property for your @@ -97,7 +97,7 @@ You can also perform this logic outside of a hook, directly in a specific route. ```js app.get('/', async (request, reply) => { request.raw.on('close', () => { - if (request.raw.destroyed) { + if (request.raw.aborted) { app.log.info('request closed') } }) @@ -112,7 +112,7 @@ aborted and perform alternative actions. ```js app.get('/', async (request, reply) => { await sleep(3000) - if (request.raw.destroyed) { + if (request.raw.aborted) { // do something here } await sleep(3000) From 9f575d09cbd90113d73473f5c63b8a6daeaac771 Mon Sep 17 00:00:00 2001 From: Mohamed El Amine Yamani Date: Sat, 15 Jul 2023 12:31:49 +0100 Subject: [PATCH 0393/1295] docs(logging): fix typo (#4905) --- docs/Reference/Logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 59fee063102..69fd9286c56 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -196,7 +196,7 @@ app.addHook('preHandler', function (req, reply, done) { }) ``` -**Note**: Care should be take to ensure serializers never throw, as an error +**Note**: Care should be taken to ensure serializers never throw, as an error thrown from a serializer has the potential to cause the Node process to exit. See the [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) on serializers for more information. From 56ca4189bdb7b7d39fcf68de2b1f903d49934453 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 15 Jul 2023 13:52:39 +0200 Subject: [PATCH 0394/1295] Support IPv6 ::1 in listeningOrigin (#4902) * Support IPv6 ::1 in listeningOrigin Signed-off-by: Matteo Collina * Update fastify.js Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --------- Signed-off-by: Matteo Collina Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --- fastify.js | 3 ++- test/fastify-instance.test.js | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/fastify.js b/fastify.js index 298f9d2021f..8be1366029a 100644 --- a/fastify.js +++ b/fastify.js @@ -355,7 +355,8 @@ function fastify (options) { if (typeof address === 'string') { return address } - return `${this[kOptions].https ? 'https' : 'http'}://${address.address}:${address.port}` + const host = address.family === 'IPv6' ? `[${address.address}]` : address.address + return `${this[kOptions].https ? 'https' : 'http'}://${host}:${address.port}` } }, pluginName: { diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index 32bfa80fef5..4435da0245e 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -152,15 +152,6 @@ test('fastify instance should contains listeningOrigin property (with port and h await fastify.close() }) -test('fastify instance should contains listeningOrigin property (no options)', async t => { - t.plan(1) - const fastify = Fastify() - await fastify.listen() - const address = fastify.server.address() - t.same(fastify.listeningOrigin, `http://${address.address}:${address.port}`) - await fastify.close() -}) - test('fastify instance should contains listeningOrigin property (unix socket)', { skip: os.platform() === 'win32' }, async t => { const fastify = Fastify() const path = `fastify.${Date.now()}.sock` @@ -168,3 +159,13 @@ test('fastify instance should contains listeningOrigin property (unix socket)', t.same(fastify.listeningOrigin, path) await fastify.close() }) + +test('fastify instance should contains listeningOrigin property (IPv6)', async t => { + t.plan(1) + const port = 3000 + const host = '::1' + const fastify = Fastify() + await fastify.listen({ port, host }) + t.same(fastify.listeningOrigin, `http://[::1]:${port}`) + await fastify.close() +}) From c65a6be1b5ecbc99da906e7e19c9ca4d14ee5011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Acosta?= Date: Sat, 15 Jul 2023 08:59:40 -0300 Subject: [PATCH 0395/1295] fix extend isCustomValidatorCompiler from parent controller (#4903) --- lib/schema-controller.js | 2 ++ test/internals/request-validate.test.js | 41 ++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/schema-controller.js b/lib/schema-controller.js index 29a5de5b09e..47915aeea71 100644 --- a/lib/schema-controller.js +++ b/lib/schema-controller.js @@ -50,6 +50,8 @@ class SchemaController { this.schemaBucket = this.opts.bucket(parent.getSchemas()) this.validatorCompiler = parent.getValidatorCompiler() this.serializerCompiler = parent.getSerializerCompiler() + this.isCustomValidatorCompiler = parent.isCustomValidatorCompiler + this.isCustomSerializerCompiler = parent.isCustomSerializerCompiler this.parent = parent } else { this.schemaBucket = this.opts.bucket() diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js index 2d59b1074a9..ce184dc2a4b 100644 --- a/test/internals/request-validate.test.js +++ b/test/internals/request-validate.test.js @@ -745,7 +745,7 @@ test('Nested Context', subtest => { subtest.test('Level_1', tst => { tst.plan(3) tst.test('#compileValidationSchema', ntst => { - ntst.plan(4) + ntst.plan(5) ntst.test('Should return a function - Route without schema', async t => { const fastify = Fastify() @@ -896,6 +896,45 @@ test('Nested Context', subtest => { await fastify.inject('/') } ) + + ntst.test('Should compile the custom validation - nested with schema.headers', async t => { + const fastify = Fastify() + let called = false + + const schemaWithHeaders = { + headers: { + 'x-foo': { + type: 'string' + } + } + } + + const custom = ({ schema, httpPart, url, method }) => { + if (called) return () => true + // only custom validators keep the same headers object + t.equal(schema, schemaWithHeaders.headers) + t.equal(url, '/') + t.equal(httpPart, 'headers') + called = true + return () => true + } + + t.plan(4) + + fastify.setValidatorCompiler(custom) + + fastify.register((instance, opts, next) => { + instance.get('/', { schema: schemaWithHeaders }, (req, reply) => { + t.equal(called, true) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + }) }) tst.test('#getValidationFunction', ntst => { From 62d2cc96f792e7dd7d8a8b6fc7eb19092e26aae8 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 17 Jul 2023 11:50:38 +0200 Subject: [PATCH 0396/1295] fix: allow uppercase requestIdHandler (#4906) --- fastify.js | 2 +- test/request-id.test.js | 131 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 test/request-id.test.js diff --git a/fastify.js b/fastify.js index 8be1366029a..ec327935c74 100644 --- a/fastify.js +++ b/fastify.js @@ -109,7 +109,7 @@ function fastify (options) { validateBodyLimitOption(options.bodyLimit) - const requestIdHeader = (options.requestIdHeader === false) ? false : (options.requestIdHeader || defaultInitOptions.requestIdHeader) + const requestIdHeader = (options.requestIdHeader === false) ? false : (options.requestIdHeader || defaultInitOptions.requestIdHeader).toLowerCase() const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId) const requestIdLogLabel = options.requestIdLogLabel || 'reqId' const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit diff --git a/test/request-id.test.js b/test/request-id.test.js new file mode 100644 index 00000000000..0ad1bfc8b34 --- /dev/null +++ b/test/request-id.test.js @@ -0,0 +1,131 @@ +'use strict' + +const t = require('tap') +const Fastify = require('..') +const sget = require('simple-get').concat + +t.test('The request id header key can be customized', async (t) => { + t.plan(2) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'my-custom-request-id' + }) + + fastify.get('/', (req, reply) => { + t.equal(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, REQUEST_ID) +}) + +t.test('The request id header key can be customized', async (t) => { + t.plan(2) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'my-custom-request-id' + }) + + fastify.get('/', (req, reply) => { + t.equal(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'MY-CUSTOM-REQUEST-ID': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, REQUEST_ID) +}) + +t.test('The request id header key can be customized', (t) => { + t.plan(4) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'my-custom-request-id' + }) + + fastify.get('/', (req, reply) => { + t.equal(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, (err, address) => { + t.error(err) + t.teardown(() => fastify.close()) + + sget({ + method: 'GET', + url: address, + headers: { + 'my-custom-request-id': REQUEST_ID + } + }, (err, response, body) => { + t.error(err) + t.equal(body.toString(), `{"id":"${REQUEST_ID}"}`) + }) + }) +}) + +t.test('The request id header key can be customized', (t) => { + t.plan(4) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'my-custom-request-id' + }) + + fastify.get('/', (req, reply) => { + t.equal(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, (err, address) => { + t.error(err) + t.teardown(() => fastify.close()) + + sget({ + method: 'GET', + url: address, + headers: { + 'MY-CUSTOM-REQUEST-ID': REQUEST_ID + } + }, (err, response, body) => { + t.error(err) + t.equal(body.toString(), `{"id":"${REQUEST_ID}"}`) + }) + }) +}) + +t.test('The request id header key can be customized', (t) => { + t.plan(4) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'MY-CUSTOM-REQUEST-ID' + }) + + fastify.get('/', (req, reply) => { + t.equal(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, (err, address) => { + t.error(err) + t.teardown(() => fastify.close()) + + sget({ + method: 'GET', + url: address, + headers: { + 'MY-CUSTOM-REQUEST-ID': REQUEST_ID + } + }, (err, response, body) => { + t.error(err) + t.equal(body.toString(), `{"id":"${REQUEST_ID}"}`) + }) + }) +}) From f4a661295cf0685c0dd05497bafa05e23a012746 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Jul 2023 12:04:33 +0200 Subject: [PATCH 0397/1295] Bumped v4.20.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index ec327935c74..222449766ce 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.19.2' +const VERSION = '4.20.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index d291b4db428..d8a9d87a242 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.19.2", + "version": "4.20.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 49a844edbf1c5d64958b81a31114af085a500fa0 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 18 Jul 2023 08:03:22 +0200 Subject: [PATCH 0398/1295] remove license-checker package (#4914) --- .github/workflows/ci.yml | 2 +- package.json | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06e39d9fac7..724e566d622 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: - name: Check licenses run: | - npm run license-checker + npx license-checker --production --summary --onlyAllow="0BSD;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;MIT;" lint: name: Lint diff --git a/package.json b/package.json index d8a9d87a242..746526e0b5e 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "coverage": "npm run unit -- --coverage-report=html", "coverage:ci": "npm run unit -- --coverage-report=html --no-browser --no-check-coverage", "coverage:ci-check-coverage": "c8 check-coverage --branches 100 --functions 100 --lines 100 --statements 100", - "license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"", "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown", "lint:fix": "standard --fix", "lint:markdown": "markdownlint-cli2", @@ -162,7 +161,6 @@ "joi": "^17.9.2", "json-schema-to-ts": "^2.9.1", "JSONStream": "^1.3.5", - "license-checker": "^25.0.1", "markdownlint-cli2": "^0.8.1", "proxyquire": "^2.1.3", "pump": "^3.0.0", From eed0b5b208a545ca07923cc054db85849b341c08 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 18 Jul 2023 08:03:43 +0200 Subject: [PATCH 0399/1295] chore: remove pump devDependency (#4913) --- package.json | 1 - test/stream.test.js | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 746526e0b5e..38cf677d8db 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,6 @@ "JSONStream": "^1.3.5", "markdownlint-cli2": "^0.8.1", "proxyquire": "^2.1.3", - "pump": "^3.0.0", "self-cert": "^2.0.0", "send": "^0.18.0", "simple-get": "^4.0.1", diff --git a/test/stream.test.js b/test/stream.test.js index 5014244e504..b5da8d0776e 100644 --- a/test/stream.test.js +++ b/test/stream.test.js @@ -7,7 +7,7 @@ const sget = require('simple-get').concat const fs = require('fs') const resolve = require('path').resolve const zlib = require('zlib') -const pump = require('pump') +const pipeline = require('stream').pipeline const Fastify = require('..') const errors = require('http-errors') const JSONStream = require('JSONStream') @@ -139,7 +139,7 @@ test('onSend hook stream', t => { const gzStream = zlib.createGzip() reply.header('Content-Encoding', 'gzip') - pump( + pipeline( fs.createReadStream(resolve(process.cwd() + '/test/stream.test.js'), 'utf8'), gzStream, t.error @@ -637,7 +637,7 @@ test('should destroy stream when response is ended', t => { fastify.get('/error', function (req, reply) { const reallyLongStream = new stream.Readable({ - read: function () {}, + read: function () { }, destroy: function (err, callback) { t.ok('called') callback(err) @@ -763,7 +763,7 @@ test('request terminated should not crash fastify', t => { fastify.get('/', async (req, reply) => { const stream = new Readable() - stream._read = () => {} + stream._read = () => { } reply.header('content-type', 'text/html; charset=utf-8') reply.header('transfer-encoding', 'chunked') stream.push('

HTML

') From ffa8140109285c865f262a151e9baca258cc4c27 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 18 Jul 2023 08:06:46 +0200 Subject: [PATCH 0400/1295] fix: create artifacts in coverage workflows (#4909) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38cf677d8db..74d92dc2e78 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "coverage": "npm run unit -- --coverage-report=html", - "coverage:ci": "npm run unit -- --coverage-report=html --no-browser --no-check-coverage", + "coverage:ci": "c8 --reporter=lcov tap --coverage-report=html --no-browser --no-check-coverage", "coverage:ci-check-coverage": "c8 check-coverage --branches 100 --functions 100 --lines 100 --statements 100", "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown", "lint:fix": "standard --fix", From 744d59c67cf0c895dd53efad73446a1a120584cb Mon Sep 17 00:00:00 2001 From: Philipp Viereck <105976309+philippviereck@users.noreply.github.com> Date: Tue, 18 Jul 2023 16:08:24 +0200 Subject: [PATCH 0401/1295] fix: requestIdHeader docs (#4916) --- docs/Reference/Server.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index dc13956c0a1..5a66100e7b6 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -509,8 +509,8 @@ The header name used to set the request-id. See [the request-id](./Logging.md#logging-request-id) section. Setting `requestIdHeader` to `true` will set the `requestIdHeader` to `"request-id"`. -Setting `requestIdHeader` to a String other than empty string will set the -`requestIdHeader` to `false`. +Setting `requestIdHeader` to a non-empty string will use +the specified string as the `requestIdHeader`. By default `requestIdHeader` is set to `false` and will immediately use [genReqId](#genreqid). Setting `requestIdHeader` to an empty String (`""`) will set the requestIdHeader to `false`. From bf01ba4438fd662361a23087d23056caa044aa5e Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 18 Jul 2023 19:57:43 +0100 Subject: [PATCH 0402/1295] docs(server): grammar and structure fixes (#4904) * docs(server): grammar and structure fixes * docs(server): structure changes * docs(server): grammar fixes * chore: fix line linting * chore: fix line linting again --- docs/Reference/Server.md | 376 ++++++++++++++++++++------------------- 1 file changed, 195 insertions(+), 181 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index e41670cd553..e2ac4dd85ca 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -98,6 +98,8 @@ describes the properties available in that options object. ### `http` ++ Default: `null` + An object used to configure the server's listening socket. The options are the same as the Node.js core [`createServer` method](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_createserver_options_requestlistener). @@ -105,20 +107,20 @@ method](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_create This option is ignored if options [`http2`](#factory-http2) or [`https`](#factory-https) are set. -+ Default: `null` - ### `http2` ++ Default: `false` + If `true` Node.js core's [HTTP/2](https://nodejs.org/dist/latest-v14.x/docs/api/http2.html) module is used for binding the socket. -+ Default: `false` - ### `https` ++ Default: `null` + An object used to configure the server's listening socket for TLS. The options are the same as the Node.js core [`createServer` method](https://nodejs.org/dist/latest-v14.x/docs/api/https.html#https_https_createserver_options_requestlistener). @@ -126,96 +128,100 @@ When this property is `null`, the socket will not be configured for TLS. This option also applies when the [`http2`](#factory-http2) option is set. -+ Default: `null` - ### `connectionTimeout` ++ Default: `0` (no timeout) + Defines the server timeout in milliseconds. See documentation for [`server.timeout` property](https://nodejs.org/api/http.html#http_server_timeout) to understand -the effect of this option. When `serverFactory` option is specified, this option -is ignored. +the effect of this option. -+ Default: `0` (no timeout) +When `serverFactory` option is specified this option is ignored. ### `keepAliveTimeout` ++ Default: `72000` (72 seconds) + Defines the server keep-alive timeout in milliseconds. See documentation for [`server.keepAliveTimeout` property](https://nodejs.org/api/http.html#http_server_keepalivetimeout) to understand the effect of this option. This option only applies when HTTP/1 is in -use. Also, when `serverFactory` option is specified, this option is ignored. +use. -+ Default: `72000` (72 seconds) +When `serverFactory` option is specified this option is ignored. ### `forceCloseConnections` ++ Default: `"idle"` if the HTTP server allows it, `false` otherwise + When set to `true`, upon [`close`](#close) the server will iterate the current persistent connections and [destroy their sockets](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketdestroyerror). -> Important: connections are not inspected to determine if requests have been -> completed. +> **Warning** +> Connections are not inspected to determine if requests have +> been completed. Fastify will prefer the HTTP server's [`closeAllConnections`](https://nodejs.org/dist/latest-v18.x/docs/api/http.html#servercloseallconnections) -method if supported, otherwise it will use internal connection tracking. +method if supported, otherwise, it will use internal connection tracking. When set to `"idle"`, upon [`close`](#close) the server will iterate the current persistent connections which are not sending a request or waiting for a response -and destroy their sockets. The value is supported only if the HTTP server +and destroy their sockets. The value is only supported if the HTTP server supports the [`closeIdleConnections`](https://nodejs.org/dist/latest-v18.x/docs/api/http.html#servercloseidleconnections) method, otherwise attempting to set it will throw an exception. -+ Default: `"idle"` if the HTTP server allows it, `false` otherwise - ### `maxRequestsPerSocket` -Defines the maximum number of requests socket can handle before closing keep -alive connection. See documentation for [`server.maxRequestsPerSocket` ++ Default: `0` (no limit) + +Defines the maximum number of requests a socket can handle before closing keep +alive connection. See [`server.maxRequestsPerSocket` property](https://nodejs.org/dist/latest/docs/api/http.html#http_server_maxrequestspersocket) to understand the effect of this option. This option only applies when HTTP/1.1 is in use. Also, when `serverFactory` option is specified, this option is ignored. -> At the time of this writing, only node version greater or equal to 16.10.0 -> support this option. Check the Node.js documentation for availability in the -> version you are running. -+ Default: `0` (no limit) +> **Note** +> At the time of writing, only node >= v16.10.0 supports this option. ### `requestTimeout` ++ Default: `0` (no limit) + Defines the maximum number of milliseconds for receiving the entire request from -the client. [`server.requestTimeout` +the client. See [`server.requestTimeout` property](https://nodejs.org/dist/latest/docs/api/http.html#http_server_requesttimeout) -to understand the effect of this option. Also, when `serverFactory` option is -specified, this option is ignored. It must be set to a non-zero value (e.g. 120 -seconds) to protect against potential Denial-of-Service attacks in case the -server is deployed without a reverse proxy in front. -> At the time of this writing, only node version greater or equal to 14.11.0 -> support this option. Check the Node.js documentation for availability in the -> version you are running. +to understand the effect of this option. -+ Default: `0` (no limit) +When `serverFactory` option is specified, this option is ignored. +It must be set to a non-zero value (e.g. 120 seconds) to protect against potential +Denial-of-Service attacks in case the server is deployed without a reverse proxy +in front. + +> **Note** +> At the time of writing, only node >= v14.11.0 supports this option ### `ignoreTrailingSlash` ++ Default: `false` + Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle -routing. By default, Fastify is set to take into account the trailing slashes. -Paths like `/foo` and `/foo/` will be treated as different paths. If you want to +routing. By default, Fastify will take into account the trailing slashes. +Paths like `/foo` and `/foo/` are treated as different paths. If you want to change this, set this flag to `true`. That way, both `/foo` and `/foo/` will point to the same route. This option applies to *all* route registrations for the resulting server instance. -+ Default: `false` - ```js const fastify = require('fastify')({ ignoreTrailingSlash: true @@ -235,17 +241,17 @@ fastify.get('/bar', function (req, reply) { ### `ignoreDuplicateSlashes` ++ Default: `false` + Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle routing. You can use `ignoreDuplicateSlashes` option to remove duplicate slashes -from the path. It removes duplicate slashes in the route path and in the request +from the path. It removes duplicate slashes in the route path and the request URL. This option applies to *all* route registrations for the resulting server instance. -Note that when `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set -to true, Fastify will remove duplicate slashes, and then trailing slashes, -meaning //a//b//c// will be converted to /a/b/c. - -+ Default: `false` +When `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set +to `true` Fastify will remove duplicate slashes, and then trailing slashes, +meaning `//a//b//c//` will be converted to `/a/b/c`. ```js const fastify = require('fastify')({ @@ -263,46 +269,45 @@ fastify.get('///foo//bar//', function (req, reply) { You can set a custom length for parameters in parametric (standard, regex, and multi) routes by using `maxParamLength` option; the default value is 100 -characters. +characters. If the maximum length limit is reached, the not found route will +be invoked. This can be useful especially if you have a regex-based route, protecting you -against [DoS +against [ReDoS attacks](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). -*If the maximum length limit is reached, the not found route will be invoked.* - ### `bodyLimit` -Defines the maximum payload, in bytes, the server is allowed to accept. - + Default: `1048576` (1MiB) +Defines the maximum payload, in bytes, the server is allowed to accept. + ### `onProtoPoisoning` ++ Default: `'error'` + Defines what action the framework must take when parsing a JSON object with `__proto__`. This functionality is provided by [secure-json-parse](https://github.com/fastify/secure-json-parse). See [Prototype Poisoning](../Guides/Prototype-Poisoning.md) for more details about prototype poisoning attacks. -Possible values are `'error'`, `'remove'` and `'ignore'`. - -+ Default: `'error'` +Possible values are `'error'`, `'remove'`, or `'ignore'`. ### `onConstructorPoisoning` ++ Default: `'error'` + Defines what action the framework must take when parsing a JSON object with `constructor`. This functionality is provided by [secure-json-parse](https://github.com/fastify/secure-json-parse). See [Prototype Poisoning](../Guides/Prototype-Poisoning.md) for more details about prototype poisoning attacks. -Possible values are `'error'`, `'remove'` and `'ignore'`. - -+ Default: `'error'` +Possible values are `'error'`, `'remove'`, or `'ignore'`. ### `logger` @@ -363,13 +368,18 @@ The possible values this property may have are: ### `disableRequestLogging` -By default, when logging is enabled, Fastify will issue an `info` level log ++ Default: `false` + +When logging is enabled, Fastify will issue an `info` level log message when a request is received and when the response for that request has been sent. By setting this option to `true`, these log messages will be disabled. This allows for more flexible request start and end logging by attaching custom `onRequest` and `onResponse` hooks. -+ Default: `false` +Please note that this option will also disable an error log written by the +default `onResponse` hook on reply callback errors. Other log messages +emitted by Fastify will stay enabled, like deprecation warnings and messages +emitted when requests are received while the server is closing. ```js // Examples of hooks to replicate the disabled functionality. @@ -384,11 +394,6 @@ fastify.addHook('onResponse', (req, reply, done) => { }) ``` -Please note that this setting will also disable an error log written by the -default `onResponse` hook on reply callback errors. Other log messages -emitted by Fastify will stay enabled, like deprecation warnings and messages -emitted when requests are received while the server is closing. - ### `serverFactory` @@ -428,13 +433,17 @@ enhance the server instance inside the `serverFactory` function before the + Default: `true` -Internally, and by default, Fastify will automatically infer the root properties +By default, Fastify will automatically infer the root properties of JSON Schemas if it does not find valid root properties according to the JSON -Schema spec. If you wish to implement your own schema validation compiler, for -example: to parse schemas as JTD instead of JSON Schema, then you can explicitly +Schema spec. If you wish to implement your own schema validation compiler, to +parse schemas as JTD instead of JSON Schema for example, then you can explicitly set this option to `false` to make sure the schemas you receive are unmodified and are not being treated internally as JSON Schema. +Fastify does not throw on invalid schemas so if this option is set to `false` +in an existing project, check that none of your existing schemas become +invalid as a result, as they will be treated as catch-alls. + ```js const AjvJTD = require('ajv/dist/jtd'/* only valid for AJV v7+ */) const ajv = new AjvJTD({ @@ -457,21 +466,23 @@ fastify.post('/', { }) ``` -**Note: Fastify does not currently throw on invalid schemas, so if you turn this -off in an existing project, you need to be careful that none of your existing -schemas become invalid as a result, since they will be treated as a catch-all.** - ### `caseSensitive` -By default, value equal to `true`, routes are registered as case sensitive. That -is, `/foo` is not equivalent to `/Foo`. When set to `false`, routes are -registered in a fashion such that `/foo` is equivalent to `/Foo` which is -equivalent to `/FOO`. ++ Default: `true` + +When `true` routes are registered as case-sensitive. That is, `/foo` +is not equal to `/Foo`. +When `false` then routes are case-insensitive. + +Please note that setting this option to `false` goes against +[RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1). By setting `caseSensitive` to `false`, all paths will be matched as lowercase, but the route parameters or wildcards will maintain their original letter -casing. +casing. +This option does not affect query strings, please refer to +[`querystringParser`](#querystringparser) to change their handling. ```js fastify.get('/user/:username', (request, reply) => { @@ -480,19 +491,13 @@ fastify.get('/user/:username', (request, reply) => { }) ``` -Please note that setting this option to `false` goes against -[RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1). - -Also note, this setting will not affect query strings. If you want to change the -way query strings are handled take a look at -[`querystringParser`](#querystringparser). - - ### `allowUnsafeRegex` -The allowUnsafeRegex setting is false by default, so routes only allow safe -regular expressions. To use unsafe expressions, set allowUnsafeRegex to true. ++ Default `false` + +Disabled by default, so routes only allow safe regular expressions. To use +unsafe expressions, set `allowUnsafeRegex` to `true`. ```js fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => { @@ -500,18 +505,14 @@ fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => { }) ``` -Under the hood: [FindMyWay](https://github.com/delvedor/find-my-way) More info -about safe regexp: [Safe-regex2](https://www.npmjs.com/package/safe-regex2) - - ### `requestIdHeader` ++ Default: `'request-id'` + The header name used to set the request-id. See [the request-id](./Logging.md#logging-request-id) section. -Setting `requestIdHeader` to `false` will always use [genReqId](#genreqid) - -+ Default: `'request-id'` +Setting `requestIdHeader` to `false` will always use [genReqId](#genreqid). ```js const fastify = require('fastify')({ @@ -519,25 +520,31 @@ const fastify = require('fastify')({ //requestIdHeader: false, // -> always use genReqId }) ``` + ### `requestIdLogLabel` -Defines the label used for the request identifier when logging the request. - + Default: `'reqId'` +Defines the label used for the request identifier when logging the request. + ### `genReqId` -Function for generating the request-id. It will receive the _raw_ incoming -request as a parameter. This function is expected to be error-free. - + Default: `value of 'request-id' header if provided or monotonically increasing integers` +Function for generating the request-id. It will receive the _raw_ incoming +request as a parameter. This function is expected to be error-free. + Especially in distributed systems, you may want to override the default ID generation behavior as shown below. For generating `UUID`s you may want to check -out [hyperid](https://github.com/mcollina/hyperid) +out [hyperid](https://github.com/mcollina/hyperid). + +> **Note** +> `genReqId` will be not called if the header set in +> [requestIdHeader](#requestidheader) is available (defaults to +> 'request-id'). ```js let i = 0 @@ -546,21 +553,9 @@ const fastify = require('fastify')({ }) ``` -**Note: genReqId will _not_ be called if the header set in -[requestIdHeader](#requestidheader) is available (defaults to -'request-id').** - ### `trustProxy` -By enabling the `trustProxy` option, Fastify will know that it is sitting behind -a proxy and that the `X-Forwarded-*` header fields may be trusted, which -otherwise may be easily spoofed. - -```js -const fastify = Fastify({ trustProxy: true }) -``` - + Default: `false` + `true/false`: Trust all proxies (`true`) or do not trust any proxies (`false`). @@ -575,6 +570,14 @@ const fastify = Fastify({ trustProxy: true }) } ``` +By enabling the `trustProxy` option, Fastify will know that it is sitting behind +a proxy and that the `X-Forwarded-*` header fields may be trusted, which +otherwise may be easily spoofed. + +```js +const fastify = Fastify({ trustProxy: true }) +``` + For more examples, refer to the [`proxy-addr`](https://www.npmjs.com/package/proxy-addr) package. @@ -590,28 +593,32 @@ fastify.get('/', (request, reply) => { }) ``` -**Note: if a request contains multiple x-forwarded-host or -x-forwarded-proto headers, it is only the last one that is used to -derive request.hostname and request.protocol** +> **Note** +> If a request contains multiple `x-forwarded-host` or `x-forwarded-proto` +> headers, it is only the last one that is used to derive `request.hostname` +> and `request.protocol`. ### `pluginTimeout` ++ Default: `10000` + The maximum amount of time in *milliseconds* in which a plugin can load. If not, [`ready`](#ready) will complete with an `Error` with code `'ERR_AVVIO_PLUGIN_TIMEOUT'`. When set to `0`, disables this check. This controls [avvio](https://www.npmjs.com/package/avvio) 's `timeout` parameter. -+ Default: `10000` - ### `querystringParser` The default query string parser that Fastify uses is the Node.js's core `querystring` module. -You can change this default setting by passing the option `querystringParser` -and use a custom one, such as [`qs`](https://www.npmjs.com/package/qs). +You can use this option to use a custom parser, such as +[`qs`](https://www.npmjs.com/package/qs). + +If you only want the keys (and not the values) to be case insensitive we +recommend using a custom parser to convert only the keys to lowercase. ```js const qs = require('qs') @@ -620,7 +627,7 @@ const fastify = require('fastify')({ }) ``` -You can also use Fastify's default parser but change some handling behaviour, +You can also use Fastify's default parser but change some handling behavior, like the example below for case insensitive keys and values: ```js @@ -630,24 +637,21 @@ const fastify = require('fastify')({ }) ``` -Note, if you only want the keys (and not the values) to be case insensitive we -recommend using a custom parser to convert only the keys to lowercase. - ### `exposeHeadRoutes` ++ Default: `true` + Automatically creates a sibling `HEAD` route for each `GET` route defined. If you want a custom `HEAD` handler without disabling this option, make sure to define it before the `GET` route. -+ Default: `true` - ### `constraints` -Fastify's built in route constraints are provided by `find-my-way`, which allow -constraining routes by `version` or `host`. You are able to add new constraint -strategies, or override the built in strategies by providing a `constraints` +Fastify's built-in route constraints are provided by `find-my-way`, which +allows constraining routes by `version` or `host`. You can add new constraint +strategies, or override the built-in strategies, by providing a `constraints` object with strategies for `find-my-way`. You can find more information on constraint strategies in the [find-my-way](https://github.com/delvedor/find-my-way) documentation. @@ -676,11 +680,11 @@ const fastify = require('fastify')({ ### `return503OnClosing` ++ Default: `true` + Returns 503 after calling `close` server method. If `false`, the server routes the incoming request as usual. -+ Default: `true` - ### `ajv` @@ -709,7 +713,7 @@ const fastify = require('fastify')({ Customize the options of the default [`fast-json-stringify`](https://github.com/fastify/fast-json-stringify#options) -instance that serialize the response's payload: +instance that serializes the response's payload: ```js const fastify = require('fastify')({ @@ -722,15 +726,17 @@ const fastify = require('fastify')({ ### `http2SessionTimeout` ++ Default: `72000` + Set a default -[timeout](https://nodejs.org/api/http2.html#http2_http2session_settimeout_msecs_callback) -to every incoming HTTP/2 session. The session will be closed on the timeout. -Default: `72000` ms. +[timeout](https://nodejs.org/api/http2.html#http2sessionsettimeoutmsecs-callback) +to every incoming HTTP/2 session in milliseconds. The session will be closed on +the timeout. -Note that this is needed to offer the graceful "close" experience when using +This option is needed to offer a graceful "close" experience when using HTTP/2. The low default has been chosen to mitigate denial of service attacks. When the server is behind a load balancer or can scale automatically this value -can be increased to fit the use case. Node core defaults this to `0`. ` +can be increased to fit the use case. Node core defaults this to `0`. ### `frameworkErrors` @@ -741,8 +747,8 @@ Fastify provides default error handlers for the most common use cases. It is possible to override one or more of those handlers with custom code using this option. -*Note: Only `FST_ERR_BAD_URL` and `FST_ERR_ASYNC_CONSTRAINT` are implemented at -the moment.* +> **Note** +> Only `FST_ERR_BAD_URL` and `FST_ERR_ASYNC_CONSTRAINT` are implemented at present. ```js const fastify = require('fastify')({ @@ -794,10 +800,11 @@ function defaultClientErrorHandler (err, socket) { } ``` -*Note: `clientErrorHandler` operates with raw socket. The handler is expected to -return a properly formed HTTP response that includes a status line, HTTP headers -and a message body. Before attempting to write the socket, the handler should -check if the socket is still writable as it may have already been destroyed.* +> **Note** +> `clientErrorHandler` operates with raw sockets. The handler is expected to +> return a properly formed HTTP response that includes a status line, HTTP headers +> and a message body. Before attempting to write the socket, the handler should +> check if the socket is still writable as it may have already been destroyed. ```js const fastify = require('fastify')({ @@ -826,9 +833,11 @@ const fastify = require('fastify')({ Set a sync callback function that must return a string that allows rewriting -URLs. +URLs. This is useful when you are behind a proxy that changes the URL. +Rewriting a URL will modify the `url` property of the `req` object. -> Rewriting a URL will modify the `url` property of the `req` object +Note that `rewriteUrl` is called _before_ routing, it is not encapsulated and it +is an instance-wide configuration. ```js // @param {object} req The raw Node.js HTTP request, not the `FastifyRequest` object. @@ -844,9 +853,6 @@ function rewriteUrl (req) { } ``` -Note that `rewriteUrl` is called _before_ routing, it is not encapsulated and it -is an instance-wide configuration. - ## Instance ### Server Methods @@ -858,8 +864,9 @@ is an instance-wide configuration. [server](https://nodejs.org/api/http.html#http_class_http_server) object as returned by the [**`Fastify factory function`**](#factory). ->__Warning__: If utilized improperly, certain Fastify features could be disrupted. ->It is recommended to only use it for attaching listeners. +> **Warning** +> If utilized improperly, certain Fastify features could be disrupted. +> It is recommended to only use it for attaching listeners. #### after @@ -1057,8 +1064,9 @@ Note that the array contains the `fastify.server.address()` too. #### getDefaultRoute -**Notice**: this method is deprecated and should be removed in the next Fastify -major version. +> **Warning** +> This method is deprecated and will be removed in the next Fastify +> major version. The `defaultRoute` handler handles requests that do not match any URL specified by your Fastify application. This defaults to the 404 handler, but can be @@ -1072,9 +1080,10 @@ const defaultRoute = fastify.getDefaultRoute() #### setDefaultRoute -**Notice**: this method is deprecated and should be removed in the next Fastify -major version. Please, consider to use `setNotFoundHandler` or a wildcard -matching route. +> **Warning** +> This method is deprecated and will be removed in the next Fastify +> major version. Please, consider using `setNotFoundHandler` or a wildcard +> matching route. The default 404 handler, or one set using `setNotFoundHandler`, will never trigger if the default route is overridden. This sets the handler for the @@ -1117,9 +1126,9 @@ Method to add routes to the server, it also has shorthand functions, check Method to check if a route is already registered to the internal router. It -expects an object as payload. `url` and `method` are mandatory fields. It is -possible to also specify `constraints`. The method returns true if the route is -registered, and false if it is not registered. +expects an object as the payload. `url` and `method` are mandatory fields. It +is possible to also specify `constraints`. The method returns `true` if the +route is registered or `false` if not. ```js const routeExists = fastify.hasRoute({ @@ -1220,11 +1229,12 @@ different ways to define a name (in order). Newlines are replaced by ` -- `. This will help to identify the root cause when you deal with many plugins. -Important: If you have to deal with nested plugins, the name differs with the -usage of the [fastify-plugin](https://github.com/fastify/fastify-plugin) because -no new scope is created and therefore we have no place to attach contextual -data. In that case, the plugin name will represent the boot order of all -involved plugins in the format of `fastify -> plugin-A -> plugin-B`. +> **Warning** +> If you have to deal with nested plugins, the name differs with the usage of +> the [fastify-plugin](https://github.com/fastify/fastify-plugin) because +> no new scope is created and therefore we have no place to attach contextual +> data. In that case, the plugin name will represent the boot order of all +> involved plugins in the format of `fastify -> plugin-A -> plugin-B`. #### hasPlugin @@ -1327,7 +1337,9 @@ Set the schema error formatter for all routes. See Set the schema serializer compiler for all routes. See [#schema-serializer](./Validation-and-Serialization.md#schema-serializer). -**Note:** [`setReplySerializer`](#set-reply-serializer) has priority if set! + +> **Note** +> [`setReplySerializer`](#set-reply-serializer) has priority if set! #### validatorCompiler @@ -1362,9 +1374,7 @@ This property can be used to fully manage: - `compilersFactory`: what module must compile the JSON schemas It can be useful when your schemas are stored in another data structure that is -unknown to Fastify. See [issue -#2446](https://github.com/fastify/fastify/issues/2446) for an example of what -this property helps to resolve. +unknown to Fastify. Another use case is to tweak all the schemas processing. Doing so it is possible to use Ajv v8 JTD or Standalone feature. To use such as JTD or the Standalone @@ -1404,7 +1414,7 @@ const fastify = Fastify({ }, /** - * The compilers factory let you fully control the validator and serializer + * The compilers factory lets you fully control the validator and serializer * in the Fastify's lifecycle, providing the encapsulation to your compilers. */ compilersFactory: { @@ -1461,10 +1471,12 @@ lifecycle](./Lifecycle.md#lifecycle). *async-await* is supported as well. You can also register [`preValidation`](./Hooks.md#route-hooks) and [`preHandler`](./Hooks.md#route-hooks) hooks for the 404 handler. -_Note: The `preValidation` hook registered using this method will run for a -route that Fastify does not recognize and **not** when a route handler manually -calls [`reply.callNotFound`](./Reply.md#call-not-found)_. In which case, only -preHandler will be run. +> **Note** +> The `preValidation` hook registered using this method will run for a +> route that Fastify does not recognize and **not** when a route handler manually +> calls [`reply.callNotFound`](./Reply.md#call-not-found). In which case, only +> preHandler will be run. + ```js fastify.setNotFoundHandler({ @@ -1495,8 +1507,9 @@ plugins are registered. If you would like to augment the behavior of the default arguments `fastify.setNotFoundHandler()` within the context of these registered plugins. -> Note: Some config properties from the request object will be -> undefined inside the custom not found handler. E.g: +> **Note** +> Some config properties from the request object will be +> undefined inside the custom not found handler. E.g.: > `request.routerPath`, `routerMethod` and `context.config`. > This method design goal is to allow calling the common not found route. > To return a per-route customized 404 response, you can do it in @@ -1510,11 +1523,12 @@ will be called whenever an error happens. The handler is bound to the Fastify instance and is fully encapsulated, so different plugins can set different error handlers. *async-await* is supported as well. -*Note: If the error `statusCode` is less than 400, Fastify will automatically -set it at 500 before calling the error handler.* +If the error `statusCode` is less than 400, Fastify will automatically +set it to 500 before calling the error handler. -*Also note* that `setErrorHandler` will ***not*** catch any error inside -an `onResponse` hook because the response has already been sent to the client. +> **Note** +> `setErrorHandler` will ***not*** catch any error inside +> an `onResponse` hook because the response has already been sent to the client. ```js fastify.setErrorHandler(function (error, request, reply) { @@ -1545,10 +1559,10 @@ if (statusCode >= 500) { `fastify.setChildLoggerFactory(factory(logger, bindings, opts, rawReq))`: Set a function that will be called when creating a child logger instance for each request which allows for modifying or adding child logger bindings and logger options, or -returning a completely custom child logger implementation. +returning a custom child logger implementation. -Child logger bindings have a performance advantage over per-log bindings, because -they are pre-serialised by Pino when the child logger is created. +Child logger bindings have a performance advantage over per-log bindings because +they are pre-serialized by Pino when the child logger is created. The first parameter is the parent logger instance, followed by the default bindings and logger options which should be passed to the child logger, and finally @@ -1616,7 +1630,7 @@ a custom constraint strategy with the same name. `fastify.printRoutes()`: Fastify router builds a tree of routes for each HTTP method. If you call the prettyPrint without specifying an HTTP method, it will merge all the trees into one and print it. The merged tree doesn't represent the -internal router structure. **Don't use it for debugging.** +internal router structure. **Do not use it for debugging.** *Remember to call it inside or after a `ready` call.* @@ -1658,8 +1672,8 @@ param. Printed tree will represent the internal router structure. ``` `fastify.printRoutes({ commonPrefix: false })` will print compressed trees. This -might useful when you have a large number of routes with common prefixes. -It doesn't represent the internal router structure. **Don't use it for debugging.** +may be useful when you have a large number of routes with common prefixes. +It doesn't represent the internal router structure. **Do not use it for debugging.** ```js console.log(fastify.printRoutes({ commonPrefix: false })) @@ -1752,7 +1766,7 @@ fastify.ready(() => { `fastify.addContentTypeParser(content-type, options, parser)` is used to pass -custom parser for a given content type. Useful for adding parsers for custom +a custom parser for a given content type. Useful for adding parsers for custom content types, e.g. `text/json, application/vnd.oasis.opendocument.text`. `content-type` can be a string, string array or RegExp. @@ -1854,7 +1868,7 @@ for more info. `fastify.initialConfig`: Exposes a frozen read-only object registering the initial options passed down by the user to the Fastify instance. -Currently the properties that can be exposed are: +The properties that can currently be exposed are: - connectionTimeout - keepAliveTimeout - bodyLimit From b31b1b48a148e8fae4d65a7b8eb2d271389d02db Mon Sep 17 00:00:00 2001 From: Jason Gwartz Date: Fri, 21 Jul 2023 11:59:15 +0100 Subject: [PATCH 0403/1295] Fix typo in TypeScript docs ("@types/node", not "@node/types") (#4922) --- docs/Reference/TypeScript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 7148d016067..35aaa660140 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -788,7 +788,7 @@ There are a couple supported import methods with the Fastify type system. Many type definitions share the same generic parameters; they are all documented, in detail, within this section. -Most definitions depend on `@node/types` modules `http`, `https`, and `http2` +Most definitions depend on `@types/node` modules `http`, `https`, and `http2` ##### RawServer Underlying Node.js server type From 5473ce398abbf99862ef900c42c3f2067324f183 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 22 Jul 2023 00:14:07 -0700 Subject: [PATCH 0404/1295] [readme] add CII Best Practices Badge (#4926) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 228126800fb..a20167d1c3c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?br [![Web SIte](https://github.com/fastify/fastify/workflows/website/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585) @@ -48,7 +49,7 @@ The `main` branch refers to the Fastify `v4` release. Check out the -### Table of Contents +### Table of Contents - [Quick start](#quick-start) - [Install](#install) From 7b0f8ed28c425f724e22be1e4a388d4dcba81661 Mon Sep 17 00:00:00 2001 From: Andrea Carraro Date: Mon, 24 Jul 2023 10:11:53 +0200 Subject: [PATCH 0405/1295] fix: lowercase type-providers headers types (#4928) * fix: lowercase type-providers headers types * test: lowercase type-providers headers types --- test/types/type-provider.test-d.ts | 56 ++++++++++++++++++++++++++++++ types/type-provider.d.ts | 3 +- types/utils.d.ts | 9 +++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index ae7ad228c3b..613c5c8edc3 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -278,6 +278,62 @@ expectAssignable(server.withTypeProvider().get( } )) +// ------------------------------------------------------------------- +// Request headers +// ------------------------------------------------------------------- + +// JsonSchemaToTsProvider +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + headers: { + type: 'object', + properties: { + lowercase: { type: 'string' }, + UPPERCASE: { type: 'number' }, + camelCase: { type: 'boolean' }, + 'KEBAB-case': { type: 'boolean' }, + PRESERVE_OPTIONAL: { type: 'number' } + }, + required: ['lowercase', 'UPPERCASE', 'camelCase', 'KEBAB-case'] + } as const + } + }, + (req) => { + expectType(req.headers.lowercase) + expectType(req.headers.UPPERCASE) + expectType(req.headers.uppercase) + expectType(req.headers.camelcase) + expectType(req.headers['kebab-case']) + expectType(req.headers.preserve_optional) + } +)) + +// TypeBoxProvider +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + headers: Type.Object({ + lowercase: Type.String(), + UPPERCASE: Type.Number(), + camelCase: Type.Boolean(), + 'KEBAB-case': Type.Boolean(), + PRESERVE_OPTIONAL: Type.Optional(Type.Number()) + }) + } + }, + (req) => { + expectType(req.headers.lowercase) + expectType(req.headers.UPPERCASE) + expectType(req.headers.uppercase) + expectType(req.headers.camelcase) + expectType(req.headers['kebab-case']) + expectType(req.headers.preserve_optional) + } +)) + // ------------------------------------------------------------------- // TypeBox Reply Type // ------------------------------------------------------------------- diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 2d22543e398..04447e816a0 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -1,5 +1,6 @@ import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' +import { RecordKeysToLowercase } from './utils' // ----------------------------------------------------------------------------------------------- // TypeProvider @@ -51,7 +52,7 @@ export interface FastifyRequestType extends FastifyRequestType { params: ResolveRequestParams, query: ResolveRequestQuerystring, - headers: ResolveRequestHeaders, + headers: RecordKeysToLowercase>, body: ResolveRequestBody } diff --git a/types/utils.d.ts b/types/utils.d.ts index 8caa3070470..7edbf347ede 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -69,3 +69,12 @@ export type ReplyKeysToCodes = [Key] extends [never] ? number : export type CodeToReplyKey = `${Code}` extends `${infer FirstDigit extends CodeClasses}${number}` ? `${FirstDigit}xx` : never; + +export type RecordKeysToLowercase = Input extends Record + ? { + [Key in keyof Input as Key extends string + ? Lowercase + : Key + ]: Input[Key]; + } + : Input; From 5fa052ebc155af01b77167bd697b06ac3844e6e4 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 24 Jul 2023 14:34:03 +0200 Subject: [PATCH 0406/1295] feat: ERR_REP_ALREADY_SENT hint that a route may be missing "return reply" (#4921) * ERR_REP_ALREADY_SENT explicitate that a route is missing "return reply" Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * Apply suggestions from code review Co-authored-by: Frazer Smith * Add method Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Co-authored-by: Uzlopak Co-authored-by: Frazer Smith --- lib/errors.js | 2 +- lib/reply.js | 20 ++++++++++++++++---- test/async-await.test.js | 2 +- test/internals/errors.test.js | 4 ++-- test/internals/reply.test.js | 35 +++++++++++++++++++++++++++++++++-- test/serial/logger.0.test.js | 7 ++++++- 6 files changed, 59 insertions(+), 11 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index 1f44188521a..a371bcbac37 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -214,7 +214,7 @@ const codes = { ), FST_ERR_REP_ALREADY_SENT: createError( 'FST_ERR_REP_ALREADY_SENT', - 'Reply was already sent.' + 'Reply was already sent, did you forget to "return reply" in "%s" (%s)?' ), FST_ERR_REP_SENT_VALUE: createError( 'FST_ERR_REP_SENT_VALUE', diff --git a/lib/reply.js b/lib/reply.js index 75b02394f55..e9f631bcd7b 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -97,7 +97,7 @@ Object.defineProperties(Reply.prototype, { // We throw only if sent was overwritten from Fastify if (this.sent && this[kReplyHijacked]) { - throw new FST_ERR_REP_ALREADY_SENT() + throw new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) } this[kReplyHijacked] = true @@ -124,7 +124,7 @@ Reply.prototype.send = function (payload) { } if (this.sent) { - this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }, 'Reply already sent') + this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) }) return this } @@ -539,6 +539,18 @@ function wrapOnSendEnd (err, request, reply, payload) { } } +function safeWriteHead (reply, statusCode) { + const res = reply.raw + try { + res.writeHead(statusCode, reply[kReplyHeaders]) + } catch (err) { + if (err.code === 'ERR_HTTP_HEADERS_SENT') { + reply.log.warn(`Reply was already sent, did you forget to "return reply" in the "${reply.request.raw.url}" (${reply.request.raw.method}) route?`) + } + throw err + } +} + function onSendEnd (reply, payload) { const res = reply.raw const req = reply.request @@ -569,7 +581,7 @@ function onSendEnd (reply, payload) { reply[kReplyHeaders]['content-length'] = '0' } - res.writeHead(statusCode, reply[kReplyHeaders]) + safeWriteHead(reply, statusCode) sendTrailer(payload, res, reply) return } @@ -594,7 +606,7 @@ function onSendEnd (reply, payload) { } } - res.writeHead(statusCode, reply[kReplyHeaders]) + safeWriteHead(reply, statusCode) // write payload first res.write(payload) // then send trailers diff --git a/test/async-await.test.js b/test/async-await.test.js index be40490a11d..6024cdd8cca 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -124,7 +124,7 @@ test('ignore the result of the promise if reply.send is called beforehand (objec }) test('server logs an error if reply.send is called and a value is returned via async/await', t => { - const lines = ['incoming request', 'request completed', 'Reply already sent'] + const lines = ['incoming request', 'request completed', 'Reply was already sent, did you forget to "return reply" in "/" (GET)?'] t.plan(lines.length + 2) const splitStream = split(JSON.parse) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index d158b1ad34b..7a20abd6fca 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -339,10 +339,10 @@ test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => { test('FST_ERR_REP_ALREADY_SENT', t => { t.plan(5) - const error = new errors.FST_ERR_REP_ALREADY_SENT() + const error = new errors.FST_ERR_REP_ALREADY_SENT('/hello', 'GET') t.equal(error.name, 'FastifyError') t.equal(error.code, 'FST_ERR_REP_ALREADY_SENT') - t.equal(error.message, 'Reply was already sent.') + t.equal(error.message, 'Reply was already sent, did you forget to "return reply" in "/hello" (GET)?') t.equal(error.statusCode, 500) t.ok(error instanceof Error) }) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 4d6ca401d6b..1fc300bd57d 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -1508,7 +1508,7 @@ test('should throw error when passing falsy value to reply.sent', t => { }) test('should throw error when attempting to set reply.sent more than once', t => { - t.plan(4) + t.plan(3) const fastify = Fastify() fastify.get('/', function (req, reply) { @@ -1518,7 +1518,6 @@ test('should throw error when attempting to set reply.sent more than once', t => t.fail('must throw') } catch (err) { t.equal(err.code, 'FST_ERR_REP_ALREADY_SENT') - t.equal(err.message, 'Reply was already sent.') } reply.raw.end() }) @@ -2088,3 +2087,35 @@ test('invalid response headers and custom error handler', async t => { await fastify.close() }) + +test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', t => { + t.plan(2) + + const response = new Writable() + Object.assign(response, { + setHeader: () => {}, + hasHeader: () => false, + getHeader: () => undefined, + writeHead: () => { + const err = new Error('kaboom') + err.code = 'ERR_HTTP_HEADERS_SENT' + throw err + }, + write: () => {}, + headersSent: true + }) + + const log = { + warn: (msg) => { + t.equal(msg, 'Reply was already sent, did you forget to "return reply" in the "/hello" (GET) route?') + } + } + + const reply = new Reply(response, { [kRouteContext]: { onSend: null }, raw: { url: '/hello', method: 'GET' } }, log) + + try { + reply.send('') + } catch (err) { + t.equal(err.code, 'ERR_HTTP_HEADERS_SENT') + } +}) diff --git a/test/serial/logger.0.test.js b/test/serial/logger.0.test.js index 3c0b6769b2a..f210afd6127 100644 --- a/test/serial/logger.0.test.js +++ b/test/serial/logger.0.test.js @@ -519,7 +519,12 @@ t.test('test log stream', (t) => { }) t.test('reply.send logs an error if called twice in a row', async (t) => { - const lines = ['incoming request', 'request completed', 'Reply already sent', 'Reply already sent'] + const lines = [ + 'incoming request', + 'request completed', + 'Reply was already sent, did you forget to "return reply" in "/" (GET)?', + 'Reply was already sent, did you forget to "return reply" in "/" (GET)?' + ] t.plan(lines.length + 1) const stream = split(JSON.parse) From 9d2da7586dc2ff02414f2e25245ab657244275ac Mon Sep 17 00:00:00 2001 From: Pedro Escumalha <63684493+pedroescumalha@users.noreply.github.com> Date: Wed, 26 Jul 2023 10:29:28 +0200 Subject: [PATCH 0407/1295] fix: ReplyTypeConstrainer array type inference (#4885) * fixed ReplyTypeConstrainer array type inference * Fix ReplyTypeConstrainer Make sure the reply type is a valid object with only http codes as keys * remove required constraint --- test/types/reply.test-d.ts | 25 +++++++++++++++++++++++++ types/reply.d.ts | 14 ++++++++------ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 145cf8c03fe..e239a112f33 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -53,6 +53,10 @@ interface ReplyPayload { }; } +interface ReplyArrayPayload { + Reply: string[] +} + interface ReplyUnion { Reply: { success: boolean; @@ -70,6 +74,14 @@ interface ReplyHttpCodes { } } +interface InvalidReplyHttpCodes { + Reply: { + '1xx': number, + 200: string, + 999: boolean, + } +} + const typedHandler: RouteHandler = async (request, reply) => { expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.send) expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.code(100).send) @@ -137,3 +149,16 @@ expectError(server.get('/get-generic-http-codes-send-error-4', a expectError(server.get('/get-generic-http-codes-send-error-5', async function handler (request, reply) { reply.code(401).send({ foo: 123 }) })) +server.get('/get-generic-array-send', async function handler (request, reply) { + reply.code(200).send(['']) +}) +expectError(server.get('get-invalid-http-codes-reply-error', async function handler (request, reply) { + reply.code(200).send('') +})) +server.get('get-invalid-http-codes-reply-error', async function handler (request, reply) { + reply.code(200).send({ + '1xx': 0, + 200: '', + 999: false + }) +}) diff --git a/types/reply.d.ts b/types/reply.d.ts index fc1386936d0..f368ad407ee 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -6,17 +6,19 @@ import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider' -import { CodeToReplyKey, ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils' +import { CodeToReplyKey, ContextConfigDefault, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils' export interface ReplyGenericInterface { Reply?: ReplyDefault; } -type ReplyTypeConstrainer, ReplyKey = CodeToReplyKey> = - Code extends keyof RouteGenericReply ? RouteGenericReply[Code] : - [ReplyKey] extends [never] ? unknown : - ReplyKey extends keyof RouteGenericReply ? RouteGenericReply[ReplyKey] : - RouteGenericReply; +type HttpCodesReplyType = Partial> + +type ReplyTypeConstrainer> = + RouteGenericReply extends HttpCodesReplyType & Record, never> ? + Code extends keyof RouteGenericReply ? RouteGenericReply[Code] : + CodeToReplyKey extends keyof RouteGenericReply ? RouteGenericReply[CodeToReplyKey] : unknown : + RouteGenericReply; export type ResolveReplyTypeWithRouteGeneric, SchemaCompiler extends FastifySchema = FastifySchema, From ef7408a3feb377838e2aa580a4c05e34e00fdf1a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 26 Jul 2023 10:31:25 +0200 Subject: [PATCH 0408/1295] Bumped v4.21.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 222449766ce..cdcea1165e8 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.20.0' +const VERSION = '4.21.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 74d92dc2e78..294f30046a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.20.0", + "version": "4.21.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 82a9932f965e68dc2c2d8f9d900ff7a7ae03b66c Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Fri, 28 Jul 2023 18:48:29 +0900 Subject: [PATCH 0409/1295] make FastifySchemaValidationError.params wider (#4476) * fix: FastifySchemaValidationError.params could be unknown * refactor: use FastifySchemaValidationError instead of ValidationResult * fix: export ValidationResult for backword compatibility * test: add non-assignable test for AjvErrorObject --- fastify.d.ts | 17 +++++++---------- test/types/fastify.test-d.ts | 11 ++++++++--- types/schema.d.ts | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/fastify.d.ts b/fastify.d.ts index 4ea743b84aa..b5cb47906b5 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -20,15 +20,15 @@ import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './type import { FastifyReply } from './types/reply' import { FastifyRequest, RequestGenericInterface } from './types/request' import { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route' -import { FastifySchema, FastifySchemaCompiler, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema' +import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema' import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider' import { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils' declare module '@fastify/error' { interface FastifyError { - validation?: fastify.ValidationResult[]; validationContext?: SchemaErrorDataVar; + validation?: FastifySchemaValidationError[]; } } @@ -162,13 +162,10 @@ declare namespace fastify { clientErrorHandler?: (error: ConnectionError, socket: Socket) => void, } - export interface ValidationResult { - keyword: string; - instancePath: string; - schemaPath: string; - params: Record; - message?: string; - } + /** + * @deprecated use {@link FastifySchemaValidationError} + */ + export type ValidationResult = FastifySchemaValidationError; /* Export additional types */ export type { @@ -241,4 +238,4 @@ declare function fastify< // CJS export // const fastify = require('fastify') -export = fastify +export = fastify \ No newline at end of file diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 230cf678fb1..73bbe9df79c 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -10,7 +10,6 @@ import fastify, { InjectOptions, FastifyBaseLogger, RawRequestDefaultExpression, RouteGenericInterface, - ValidationResult, FastifyErrorCodes, FastifyError } from '../../fastify' @@ -18,7 +17,7 @@ import { ErrorObject as AjvErrorObject } from 'ajv' import * as http from 'http' import * as https from 'https' import * as http2 from 'http2' -import { expectType, expectError, expectAssignable } from 'tsd' +import { expectType, expectError, expectAssignable, expectNotAssignable } from 'tsd' import { FastifyLoggerInstance } from '../../types/logger' import { Socket } from 'net' @@ -244,7 +243,13 @@ const ajvErrorObject: AjvErrorObject = { params: {}, message: '' } -expectAssignable(ajvErrorObject) +expectNotAssignable({ + keyword: '', + instancePath: '', + schemaPath: '', + params: '', + message: '' +}) expectAssignable([ajvErrorObject]) expectAssignable('body') diff --git a/types/schema.d.ts b/types/schema.d.ts index 3f0fe57a40b..fc01ac0241b 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -28,7 +28,7 @@ export interface FastifySchemaValidationError { keyword: string; instancePath: string; schemaPath: string; - params: Record; + params: Record; message?: string; } From 395b9117bf0c49406a2ae1f7cdb761aa07f9d41c Mon Sep 17 00:00:00 2001 From: Anderson Date: Fri, 28 Jul 2023 05:50:32 -0400 Subject: [PATCH 0410/1295] docs(ecosystem): add fastify-hashids (#4934) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 28b1130d0f2..6faa548bb48 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -358,6 +358,8 @@ section. Providers. - [`fastify-guard`](https://github.com/hsynlms/fastify-guard) A Fastify plugin that protects endpoints by checking authenticated user roles and/or scopes. +- [`fastify-hashids`](https://github.com/andersonjoseph/fastify-hashids) A Fastify + plugin to encode/decode IDs using [hashids](https://github.com/niieani/hashids.js). - [`fastify-hasura`](https://github.com/ManUtopiK/fastify-hasura) A Fastify plugin to have fun with [Hasura](https://github.com/hasura/graphql-engine). - [`fastify-healthcheck`](https://github.com/smartiniOnGitHub/fastify-healthcheck) From d8165ef18baa0b174137209a248da51d08575b4a Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 28 Jul 2023 12:03:35 +0200 Subject: [PATCH 0411/1295] fix: hasPlugin does not track parent plugins (#4929) Co-authored-by: Carlos Fuentes --- fastify.js | 4 ++-- lib/pluginOverride.js | 13 ++++++++++--- lib/pluginUtils.js | 8 ++++---- test/internals/plugin.test.js | 4 ++-- test/plugin.test.js | 26 ++++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/fastify.js b/fastify.js index cdcea1165e8..49cbb740689 100644 --- a/fastify.js +++ b/fastify.js @@ -241,7 +241,7 @@ function fastify (options) { [kReply]: Reply.buildReply(Reply), [kRequest]: Request.buildRequest(Request, options.trustProxy), [kFourOhFour]: fourOhFour, - [pluginUtils.registeredPlugins]: [], + [pluginUtils.kRegisteredPlugins]: [], [kPluginNameChain]: ['fastify'], [kAvvioBoot]: null, // routing method @@ -312,7 +312,7 @@ function fastify (options) { close: null, printPlugins: null, hasPlugin: function (name) { - return this[kPluginNameChain].includes(name) + return this[pluginUtils.kRegisteredPlugins].includes(name) || this[kPluginNameChain].includes(name) }, // http server listen, diff --git a/lib/pluginOverride.js b/lib/pluginOverride.js index d3d9d7b6e91..0b2adfbbf25 100644 --- a/lib/pluginOverride.js +++ b/lib/pluginOverride.js @@ -27,9 +27,10 @@ const pluginUtils = require('./pluginUtils') module.exports = function override (old, fn, opts) { const shouldSkipOverride = pluginUtils.registerPlugin.call(old, fn) + const fnName = pluginUtils.getPluginName(fn) || pluginUtils.getFuncPreview(fn) if (shouldSkipOverride) { // after every plugin registration we will enter a new name - old[kPluginNameChain].push(pluginUtils.getDisplayName(fn)) + old[kPluginNameChain].push(fnName) return old } @@ -48,8 +49,14 @@ module.exports = function override (old, fn, opts) { instance[kSchemaController] = SchemaController.buildSchemaController(old[kSchemaController]) instance.getSchema = instance[kSchemaController].getSchema.bind(instance[kSchemaController]) instance.getSchemas = instance[kSchemaController].getSchemas.bind(instance[kSchemaController]) - instance[pluginUtils.registeredPlugins] = Object.create(instance[pluginUtils.registeredPlugins]) - instance[kPluginNameChain] = [pluginUtils.getPluginName(fn) || pluginUtils.getFuncPreview(fn)] + + // Track the registered and loaded plugins since the root instance. + // It does not track the current encapsulated plugin. + instance[pluginUtils.kRegisteredPlugins] = Object.create(instance[pluginUtils.kRegisteredPlugins]) + + // Track the plugin chain since the root instance. + // When an non-encapsulated plugin is added, the chain will be updated. + instance[kPluginNameChain] = [fnName] if (instance[kLogSerializers] || opts.logSerializers) { instance[kLogSerializers] = Object.assign(Object.create(instance[kLogSerializers]), opts.logSerializers) diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index ecb456aa493..2bddd77baf1 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -2,7 +2,7 @@ const semver = require('semver') const assert = require('assert') -const registeredPlugins = Symbol.for('registered-plugin') +const kRegisteredPlugins = Symbol.for('registered-plugin') const { kTestInternals } = require('./symbols.js') @@ -65,7 +65,7 @@ function checkDependencies (fn) { dependencies.forEach(dependency => { assert( - this[registeredPlugins].indexOf(dependency) > -1, + this[kRegisteredPlugins].indexOf(dependency) > -1, `The dependency '${dependency}' of plugin '${meta.name}' is not registered` ) }) @@ -128,7 +128,7 @@ function registerPluginName (fn) { const name = meta.name if (!name) return - this[registeredPlugins].push(name) + this[kRegisteredPlugins].push(name) } function registerPlugin (fn) { @@ -142,7 +142,7 @@ function registerPlugin (fn) { module.exports = { getPluginName, getFuncPreview, - registeredPlugins, + kRegisteredPlugins, getDisplayName, registerPlugin } diff --git a/test/internals/plugin.test.js b/test/internals/plugin.test.js index 0d03f234f5a..3d19c18d8e6 100644 --- a/test/internals/plugin.test.js +++ b/test/internals/plugin.test.js @@ -122,7 +122,7 @@ test('checkDependencies should check if the given dependency is present in the i } function context () {} - context[pluginUtilsPublic.registeredPlugins] = ['plugin'] + context[pluginUtilsPublic.kRegisteredPlugins] = ['plugin'] try { pluginUtils.checkDependencies.call(context, fn) @@ -143,7 +143,7 @@ test('checkDependencies should check if the given dependency is present in the i } function context () {} - context[pluginUtilsPublic.registeredPlugins] = [] + context[pluginUtilsPublic.kRegisteredPlugins] = [] try { pluginUtils.checkDependencies.call(context, fn) diff --git a/test/plugin.test.js b/test/plugin.test.js index d4f56bc01ed..51e45e18cb9 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -1247,3 +1247,29 @@ test('hasPlugin returns true when using no encapsulation', async t => { await fastify.ready() }) + +test('hasPlugin returns true when using encapsulation', async t => { + t.plan(2) + + const fastify = Fastify() + + const pluginCallback = function (server, options, done) { + done() + } + const pluginName = 'awesome-plugin' + const plugin = fp(pluginCallback, { name: pluginName }) + + fastify.register(plugin) + + fastify.register(async (server) => { + t.ok(server.hasPlugin(pluginName)) + }) + + fastify.register(async function foo (server) { + server.register(async function bar (server) { + t.ok(server.hasPlugin(pluginName)) + }) + }) + + await fastify.ready() +}) From c7392f8d4a4fd10f93e54fbdcbf7f6694b2803b1 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 1 Aug 2023 19:32:42 +0200 Subject: [PATCH 0412/1295] docs: early hints plugin is fastify plugin (#4947) * docs: early hints plugin is fastify plugin * fix * Update docs/Guides/Ecosystem.md Co-authored-by: Frazer Smith --------- Co-authored-by: Frazer Smith --- docs/Guides/Ecosystem.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 6faa548bb48..84be994540e 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -44,6 +44,9 @@ section. Fastify. - [`@fastify/diagnostics-channel`](https://github.com/fastify/fastify-diagnostics-channel) Plugin to deal with `diagnostics_channel` on Fastify +- [`@fastify/early-hints`](https://github.com/fastify/fastify-early-hints) Plugin + to add HTTP 103 feature based on [RFC + 8297](https://httpwg.org/specs/rfc8297.html). - [`@fastify/elasticsearch`](https://github.com/fastify/fastify-elasticsearch) Plugin to share the same ES client. - [`@fastify/env`](https://github.com/fastify/fastify-env) Load and check @@ -304,9 +307,6 @@ section. object. - [`fastify-dynareg`](https://github.com/greguz/fastify-dynareg) Dynamic plugin register for Fastify. -- [`fastify-early-hints`](https://github.com/zekth/fastify-early-hints) Plugin - to add HTTP 103 feature based on [RFC - 8297](https://httpwg.org/specs/rfc8297.html) - [`fastify-envalid`](https://github.com/alemagio/fastify-envalid) Fastify plugin to integrate [envalid](https://github.com/af/envalid) in your Fastify project. From ace58fe4b64e217fe7f49bf564c5fa6a255c1196 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Fri, 4 Aug 2023 10:31:00 +0200 Subject: [PATCH 0413/1295] chore: add pull request title check (#4951) * chore: add pull request title check * Update .github/workflows/pull-request-title.yml Co-authored-by: Frazer Smith --------- Co-authored-by: Frazer Smith --- .github/dependabot.yml | 6 ++++++ .github/workflows/pull-request-title.yml | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .github/workflows/pull-request-title.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dfa7fa6cba8..5219073ac27 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,12 +2,18 @@ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" + commit-message: + # Prefix all commit messages with "chore: " + prefix: "chore" schedule: interval: "monthly" open-pull-requests-limit: 10 - package-ecosystem: "npm" directory: "/" + commit-message: + # Prefix all commit messages with "chore: " + prefix: "chore" schedule: interval: "weekly" open-pull-requests-limit: 10 diff --git a/.github/workflows/pull-request-title.yml b/.github/workflows/pull-request-title.yml new file mode 100644 index 00000000000..0f484667bdf --- /dev/null +++ b/.github/workflows/pull-request-title.yml @@ -0,0 +1,13 @@ +name: pull request title check +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + pull-request-title-check: + runs-on: ubuntu-latest + steps: + - uses: fastify/action-pr-title@v0 + with: + prefixes: 'build:,chore:,ci:,docs:,feat:,fix:,perf:,refactor:,style:,test:' + github-token: ${{ github.token }} \ No newline at end of file From c63fa60b7695f284ab8bcced9ac4f58f5eb173d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Aug 2023 08:43:17 +0000 Subject: [PATCH 0414/1295] chore: Bump @sinclair/typebox from 0.29.6 to 0.30.2 (#4952) Bumps [@sinclair/typebox](https://github.com/sinclairzx81/typebox) from 0.29.6 to 0.30.2. - [Commits](https://github.com/sinclairzx81/typebox/compare/0.29.6...0.30.2) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 294f30046a3..84db619bdc7 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "homepage": "https://www.fastify.io/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.29.1", + "@sinclair/typebox": "^0.30.2", "@sinonjs/fake-timers": "^11.0.0", "@types/node": "^20.1.0", "@typescript-eslint/eslint-plugin": "^5.59.2", From 5518a4e7dca063a55825deedab6d24acf613c469 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Fri, 4 Aug 2023 16:07:19 +0200 Subject: [PATCH 0415/1295] ci: prove pr title check (#4953) * improve pr title check * Apply suggestions from code review * fix * fix * Update .github/workflows/pull-request-title.yml Co-authored-by: Luis Orbaiceta * Apply suggestions from code review --------- Co-authored-by: Luis Orbaiceta --- .github/workflows/pull-request-title.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request-title.yml b/.github/workflows/pull-request-title.yml index 0f484667bdf..ee0f7a630e6 100644 --- a/.github/workflows/pull-request-title.yml +++ b/.github/workflows/pull-request-title.yml @@ -9,5 +9,5 @@ jobs: steps: - uses: fastify/action-pr-title@v0 with: - prefixes: 'build:,chore:,ci:,docs:,feat:,fix:,perf:,refactor:,style:,test:' - github-token: ${{ github.token }} \ No newline at end of file + regex: '/^(build|chore|ci|docs|feat|types|fix|perf|refactor|style|test)(?:\([^\):]*\))?:\s/' + github-token: ${{ github.token }} From 4754dc7c670df07d70c2d798a4a9229c74ee2c8a Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sat, 5 Aug 2023 14:35:56 +0200 Subject: [PATCH 0416/1295] ci: fix warnings in benchmark workflows (#4954) * improve pr title check * Apply suggestions from code review * fix * fix * ci: fix warnings in benchmark workflow * Update .github/workflows/benchmark.yml * Update .github/workflows/benchmark.yml --- .github/workflows/benchmark-parser.yml | 28 ++++++++++++++++++++------ .github/workflows/benchmark.yml | 28 ++++++++++++++++++++------ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 712425d832f..558cc8533ef 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -40,7 +40,9 @@ jobs: run: | npm run --silent benchmark:parser > ./bench-result.md result=$(awk '/requests in/' ./bench-result.md) - echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" + echo 'BENCH_RESULT${{matrix.node-version}}<> $GITHUB_OUTPUT + echo "$result" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT # main benchmark - uses: actions/checkout@v3 @@ -57,7 +59,9 @@ jobs: run: | npm run --silent benchmark:parser > ./bench-result.md result=$(awk '/requests in/' ./bench-result.md) - echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" + echo 'BENCH_RESULT${{matrix.node-version}}<> $GITHUB_OUTPUT + echo "$result" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT output-benchmark: needs: [benchmark] @@ -89,8 +93,20 @@ jobs: **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} - - uses: actions-ecosystem/action-remove-labels@v1 + remove-label: + if: "always()" + needs: + - benchmark + - output-benchmark + runs-on: ubuntu-latest + steps: + - name: Remove benchmark label + uses: octokit/request-action@v2.x + id: remove-label with: - labels: | - benchmark - github_token: ${{ secrets.GITHUB_TOKEN }} + route: DELETE /repos/{repo}/issues/{issue_number}/labels/{name} + repo: ${{ github.event.pull_request.head.repo.full_name }} + issue_number: ${{ github.event.pull_request.number }} + name: benchmark + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 12006bbdece..ea479a4ac77 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -40,7 +40,9 @@ jobs: run: | npm run --silent benchmark > ./bench-result.md result=$(awk '/requests in/' ./bench-result.md) - echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" + echo 'BENCH_RESULT${{matrix.node-version}}<> $GITHUB_OUTPUT + echo "$result" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT # main benchmark - uses: actions/checkout@v3 @@ -57,7 +59,9 @@ jobs: run: | npm run --silent benchmark > ./bench-result.md result=$(awk '/requests in/' ./bench-result.md) - echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" + echo 'BENCH_RESULT${{matrix.node-version}}<> $GITHUB_OUTPUT + echo "$result" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT output-benchmark: needs: [benchmark] @@ -86,8 +90,20 @@ jobs: **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} - - uses: actions-ecosystem/action-remove-labels@v1 + remove-label: + if: "always()" + needs: + - benchmark + - output-benchmark + runs-on: ubuntu-latest + steps: + - name: Remove benchmark label + uses: octokit/request-action@v2.x + id: remove-label with: - labels: | - benchmark - github_token: ${{ secrets.GITHUB_TOKEN }} + route: DELETE /repos/{repo}/issues/{issue_number}/labels/{name} + repo: ${{ github.event.pull_request.head.repo.full_name }} + issue_number: ${{ github.event.pull_request.number }} + name: benchmark + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 298adbc3cf32b72998736eb503ab61d01c191562 Mon Sep 17 00:00:00 2001 From: Mohammed Gomaa Date: Sat, 5 Aug 2023 16:09:34 +0300 Subject: [PATCH 0417/1295] docs: fix removeAdditional comment (#4948) * docs(Validator Compiler): fix removeAdditional comment * Update docs/Reference/Validation-and-Serialization.md Co-authored-by: Uzlopak * fix: typo in comment * Update docs/Reference/Validation-and-Serialization.md Co-authored-by: Frazer Smith --------- Co-authored-by: Uzlopak Co-authored-by: Frazer Smith --- docs/Reference/Validation-and-Serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 17c2ed2d7a9..460373f9213 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -397,7 +397,7 @@ configuration](https://github.com/fastify/ajv-compiler#ajv-configuration) is: { coerceTypes: 'array', // change data type of data to match type keyword useDefaults: true, // replace missing properties and items with the values from corresponding default keyword - removeAdditional: true, // remove additional properties + removeAdditional: true, // remove additional properties if additionalProperties is set to false, see: https://ajv.js.org/guide/modifying-data.html#removing-additional-properties uriResolver: require('fast-uri'), addUsedSchema: false, // Explicitly set allErrors to `false`. From d380535e0cf4b37424f5248ee62024ef1d7732c7 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Sun, 6 Aug 2023 19:53:35 +0300 Subject: [PATCH 0418/1295] fix: Try to fix parser benchmark workflow (#4956) --- .github/workflows/benchmark-parser.yml | 41 +++++++------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 558cc8533ef..58c2d5c99f8 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -1,4 +1,4 @@ -name: Benchmark-parser +name: Benchmark Parser on: pull_request_target: @@ -11,15 +11,15 @@ jobs: permissions: contents: read outputs: - PR-BENCH-14: ${{ steps.benchmark-pr.outputs.BENCH_RESULT14 }} PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }} PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} - MAIN-BENCH-14: ${{ steps.benchmark-main.outputs.BENCH_RESULT14 }} + PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }} MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} + MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} strategy: matrix: - node-version: [14, 16, 18] + node-version: [16, 18, 20] steps: - uses: actions/checkout@v3 with: @@ -74,39 +74,18 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | - **Node**: 14 - **Type**: Parser - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-14 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-14 }} - - --- - **Node**: 16 - **Type**: Parser **PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }} --- **Node**: 18 - **Type**: Parser **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} - - remove-label: - if: "always()" - needs: - - benchmark - - output-benchmark - runs-on: ubuntu-latest - steps: - - name: Remove benchmark label - uses: octokit/request-action@v2.x - id: remove-label - with: - route: DELETE /repos/{repo}/issues/{issue_number}/labels/{name} - repo: ${{ github.event.pull_request.head.repo.full_name }} - issue_number: ${{ github.event.pull_request.number }} - name: benchmark - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + --- + + **Node**: 20 + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} From b4b4dc7568e7b0bcff7f71a63f8c9630e4cab232 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Aug 2023 14:07:10 +0200 Subject: [PATCH 0419/1295] fix: infer correct hook handler (#4945) * fix: infer correct hook handler * Update test/types/hooks.test-d.ts * simplify * remove duplicate typing tests * remove duplicate test * remove unused import * fix * fix linting --- package.json | 3 +- test/types/hooks.test-d.ts | 13 +++ types/hooks.d.ts | 106 +++++++++++++++++++- types/instance.d.ts | 195 ++++++++++--------------------------- 4 files changed, 169 insertions(+), 148 deletions(-) diff --git a/package.json b/package.json index 84db619bdc7..3a0f1e6429c 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,11 @@ "coverage:ci": "c8 --reporter=lcov tap --coverage-report=html --no-browser --no-check-coverage", "coverage:ci-check-coverage": "c8 check-coverage --branches 100 --functions 100 --lines 100 --statements 100", "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown", - "lint:fix": "standard --fix", + "lint:fix": "standard --fix && npm run lint:typescript:fix", "lint:markdown": "markdownlint-cli2", "lint:standard": "standard | snazzy", "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", + "lint:typescript:fix": "npm run lint:typescript -- --fix", "prepublishOnly": "cross-env PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript", diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index f29543aaa00..7e5f2f80810 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -392,3 +392,16 @@ server.addHook('preClose', function (done) { server.addHook('preClose', async function () { expectType(this) }) + +expectError(server.addHook('onClose', async function (instance, done) {})) +expectError(server.addHook('onError', async function (request, reply, error, done) {})) +expectError(server.addHook('onReady', async function (done) {})) +expectError(server.addHook('onRequest', async function (request, reply, done) {})) +expectError(server.addHook('onRequestAbort', async function (request, done) {})) +expectError(server.addHook('onResponse', async function (request, reply, done) {})) +expectError(server.addHook('onSend', async function (request, reply, payload, done) {})) +expectError(server.addHook('onTimeout', async function (request, reply, done) {})) +expectError(server.addHook('preClose', async function (done) {})) +expectError(server.addHook('preHandler', async function (request, reply, done) {})) +expectError(server.addHook('preSerialization', async function (request, reply, payload, done) {})) +expectError(server.addHook('preValidation', async function (request, reply, done) {})) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 4264d2a5402..ed5dec17fdd 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -190,7 +190,7 @@ interface DoneFuncWithErrOrRes { * Note: the hook is NOT called if the payload is a string, a Buffer, a stream or null. */ export interface preSerializationHookHandler< - PreSerializationPayload, + PreSerializationPayload = unknown, RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, @@ -210,7 +210,7 @@ export interface preSerializationHookHandler< } export interface preSerializationAsyncHookHandler< - PreSerializationPayload, + PreSerializationPayload = unknown, RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, @@ -233,7 +233,7 @@ export interface preSerializationAsyncHookHandler< * Note: If you change the payload, you may only change it to a string, a Buffer, a stream, or null. */ export interface onSendHookHandler< - OnSendPayload, + OnSendPayload = unknown, RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, @@ -253,7 +253,7 @@ export interface onSendHookHandler< } export interface onSendAsyncHookHandler< - OnSendPayload, + OnSendPayload = unknown, RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, @@ -432,6 +432,66 @@ export interface onRequestAbortAsyncHookHandler< ): Promise; } +export type LifecycleHook = 'onRequest' +| 'preParsing' +| 'preValidation' +| 'preHandler' +| 'preSerialization' +| 'onSend' +| 'onResponse' +| 'onRequest' +| 'onError' +| 'onTimeout' +| 'onRequestAbort' + +export type LifecycleHookLookup = K extends 'onRequest' + ? onRequestHookHandler + : K extends 'preParsing' + ? preParsingHookHandler + : K extends 'preValidation' + ? preValidationHookHandler + : K extends 'preHandler' + ? preHandlerHookHandler + : K extends 'preSerialization' + ? preSerializationHookHandler + : K extends 'onSend' + ? onSendHookHandler + : K extends 'onResponse' + ? onResponseHookHandler + : K extends 'onRequest' + ? onRequestHookHandler + : K extends 'onError' + ? onErrorHookHandler + : K extends 'onTimeout' + ? onTimeoutHookHandler + : K extends 'onRequestAbort' + ? onRequestAbortHookHandler + : never + +export type LifecycleHookAsyncLookup = K extends 'onRequest' + ? onRequestAsyncHookHandler + : K extends 'preParsing' + ? preParsingAsyncHookHandler + : K extends 'preValidation' + ? preValidationAsyncHookHandler + : K extends 'preHandler' + ? preHandlerAsyncHookHandler + : K extends 'preSerialization' + ? preSerializationAsyncHookHandler + : K extends 'onSend' + ? onSendAsyncHookHandler + : K extends 'onResponse' + ? onResponseAsyncHookHandler + : K extends 'onRequest' + ? onRequestAsyncHookHandler + : K extends 'onError' + ? onErrorAsyncHookHandler + : K extends 'onTimeout' + ? onTimeoutAsyncHookHandler + : K extends 'onRequestAbort' + ? onRequestAbortAsyncHookHandler + : never + // Application Hooks /** @@ -555,3 +615,41 @@ export interface preCloseAsyncHookHandler< this: FastifyInstance, ): Promise; } + +export type ApplicationHook = 'onRoute' +| 'onRegister' +| 'onReady' +| 'onClose' +| 'preClose' + +export type ApplicationHookLookup = K extends 'onRegister' + ? onRegisterHookHandler + : K extends 'onReady' + ? onReadyHookHandler + : K extends 'onClose' + ? onCloseHookHandler + : K extends 'preClose' + ? preCloseHookHandler + : never + +export type ApplicationHookAsyncLookup = K extends 'onRegister' + ? onRegisterHookHandler + : K extends 'onReady' + ? onReadyAsyncHookHandler + : K extends 'onClose' + ? onCloseAsyncHookHandler + : K extends 'preClose' + ? preCloseAsyncHookHandler + : never + +export type HookLookup = K extends ApplicationHook + ? ApplicationHookLookup + : K extends LifecycleHook + ? LifecycleHookLookup + : never + +export type HookAsyncLookup = K extends ApplicationHook + ? ApplicationHookAsyncLookup + : K extends LifecycleHook + ? LifecycleHookAsyncLookup + : never diff --git a/types/instance.d.ts b/types/instance.d.ts index bac771fac3f..34e8f59f8a3 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -3,7 +3,7 @@ import { ConstraintStrategy, HTTPVersion } from 'find-my-way' import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' -import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, preCloseHookHandler, preCloseAsyncHookHandler } from './hooks' +import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, preCloseHookHandler, preCloseAsyncHookHandler, LifecycleHook, ApplicationHook, HookAsyncLookup, HookLookup } from './hooks' import { FastifyBaseLogger, FastifyChildLoggerFactory } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' @@ -30,6 +30,8 @@ export interface PrintRoutesOptions { includeHooks?: boolean } +type AsyncFunction = (...args: any) => Promise; + export interface FastifyListenOptions { /** * Default to `0` (picks the first available open port). @@ -224,20 +226,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'onRequest', - hook: onRequestHookHandler - ): FastifyInstance; - - addHook< - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onRequestHookHandler | onRequestAsyncHookHandler = onRequestHookHandler >( name: 'onRequest', - hook: onRequestAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? onRequestAsyncHookHandler : onRequestHookHandler : Fn, ): FastifyInstance; /** @@ -248,20 +241,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends preParsingHookHandler | preParsingAsyncHookHandler = preParsingHookHandler >( name: 'preParsing', - hook: preParsingHookHandler - ): FastifyInstance; - - addHook< - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'preParsing', - hook: preParsingAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? preParsingAsyncHookHandler : preParsingHookHandler : Fn, ): FastifyInstance; /** @@ -271,20 +255,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'preValidation', - hook: preValidationHookHandler - ): FastifyInstance; - - addHook< - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends preValidationHookHandler | preValidationAsyncHookHandler = preValidationHookHandler >( name: 'preValidation', - hook: preValidationAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? preValidationAsyncHookHandler : preValidationHookHandler : Fn, ): FastifyInstance; /** @@ -294,20 +269,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'preHandler', - hook: preHandlerHookHandler - ): FastifyInstance; - - addHook< - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends preHandlerHookHandler | preHandlerAsyncHookHandler = preHandlerHookHandler >( name: 'preHandler', - hook: preHandlerAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? preHandlerAsyncHookHandler : preHandlerHookHandler : Fn, ): FastifyInstance; /** @@ -319,21 +285,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends preSerializationHookHandler | preSerializationAsyncHookHandler = preSerializationHookHandler >( name: 'preSerialization', - hook: preSerializationHookHandler - ): FastifyInstance; - - addHook< - PreSerializationPayload = unknown, - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'preSerialization', - hook: preSerializationAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? preSerializationAsyncHookHandler : preSerializationHookHandler : Fn, ): FastifyInstance; /** @@ -345,21 +301,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onSendHookHandler | onSendAsyncHookHandler = onSendHookHandler >( name: 'onSend', - hook: onSendHookHandler - ): FastifyInstance; - - addHook< - OnSendPayload = unknown, - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'onSend', - hook: onSendAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? onSendAsyncHookHandler : onSendHookHandler : Fn, ): FastifyInstance; /** @@ -370,20 +316,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'onResponse', - hook: onResponseHookHandler - ): FastifyInstance; - - addHook< - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onResponseHookHandler | onResponseAsyncHookHandler = onResponseHookHandler >( name: 'onResponse', - hook: onResponseAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? onResponseAsyncHookHandler : onResponseHookHandler : Fn, ): FastifyInstance; /** @@ -394,20 +331,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onTimeoutHookHandler | onTimeoutAsyncHookHandler = onTimeoutHookHandler >( name: 'onTimeout', - hook: onTimeoutHookHandler - ): FastifyInstance; - - addHook< - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'onTimeout', - hook: onTimeoutAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? onTimeoutAsyncHookHandler : onTimeoutHookHandler : Fn, ): FastifyInstance; /** @@ -419,20 +347,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'onRequestAbort', - hook: onRequestAbortHookHandler - ): FastifyInstance; - - addHook< - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onRequestAbortHookHandler | onRequestAbortAsyncHookHandler = onRequestAbortHookHandler >( name: 'onRequestAbort', - hook: onRequestAbortAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? onRequestAbortAsyncHookHandler : onRequestAbortHookHandler : Fn, ): FastifyInstance; /** @@ -445,20 +364,11 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onErrorHookHandler | onErrorAsyncHookHandler = onErrorHookHandler >( name: 'onError', - hook: onErrorHookHandler - ): FastifyInstance; - - addHook< - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, - ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, - Logger extends FastifyBaseLogger = FastifyBaseLogger - >( - name: 'onError', - hook: onErrorAsyncHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? onErrorAsyncHookHandler : onErrorHookHandler : Fn, ): FastifyInstance; // Application addHooks @@ -489,41 +399,40 @@ export interface FastifyInstance< /** * Triggered when fastify.listen() or fastify.ready() is invoked to start the server. It is useful when plugins need a "ready" event, for example to load data before the server start listening for requests. */ - addHook( - name: 'onReady', - hook: onReadyHookHandler - ): FastifyInstance; - - addHook( + addHook< + Fn extends onReadyHookHandler | onReadyAsyncHookHandler = onReadyHookHandler + >( name: 'onReady', - hook: onReadyAsyncHookHandler, + hook: Fn extends unknown ? Fn extends AsyncFunction ? onReadyAsyncHookHandler : onReadyHookHandler : Fn, ): FastifyInstance; /** * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need a "shutdown" event, for example to close an open connection to a database. */ - addHook( + addHook< + Fn extends onCloseHookHandler | onCloseAsyncHookHandler = onCloseHookHandler + >( name: 'onClose', - hook: onCloseHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? onCloseAsyncHookHandler : onCloseHookHandler : Fn, ): FastifyInstance; - addHook( - name: 'onClose', - hook: onCloseAsyncHookHandler - ): FastifyInstance; - /** * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need to cancel some state to allow the server to close successfully. */ - addHook( + addHook< + Fn extends preCloseHookHandler | preCloseAsyncHookHandler = preCloseHookHandler + >( name: 'preClose', - hook: preCloseHookHandler + hook: Fn extends unknown ? Fn extends AsyncFunction ? preCloseAsyncHookHandler : preCloseHookHandler : Fn, ): FastifyInstance; - addHook( - name: 'preClose', - hook: preCloseAsyncHookHandler - ): FastifyInstance; + addHook< + K extends ApplicationHook | LifecycleHook, + Fn extends (...args: any) => Promise | any + > ( + name: K, + hook: Fn extends unknown ? Fn extends AsyncFunction ? HookAsyncLookup : HookLookup : Fn + ): FastifyInstance; /** * Set the 404 handler From c235d2dc136a67c51095f188742c743d8cb32839 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 9 Aug 2023 10:50:35 +0200 Subject: [PATCH 0420/1295] Do not double send the response if the request is destroyed but not aborted (#4963) Signed-off-by: Matteo Collina --- lib/wrapThenable.js | 5 ++++- test/wrapThenable.test.js | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/wrapThenable.js b/lib/wrapThenable.js index b746a62e920..0d4e7ba812f 100644 --- a/lib/wrapThenable.js +++ b/lib/wrapThenable.js @@ -18,7 +18,10 @@ function wrapThenable (thenable, reply) { // the request may be terminated during the reply. in this situation, // it require an extra checking of request.aborted to see whether // the request is killed by client. - if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) { + // Most of the times aborted will be true when destroyed is true, + // however there is a race condition where the request is not + // aborted but only destroyed. + if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false && reply.request.raw.destroyed === false)) { // we use a try-catch internally to avoid adding a catch to another // promise, increase promise perf by 10% try { diff --git a/test/wrapThenable.test.js b/test/wrapThenable.test.js index e953d29c16c..df0ba334589 100644 --- a/test/wrapThenable.test.js +++ b/test/wrapThenable.test.js @@ -27,3 +27,25 @@ test('should reject immediately when reply[kReplyHijacked] is true', t => { const thenable = Promise.reject(new Error('Reply sent already')) wrapThenable(thenable, reply) }) + +test('should not send the payload if the raw socket was destroyed but not aborted', async t => { + const reply = { + sent: false, + raw: { + headersSent: false + }, + request: { + raw: { + aborted: false, + destroyed: true + } + }, + send () { + t.fail('should not send') + } + } + const thenable = Promise.resolve() + wrapThenable(thenable, reply) + + await thenable +}) From bcd2eb673761e7958ed9388ceb94ec12407816cd Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sun, 13 Aug 2023 14:30:57 +0200 Subject: [PATCH 0421/1295] types: remove variadic listen types (#4966) --- test/types/instance.test-d.ts | 42 ----------------------------------- types/instance.d.ts | 21 ------------------ 2 files changed, 63 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 6d8ba063a18..2ff3610036e 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -181,48 +181,6 @@ function invalidSchemaErrorFormatter (err: Error) { } expectError(server.setSchemaErrorFormatter(invalidSchemaErrorFormatter)) -// test listen method callback -expectAssignable(server.listen(3000, '', 0, (err, address) => { - expectType(err) -})) -expectAssignable(server.listen('3000', '', 0, (err, address) => { - expectType(err) -})) -expectAssignable(server.listen(3000, '', (err, address) => { - expectType(err) -})) -expectAssignable(server.listen('3000', '', (err, address) => { - expectType(err) -})) -expectAssignable(server.listen(3000, (err, address) => { - expectType(err) -})) -expectAssignable(server.listen('3000', (err, address) => { - expectType(err) -})) - -// test listen method callback types -expectAssignable(server.listen('3000', (err, address) => { - expectAssignable(err) - expectAssignable(address) -})) - -// test listen method promise -expectAssignable>(server.listen(3000)) -expectAssignable>(server.listen('3000')) -expectAssignable>(server.listen(3000, '', 0)) -expectAssignable>(server.listen('3000', '', 0)) -expectAssignable>(server.listen(3000, '')) -expectAssignable>(server.listen('3000', '')) - -// Test variadic listen signatures Typescript deprecation -expectDeprecated(server.listen(3000)) -expectDeprecated(server.listen('3000')) -expectDeprecated(server.listen(3000, '', 0)) -expectDeprecated(server.listen('3000', '', 0)) -expectDeprecated(server.listen(3000, '')) -expectDeprecated(server.listen('3000', '')) - // test listen opts objects expectAssignable>(server.listen()) expectAssignable>(server.listen({ port: 3000 })) diff --git a/types/instance.d.ts b/types/instance.d.ts index f7d92222689..a7477a5bf29 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -161,27 +161,6 @@ export interface FastifyInstance< listen(opts?: FastifyListenOptions): Promise; listen(callback: (err: Error | null, address: string) => void): void; - /** - * @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5` - * @see https://github.com/fastify/fastify/pull/3712 - */ - listen(port: number | string, address: string, backlog: number, callback: (err: Error|null, address: string) => void): void; - /** - * @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5` - * @see https://github.com/fastify/fastify/pull/3712 - */ - listen(port: number | string, address: string, callback: (err: Error|null, address: string) => void): void; - /** - * @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject, callback)` instead. The variadic signature will be removed in `fastify@5` - * @see https://github.com/fastify/fastify/pull/3712 - */ - listen(port: number | string, callback: (err: Error|null, address: string) => void): void; - /** - * @deprecated Variadic listen method is deprecated. Please use `.listen(optionsObject)` instead. The variadic signature will be removed in `fastify@5` - * @see https://github.com/fastify/fastify/pull/3712 - */ - listen(port: number | string, address?: string, backlog?: number): Promise; - ready(): FastifyInstance & PromiseLike; ready(readyListener: (err: Error) => void): FastifyInstance; From dacbabf352fadbb13df9a4cd85a34fc38a5a7855 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:28:22 +0000 Subject: [PATCH 0422/1295] chore: Bump @sinclair/typebox from 0.30.4 to 0.31.1 (#4973) Bumps [@sinclair/typebox](https://github.com/sinclairzx81/typebox) from 0.30.4 to 0.31.1. - [Commits](https://github.com/sinclairzx81/typebox/compare/0.30.4...0.31.1) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a0f1e6429c..bdbb2c07161 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "homepage": "https://www.fastify.io/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.30.2", + "@sinclair/typebox": "^0.31.1", "@sinonjs/fake-timers": "^11.0.0", "@types/node": "^20.1.0", "@typescript-eslint/eslint-plugin": "^5.59.2", From b2f30d88c2d960c6910111cad20d4a056baf7950 Mon Sep 17 00:00:00 2001 From: Sergey Burnevsky Date: Wed, 16 Aug 2023 19:08:45 +0300 Subject: [PATCH 0423/1295] fix: bodyLimit must be applied on fully decoded body (#4969) * bodyLimit must be applied on fully decoded body * Updated docs and comments --- docs/Reference/Hooks.md | 3 ++ docs/Reference/Server.md | 4 +++ lib/contentTypeParser.js | 7 ++-- test/bodyLimit.test.js | 69 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 36952818cbb..e6343a67e40 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -106,6 +106,9 @@ returned stream. This property is used to correctly match the request payload with the `Content-Length` header value. Ideally, this property should be updated on each received chunk. +**Notice:** The size of the returned stream is checked to not exceed the limit +set in [`bodyLimit`](./Server.md#bodylimit) option. + ### preValidation If you are using the `preValidation` hook, you can change the payload before it diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index e2ac4dd85ca..d4fdcf1b215 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -282,6 +282,10 @@ attacks](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ + Default: `1048576` (1MiB) Defines the maximum payload, in bytes, the server is allowed to accept. +The default body reader sends [`FST_ERR_CTP_BODY_TOO_LARGE`](./Errors.md#fst_err_ctp_body_too_large) +reply, if the size of the body exceeds this limit. +If [`preParsing` hook](./Hooks.md#preparsing) is provided, this limit is applied +to the size of the stream the hook returns (i.e. the size of "decoded" body). ### `onProtoPoisoning` diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 34a4539befb..2733dc3b75d 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -226,8 +226,11 @@ function rawBody (request, reply, options, parser, done) { function onData (chunk) { receivedLength += chunk.length - - if ((payload.receivedEncodedLength || receivedLength) > limit) { + const { receivedEncodedLength = 0 } = payload + // The resulting body length must not exceed bodyLimit (see "zip bomb"). + // The case when encoded length is larger than received length is rather theoretical, + // unless the stream returned by preParsing hook is broken and reports wrong value. + if (receivedLength > limit || receivedEncodedLength > limit) { payload.removeListener('data', onData) payload.removeListener('end', onEnd) payload.removeListener('error', onEnd) diff --git a/test/bodyLimit.test.js b/test/bodyLimit.test.js index 980dd23b2a7..0936b7b7b25 100644 --- a/test/bodyLimit.test.js +++ b/test/bodyLimit.test.js @@ -2,6 +2,7 @@ const Fastify = require('..') const sget = require('simple-get').concat +const zlib = require('zlib') const t = require('tap') const test = t.test @@ -45,6 +46,74 @@ test('bodyLimit', t => { }) }) +test('bodyLimit is applied to decoded content', t => { + t.plan(9) + + const body = { x: 'x'.repeat(30000) } + const json = JSON.stringify(body) + const encoded = zlib.gzipSync(json) + + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + t.equal(req.headers['content-length'], `${encoded.length}`) + const unzip = zlib.createGunzip() + Object.defineProperty(unzip, 'receivedEncodedLength', { + get () { + return unzip.bytesWritten + } + }) + payload.pipe(unzip) + return unzip + }) + + fastify.post('/body-limit-40k', { + bodyLimit: 40000, + onError: async (req, res, err) => { + t.fail('should not be called') + } + }, (request, reply) => { + reply.send({ x: request.body.x }) + }) + + fastify.post('/body-limit-20k', { + bodyLimit: 20000, + onError: async (req, res, err) => { + t.equal(err.code, 'FST_ERR_CTP_BODY_TOO_LARGE') + t.equal(err.statusCode, 413) + } + }, (request, reply) => { + reply.send({ x: 'handler should not be called' }) + }) + + fastify.inject({ + method: 'POST', + url: '/body-limit-40k', + headers: { + 'content-encoding': 'gzip', + 'content-type': 'application/json' + }, + payload: encoded + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 200) + t.same(res.json(), body) + }) + + fastify.inject({ + method: 'POST', + url: '/body-limit-20k', + headers: { + 'content-encoding': 'gzip', + 'content-type': 'application/json' + }, + payload: encoded + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 413) + }) +}) + test('default request.routeOptions.bodyLimit should be 1048576', t => { t.plan(4) const fastify = Fastify() From 060c2e3ac423f90a4bb9337b3f1a655c0d318e89 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 17 Aug 2023 12:18:25 +0800 Subject: [PATCH 0424/1295] chore: updates @typescript-eslint/eslint-plugin and @typescript-eslint/parser (#4977) Signed-off-by: KaKa --- package.json | 4 ++-- types/tsconfig.eslint.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index bdbb2c07161..a5935b461b8 100644 --- a/package.json +++ b/package.json @@ -137,8 +137,8 @@ "@sinclair/typebox": "^0.31.1", "@sinonjs/fake-timers": "^11.0.0", "@types/node": "^20.1.0", - "@typescript-eslint/eslint-plugin": "^5.59.2", - "@typescript-eslint/parser": "^5.59.2", + "@typescript-eslint/eslint-plugin": "^6.3.0", + "@typescript-eslint/parser": "^6.3.0", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", diff --git a/types/tsconfig.eslint.json b/types/tsconfig.eslint.json index a6dea4a89e9..f499b804d1f 100644 --- a/types/tsconfig.eslint.json +++ b/types/tsconfig.eslint.json @@ -7,7 +7,7 @@ "strict": true }, "include": [ - "/test/types/*.test-d.ts", - "/types/*.d.ts" + "../test/types/**/*.test-d.ts", + "./**/*.d.ts" ] } From e272ce5af0d4b17a736481a73afb66877eb81549 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Sun, 20 Aug 2023 00:26:00 +0800 Subject: [PATCH 0425/1295] chore: use group dependencies in dependabot (#4979) --- .github/dependabot.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5219073ac27..2ca8454fe4f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,3 +17,18 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + groups: + # Production dependencies without breaking changes + dependencies: + dependency-type: "production" + update-types: + - "minor" + - "patch" + # Production dependencies with breaking changes + dependencies-major: + dependency-type: "production" + update-types: + - "major" + # Development dependencies + dev-dependencies: + dependency-type: "development" From 6659342137f8f9fcd7e8a95706ce840b4222c412 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 21 Aug 2023 08:52:38 +0200 Subject: [PATCH 0426/1295] chore: fix ci bench (#4983) --- .github/workflows/benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index ea479a4ac77..81c1f6490e3 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -91,7 +91,7 @@ jobs: **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} remove-label: - if: "always()" + if: ${{ github.event.label.name == 'benchmark' }} needs: - benchmark - output-benchmark From b4f1450e2f65dde609ea49d70156e35ceb705a30 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Mon, 21 Aug 2023 17:05:14 +0800 Subject: [PATCH 0427/1295] fix: require.cache is undefined breaks SEA (#4982) --- lib/pluginUtils.js | 15 +++++++++------ test/internals/plugin.test.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 2bddd77baf1..45d58296bbc 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -25,12 +25,15 @@ function getPluginName (func) { // let's see if this is a file, and in that case use that // this is common for plugins const cache = require.cache - const keys = Object.keys(cache) - - for (let i = 0; i < keys.length; i++) { - const key = keys[i] - if (cache[key].exports === func) { - return key + // cache is undefined inside SEA + if (cache) { + const keys = Object.keys(cache) + + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (cache[key].exports === func) { + return key + } } } diff --git a/test/internals/plugin.test.js b/test/internals/plugin.test.js index 3d19c18d8e6..56ebfd00ee5 100644 --- a/test/internals/plugin.test.js +++ b/test/internals/plugin.test.js @@ -29,6 +29,21 @@ test('getPluginName should return plugin name if the file is cached', t => { t.equal(pluginName, expectedPluginName) }) +test('getPluginName should not throw when require.cache is undefined', t => { + t.plan(1) + function example () { + console.log('is just an example') + } + const cache = require.cache + require.cache = undefined + t.teardown(() => { + require.cache = cache + }) + const pluginName = pluginUtilsPublic.getPluginName(example) + + t.equal(pluginName, 'example') +}) + test("getMeta should return the object stored with the 'plugin-meta' symbol", t => { t.plan(1) From d3c55326fb1458c3a43ead02d21f452de379bc4a Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 21 Aug 2023 11:58:04 +0200 Subject: [PATCH 0428/1295] ci: remove sync next wf (#4985) --- .github/workflows/sync-next.yml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .github/workflows/sync-next.yml diff --git a/.github/workflows/sync-next.yml b/.github/workflows/sync-next.yml deleted file mode 100644 index d5be7663bf3..00000000000 --- a/.github/workflows/sync-next.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Sync next-branch -on: - workflow_dispatch: - schedule: - - cron: '0 8 * * 1' # every monday at 8 am - -permissions: - pull-requests: write - -jobs: - create-pull-request: - runs-on: ubuntu-latest - steps: - - uses: octokit/request-action@v2.x - id: create-pull-request - with: - route: POST /repos/{owner}/{repo}/pulls - owner: fastify - repo: fastify - title: Sync next-branch - body: Sync next-branch with latest changes of the main-branch - head: main - base: next - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 007a3077da9824aec437e7fa859a1772d3419cf8 Mon Sep 17 00:00:00 2001 From: Gustavo Nicolau <69599318+thenicolau@users.noreply.github.com> Date: Wed, 23 Aug 2023 02:42:52 -0200 Subject: [PATCH 0429/1295] feat(docs): remove mixing ES6 and commomjs in the example (#4990) --- docs/Guides/Getting-Started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index 6d1690bd0e9..2534b8cbb9a 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -277,7 +277,7 @@ async function dbConnector (fastify, options) { // Wrapping a plugin function with fastify-plugin exposes the decorators // and hooks, declared inside the plugin to the parent scope. -module.exports = fastifyPlugin(dbConnector) +export default fastifyPlugin(dbConnector) ``` From 529c2690edd13d7c4a8a5a42fddf69fe73e790d1 Mon Sep 17 00:00:00 2001 From: Mu-An Chiou Date: Wed, 23 Aug 2023 20:05:50 +0800 Subject: [PATCH 0430/1295] fix: errorHandler callback should utilize TypeProvider (#4989) * fix: errorHandler callback should utilize TypeProvider * add typings test * add missing import * fix copy paste error --------- Co-authored-by: Uzlopak --- test/types/instance.test-d.ts | 9 +++++++-- test/types/type-provider.test-d.ts | 27 ++++++++++++++++++++++++++- types/route.d.ts | 7 ++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 7ff26287bc2..598c5aebf61 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -6,7 +6,8 @@ import fastify, { FastifyInstance, RawReplyDefaultExpression, RawRequestDefaultExpression, - RawServerDefault + RawServerDefault, + RouteGenericInterface } from '../../fastify' import { HookHandlerDoneFunction } from '../../types/hooks' import { FastifyReply } from '../../types/reply' @@ -257,9 +258,13 @@ expectNotDeprecated(server.listen({ port: 3000, host: '::/0', ipv6Only: true }, expectAssignable(server.routing({} as RawRequestDefaultExpression, {} as RawReplyDefaultExpression)) -expectType(fastify().get('/', { +expectType(fastify().get('/', { handler: () => {}, errorHandler: (error, request, reply) => { + expectAssignable(error) + expectAssignable(request) + expectAssignable<{ contextKey: string }>(request.routeConfig) + expectAssignable(reply) expectAssignable(server.errorHandler(error, request, reply)) } })) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index 613c5c8edc3..f856c1088ad 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -3,7 +3,8 @@ import fastify, { HookHandlerDoneFunction, FastifyRequest, FastifyReply, - FastifyInstance + FastifyInstance, + FastifyError } from '../../fastify' import { expectAssignable, expectError, expectType } from 'tsd' import { IncomingHttpHeaders } from 'http' @@ -79,6 +80,14 @@ expectAssignable(server.withTypeProvider().get( y: Type.Number(), z: Type.Number() }) + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) } }, (req) => { @@ -108,6 +117,14 @@ expectAssignable(server.withTypeProvider().get( z: { type: 'boolean' } } } as const + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) } }, (req) => { @@ -135,6 +152,14 @@ expectAssignable(server.withTypeProvider().withTypeProvider { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) } }, (req) => { diff --git a/types/route.d.ts b/types/route.d.ts index 1ded2c6a731..aca35eafe0f 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -45,7 +45,12 @@ export interface RouteShorthandOptions< version?: string; constraints?: { [name: string]: any }, prefixTrailingSlash?: 'slash'|'no-slash'|'both'; - errorHandler?: (this: FastifyInstance, error: FastifyError, request: FastifyRequest, reply: FastifyReply) => void; + errorHandler?: ( + this: FastifyInstance, + error: FastifyError, + request: FastifyRequest, + reply: FastifyReply + ) => void; childLoggerFactory?: FastifyChildLoggerFactory; schemaErrorFormatter?: SchemaErrorFormatter; From 7270c007feda525cb4feb574ea310003ad0b7aa8 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Wed, 23 Aug 2023 14:29:26 +0200 Subject: [PATCH 0431/1295] types: add onRoute to ApplicationHookLookup (#4968) --- types/hooks.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index ed5dec17fdd..aaf5054ef17 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -630,7 +630,9 @@ export type ApplicationHookLookup = K extends 'onRegi ? onCloseHookHandler : K extends 'preClose' ? preCloseHookHandler - : never + : K extends 'onRoute' + ? onRouteHookHandler + : never export type ApplicationHookAsyncLookup = K extends 'onRegister' ? onRegisterHookHandler From 8974714cddce705c190f344de383618f9d8cdde1 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 26 Aug 2023 10:16:56 -0400 Subject: [PATCH 0432/1295] chore: make tests pass on ipv4 only machine (#4997) Signed-off-by: Matteo Collina --- test/custom-http-server.test.js | 3 ++- test/https/custom-https-server.test.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/custom-http-server.test.js b/test/custom-http-server.test.js index cb6c9367c32..bf9b02bb092 100644 --- a/test/custom-http-server.test.js +++ b/test/custom-http-server.test.js @@ -10,8 +10,9 @@ const dns = require('dns').promises test('Should support a custom http server', async t => { const localAddresses = await dns.lookup('localhost', { all: true }) + const minPlan = localAddresses.length - 1 || 1 - t.plan((localAddresses.length - 1) + 3) + t.plan(minPlan + 3) const serverFactory = (handler, opts) => { t.ok(opts.serverFactory, 'it is called once for localhost') diff --git a/test/https/custom-https-server.test.js b/test/https/custom-https-server.test.js index 84e5443b9a0..87694f9e67a 100644 --- a/test/https/custom-https-server.test.js +++ b/test/https/custom-https-server.test.js @@ -12,8 +12,9 @@ t.before(buildCertificate) test('Should support a custom https server', async t => { const localAddresses = await dns.lookup('localhost', { all: true }) + const minPlan = localAddresses.length - 1 || 1 - t.plan((localAddresses.length - 1) + 3) + t.plan(minPlan + 3) const serverFactory = (handler, opts) => { t.ok(opts.serverFactory, 'it is called once for localhost') From 889456f2c6c4ff9fbd907c9ba6cf3527808b4ac7 Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Sun, 27 Aug 2023 06:20:25 +1000 Subject: [PATCH 0433/1295] fix: type FastifyRequest id as a string (#4992) --- test/types/request.test-d.ts | 2 +- types/request.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 34b114a6e13..420bd2e528a 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -83,7 +83,7 @@ const getHandler: RouteHandler = function (request, _reply) { request.headers = {} expectType(request.query) - expectType(request.id) + expectType(request.id) expectType(request.log) expectType(request.socket) expectType(request.validationError) diff --git a/types/request.d.ts b/types/request.d.ts index bd75639fad9..b019bd3bab5 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -50,7 +50,7 @@ export interface FastifyRequest { - id: any; + id: string; params: RequestType['params']; // deferred inference raw: RawRequest; query: RequestType['query']; From 5b5e1b8ec76d4ac8ba472cb6653c17b0f8af6da9 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 27 Aug 2023 04:54:44 +0200 Subject: [PATCH 0434/1295] Bumped v4.22.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 60 +++++++++++++++++++++-------------------- package.json | 2 +- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/fastify.js b/fastify.js index 49cbb740689..d3434500acf 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.21.0' +const VERSION = '4.22.0' const Avvio = require('avvio') const http = require('http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index c723b484369..de1c3927319 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -16,41 +16,43 @@ ) { - function anonymous0 (input) { - // # + // # + function anonymous0 (input) { const obj = (input && typeof input.toJSON === 'function') ? input.toJSON() : input - let json = '{' - let addComma = false + + let addComma = false + let json = '{' + + if (obj["statusCode"] !== undefined) { + !addComma && (addComma = true) || (json += ',') + json += "\"statusCode\":" + json += serializer.asNumber(obj["statusCode"]) + } + + if (obj["code"] !== undefined) { + !addComma && (addComma = true) || (json += ',') + json += "\"code\":" + json += serializer.asString(obj["code"]) + } + + if (obj["error"] !== undefined) { + !addComma && (addComma = true) || (json += ',') + json += "\"error\":" + json += serializer.asString(obj["error"]) + } + + if (obj["message"] !== undefined) { + !addComma && (addComma = true) || (json += ',') + json += "\"message\":" + json += serializer.asString(obj["message"]) + } + + return json + '}' - if (obj["statusCode"] !== undefined) { - !addComma && (addComma = true) || (json += ',') - json += "\"statusCode\":" - json += serializer.asNumber(obj["statusCode"]) - } - - if (obj["code"] !== undefined) { - !addComma && (addComma = true) || (json += ',') - json += "\"code\":" - json += serializer.asString(obj["code"]) - } - - if (obj["error"] !== undefined) { - !addComma && (addComma = true) || (json += ',') - json += "\"error\":" - json += serializer.asString(obj["error"]) - } - - if (obj["message"] !== undefined) { - !addComma && (addComma = true) || (json += ',') - json += "\"message\":" - json += serializer.asString(obj["message"]) - } - - return json + '}' } const main = anonymous0 diff --git a/package.json b/package.json index a5935b461b8..5789e401bb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.21.0", + "version": "4.22.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From ad7ef969c942e2d2880f1ee6d0cc9f484ed4ef39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 14:43:56 +0000 Subject: [PATCH 0435/1295] chore: Bump the dev-dependencies group with 1 update (#5002) Bumps the dev-dependencies group with 1 update: [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2). - [Changelog](https://github.com/DavidAnson/markdownlint-cli2/blob/main/CHANGELOG.md) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.8.1...v0.9.2) --- updated-dependencies: - dependency-name: markdownlint-cli2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5789e401bb0..83c14a81382 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "joi": "^17.9.2", "json-schema-to-ts": "^2.9.1", "JSONStream": "^1.3.5", - "markdownlint-cli2": "^0.8.1", + "markdownlint-cli2": "^0.9.2", "proxyquire": "^2.1.3", "self-cert": "^2.0.0", "send": "^0.18.0", From dbac4e0aa8f932e53e36d60cd8359dc3d32403d9 Mon Sep 17 00:00:00 2001 From: beyazit <79628065+beyazit@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:52:20 +0300 Subject: [PATCH 0436/1295] fix: remove http version check (#4962) * make FastifySchemaValidationError.params wider (#4476) * fix: FastifySchemaValidationError.params could be unknown * refactor: use FastifySchemaValidationError instead of ValidationResult * fix: export ValidationResult for backword compatibility * test: add non-assignable test for AjvErrorObject * docs(ecosystem): add fastify-hashids (#4934) * fix: hasPlugin does not track parent plugins (#4929) Co-authored-by: Carlos Fuentes * docs: early hints plugin is fastify plugin (#4947) * docs: early hints plugin is fastify plugin * fix * Update docs/Guides/Ecosystem.md Co-authored-by: Frazer Smith --------- Co-authored-by: Frazer Smith * chore: add pull request title check (#4951) * chore: add pull request title check * Update .github/workflows/pull-request-title.yml Co-authored-by: Frazer Smith --------- Co-authored-by: Frazer Smith * chore: Bump @sinclair/typebox from 0.29.6 to 0.30.2 (#4952) Bumps [@sinclair/typebox](https://github.com/sinclairzx81/typebox) from 0.29.6 to 0.30.2. - [Commits](https://github.com/sinclairzx81/typebox/compare/0.29.6...0.30.2) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci: prove pr title check (#4953) * improve pr title check * Apply suggestions from code review * fix * fix * Update .github/workflows/pull-request-title.yml Co-authored-by: Luis Orbaiceta * Apply suggestions from code review --------- Co-authored-by: Luis Orbaiceta * ci: fix warnings in benchmark workflows (#4954) * improve pr title check * Apply suggestions from code review * fix * fix * ci: fix warnings in benchmark workflow * Update .github/workflows/benchmark.yml * Update .github/workflows/benchmark.yml * docs: fix removeAdditional comment (#4948) * docs(Validator Compiler): fix removeAdditional comment * Update docs/Reference/Validation-and-Serialization.md Co-authored-by: Uzlopak * fix: typo in comment * Update docs/Reference/Validation-and-Serialization.md Co-authored-by: Frazer Smith --------- Co-authored-by: Uzlopak Co-authored-by: Frazer Smith * fix: Try to fix parser benchmark workflow (#4956) * fix: infer correct hook handler (#4945) * fix: infer correct hook handler * Update test/types/hooks.test-d.ts * simplify * remove duplicate typing tests * remove duplicate test * remove unused import * fix * fix linting * fix: remove http version check * test: remove unsupported http version * remove obsolete documentation --------- Signed-off-by: dependabot[bot] Co-authored-by: cm-ayf Co-authored-by: Anderson Co-authored-by: Manuel Spigolon Co-authored-by: Carlos Fuentes Co-authored-by: Uzlopak Co-authored-by: Frazer Smith Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Luis Orbaiceta Co-authored-by: Mohammed Gomaa Co-authored-by: Igor Savin Co-authored-by: Cem --- docs/Reference/Routes.md | 22 ------------ fastify.js | 3 +- lib/route.js | 15 -------- lib/server.js | 51 ---------------------------- test/unsupported-httpversion.test.js | 31 ----------------- 5 files changed, 1 insertion(+), 121 deletions(-) delete mode 100644 test/unsupported-httpversion.test.js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index cc926cd4fbe..57787b7aa9a 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -826,25 +826,3 @@ const secret = { > } > }) > ``` - - -### ⚠ HTTP version check - -Fastify will check the HTTP version of every request, based on configuration -options ([http2](./Server.md#http2), [https](./Server.md#https), and -[serverFactory](./Server.md#serverfactory)), to determine if it matches one or -all of the > following versions: `2.0`, `1.1`, and `1.0`. If Fastify receives a -different HTTP version in the request it will return a `505 HTTP Version Not -Supported` error. - -| | 2.0 | 1.1 | 1.0 | skip | -|:------------------------:|:---:|:---:|:---:|:----:| -| http2 | ✓ | | | | -| http2 + https | ✓ | | | | -| http2 + https.allowHTTP1 | ✓ | ✓ | ✓ | | -| https | | ✓ | ✓ | | -| http | | ✓ | ✓ | | -| serverFactory | | | | ✓ | - - Note: The internal HTTP version check will be removed in the future when Node - implements [this feature](https://github.com/nodejs/node/issues/43115). diff --git a/fastify.js b/fastify.js index 2daa69bb05d..46f6fbf3a9e 100644 --- a/fastify.js +++ b/fastify.js @@ -31,7 +31,7 @@ const { kChildLoggerFactory } = require('./lib/symbols.js') -const { createServer, compileValidateHTTPVersion } = require('./lib/server') +const { createServer } = require('./lib/server') const Reply = require('./lib/reply') const Request = require('./lib/request') const Context = require('./lib/context.js') @@ -513,7 +513,6 @@ function fastify (options) { hasLogger, setupResponseListeners, throwIfAlreadyStarted, - validateHTTPVersion: compileValidateHTTPVersion(options), keepAliveConnections }) diff --git a/lib/route.js b/lib/route.js index 5714e587ecf..9c92795096a 100644 --- a/lib/route.js +++ b/lib/route.js @@ -66,7 +66,6 @@ function buildRouting (options) { let ignoreDuplicateSlashes let return503OnClosing let globalExposeHeadRoutes - let validateHTTPVersion let keepAliveConnections let closing = false @@ -83,7 +82,6 @@ function buildRouting (options) { hasLogger = fastifyArgs.hasLogger setupResponseListeners = fastifyArgs.setupResponseListeners throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted - validateHTTPVersion = fastifyArgs.validateHTTPVersion globalExposeHeadRoutes = options.exposeHeadRoutes genReqId = options.genReqId @@ -402,19 +400,6 @@ function buildRouting (options) { const childLogger = createChildLogger(context, logger, req, id, loggerOpts) childLogger[kDisableRequestLogging] = disableRequestLogging - // TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core. - if (!validateHTTPVersion(req.httpVersion)) { - childLogger.info({ res: { statusCode: 505 } }, 'request aborted - invalid HTTP version') - const message = '{"error":"HTTP Version Not Supported","message":"HTTP Version Not Supported","statusCode":505}' - const headers = { - 'Content-Type': 'application/json', - 'Content-Length': message.length - } - res.writeHead(505, headers) - res.end(message) - return - } - if (closing === true) { /* istanbul ignore next mac, windows */ if (req.httpVersionMajor !== 2) { diff --git a/lib/server.js b/lib/server.js index fd2afdb8898..5a9183f10ca 100644 --- a/lib/server.js +++ b/lib/server.js @@ -13,7 +13,6 @@ const { } = require('./errors') module.exports.createServer = createServer -module.exports.compileValidateHTTPVersion = compileValidateHTTPVersion function defaultResolveServerListeningText (address) { return `Server listening at ${address}` @@ -252,56 +251,6 @@ function listenPromise (server, listenOptions) { }) } -/** - * Creates a function that, based upon initial configuration, will - * verify that every incoming request conforms to allowed - * HTTP versions for the Fastify instance, e.g. a Fastify HTTP/1.1 - * server will not serve HTTP/2 requests upon the result of the - * verification function. - * - * @param {object} options fastify option - * @param {function} [options.serverFactory] If present, the - * validator function will skip all checks. - * @param {boolean} [options.http2 = false] If true, the validator - * function will allow HTTP/2 requests. - * @param {object} [options.https = null] https server options - * @param {boolean} [options.https.allowHTTP1] If true and use - * with options.http2 the validator function will allow HTTP/1 - * request to http2 server. - * - * @returns {function} HTTP version validator function. - */ -function compileValidateHTTPVersion (options) { - let bypass = false - // key-value map to store valid http version - const map = new Map() - if (options.serverFactory) { - // When serverFactory is passed, we cannot identify how to check http version reliably - // So, we should skip the http version check - bypass = true - } - if (options.http2) { - // HTTP2 must serve HTTP/2.0 - map.set('2.0', true) - if (options.https && options.https.allowHTTP1 === true) { - // HTTP2 with HTTPS.allowHTTP1 allow fallback to HTTP/1.1 and HTTP/1.0 - map.set('1.1', true) - map.set('1.0', true) - } - } else { - // HTTP must server HTTP/1.1 and HTTP/1.0 - map.set('1.1', true) - map.set('1.0', true) - } - // The compiled function here placed in one of the hottest path inside fastify - // the implementation here must be as performant as possible - return function validateHTTPVersion (httpVersion) { - // `bypass` skip the check when custom server factory provided - // `httpVersion in obj` check for the valid http version we should support - return bypass || map.has(httpVersion) - } -} - function getServerInstance (options, httpHandler) { let server = null // node@20 do not accepts options as boolean diff --git a/test/unsupported-httpversion.test.js b/test/unsupported-httpversion.test.js deleted file mode 100644 index 9d6b9b17919..00000000000 --- a/test/unsupported-httpversion.test.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict' - -const net = require('net') -const t = require('tap') -const Fastify = require('../fastify') - -t.test('Will return 505 HTTP error if HTTP version (2.0 when server is 1.1) is not supported', t => { - const fastify = Fastify() - - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', (req, reply) => { - reply.send({ hello: 'world' }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - const port = fastify.server.address().port - const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/2.0\r\nHost: example.com\r\n\r\n') - - client.once('data', data => { - t.match(data.toString(), /505 HTTP Version Not Supported/i) - client.end(() => { - t.end() - }) - }) - }) - }) -}) From 80007915c2beb796824ec96e89f3e14ba395015e Mon Sep 17 00:00:00 2001 From: Shane Date: Tue, 29 Aug 2023 15:13:28 -0400 Subject: [PATCH 0437/1295] docs(ecosystem): add fastify-rabbitmq to ecosystem.md (#5000) * New plugin rabbitmq (#1) * Update Ecosystem.md * added my plugin * chore: make tests pass on ipv4 only machine (#4997) Signed-off-by: Matteo Collina * fix: type FastifyRequest id as a string (#4992) * Bumped v4.22.0 Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Co-authored-by: Matteo Collina Co-authored-by: Sam Chung * fix --------- Signed-off-by: Matteo Collina Co-authored-by: Matteo Collina Co-authored-by: Sam Chung Co-authored-by: Uzlopak --- docs/Guides/Ecosystem.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 84be994540e..6aead13276d 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -520,6 +520,10 @@ middlewares into Fastify plugins - [`fastify-qs`](https://github.com/vanodevium/fastify-qs) A plugin for Fastify that adds support for parsing URL query parameters with [qs](https://github.com/ljharb/qs). +- [`fastify-rabbitmq`](https://github.com/Bugs5382/fastify-rabbitmq) Fastify + RabbitMQ plugin that uses + [node-amqp-connection-manager](https://github.com/jwalton/node-amqp-connection-manager) + plugin as a wrapper. - [`fastify-racing`](https://github.com/metcoder95/fastify-racing) Fastify's plugin that adds support to handle an aborted request asynchronous. - [`fastify-ravendb`](https://github.com/nearform/fastify-ravendb) RavenDB From ced4672bd64faeaa84b36c23536bf450c5c919d8 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 29 Aug 2023 20:15:52 +0100 Subject: [PATCH 0438/1295] docs(ecosystem): add `@fastify/throttle` to core list (#5004) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 6aead13276d..2da1638474c 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -121,6 +121,8 @@ section. - [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) Plugin for serving Swagger/OpenAPI documentation for Fastify, supporting dynamic generation. +- [`@fastify/throttle`](https://github.com/fastify/fastify-throttle) Plugin for +- throttling the download speed of a request. - [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) Fastify [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) From a250c394611d618edaa05c303e2e1b542152b470 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Wed, 30 Aug 2023 16:41:26 +0200 Subject: [PATCH 0439/1295] chore: typos (#5006) * fix: typos * fix: typos --- docs/Guides/Ecosystem.md | 2 +- docs/Guides/Serverless.md | 4 ++-- docs/Reference/Errors.md | 2 +- docs/Reference/HTTP2.md | 3 ++- docs/Reference/Hooks.md | 2 +- docs/Reference/Server.md | 2 +- lib/contentTypeParser.js | 4 ++-- lib/handleRequest.js | 6 +++--- lib/hooks.js | 11 ++++++---- lib/logger.js | 2 +- lib/reply.js | 33 +++++++++++++++++------------ lib/request.js | 4 ++-- lib/schemas.js | 2 +- lib/server.js | 6 +++--- lib/validation.js | 2 +- test/build/error-serializer.test.js | 4 ++-- 16 files changed, 49 insertions(+), 40 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 2da1638474c..5764f8abe5b 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -46,7 +46,7 @@ section. Plugin to deal with `diagnostics_channel` on Fastify - [`@fastify/early-hints`](https://github.com/fastify/fastify-early-hints) Plugin to add HTTP 103 feature based on [RFC - 8297](https://httpwg.org/specs/rfc8297.html). + 8297](https://datatracker.ietf.org/doc/html/rfc8297). - [`@fastify/elasticsearch`](https://github.com/fastify/fastify-elasticsearch) Plugin to share the same ES client. - [`@fastify/env`](https://github.com/fastify/fastify-env) Load and check diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 4c057aa6898..2dfe0d82ced 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -487,10 +487,10 @@ with your GitHub account. Create your first application and a static workspace: be careful to download the API key as an env file, e.g. `yourworkspace.txt`. -Then, you can easily deploy your application with the following commmand: +Then, you can easily deploy your application with the following command: ```bash -platformatic deploy --keys `yuourworkspace.txt` +platformatic deploy --keys `yourworkspace.txt` ``` Check out the [Full Guide](https://blog.platformatic.dev/how-to-migrate-a-fastify-app-to-platformatic-service) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 6648d825521..b95c3aac360 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -510,7 +510,7 @@ Impossible to load plugin because the parent (mapped directly from `avvio`) #### FST_ERR_PLUGIN_TIMEOUT -Plugin did not start in time. Default timeout (in millis): `10000` +Plugin did not start in time. Default timeout (in milliseconds): `10000` #### FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE diff --git a/docs/Reference/HTTP2.md b/docs/Reference/HTTP2.md index c67ec1b63b9..11da8427fc0 100644 --- a/docs/Reference/HTTP2.md +++ b/docs/Reference/HTTP2.md @@ -32,7 +32,8 @@ fastify.get('/', function (request, reply) { fastify.listen({ port: 3000 }) ``` -ALPN negotiation allows support for both HTTPS and HTTP/2 over the same socket. +[ALPN negotiation](https://datatracker.ietf.org/doc/html/rfc7301) allows +support for both HTTPS and HTTP/2 over the same socket. Node core `req` and `res` objects can be either [HTTP/1](https://nodejs.org/api/http.html) or [HTTP/2](https://nodejs.org/api/http2.html). _Fastify_ supports this out of the diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index e6343a67e40..72be5436974 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -80,7 +80,7 @@ hooks, and a stream with the current request payload. If it returns a value (via `return` or via the callback function), it must return a stream. -For instance, you can uncompress the request body: +For instance, you can decompress the request body: ```js fastify.addHook('preParsing', (request, reply, payload, done) => { diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index d4fdcf1b215..aacc502e913 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -480,7 +480,7 @@ is not equal to `/Foo`. When `false` then routes are case-insensitive. Please note that setting this option to `false` goes against -[RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1). +[RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1). By setting `caseSensitive` to `false`, all paths will be matched as lowercase, but the route parameters or wildcards will maintain their original letter diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 2733dc3b75d..7bc98e6564b 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -102,7 +102,7 @@ ContentTypeParser.prototype.getParser = function (contentType) { const parsed = safeParseContentType(contentType) // dummyContentType always the same object - // we can use === for the comparsion and return early + // we can use === for the comparison and return early if (parsed === defaultContentType) { return this.customParsers.get('') } @@ -399,7 +399,7 @@ function compareRegExpContentType (contentType, essenceMIMEType, regexp) { function ParserListItem (contentType) { this.name = contentType // we pre-calculate all the needed information - // before content-type comparsion + // before content-type comparison const parsed = safeParseContentType(contentType) this.isEssence = contentType.indexOf(';') === -1 // we should not allow empty string for parser list item diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 1c8c66216b5..a29bf1e3f29 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -1,7 +1,7 @@ 'use strict' const { validate: validateSchema } = require('./validation') -const { onPreValidationHookRunner, onPreHandlerHookRunner } = require('./hooks') +const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') const wrapThenable = require('./wrapThenable') const { kReplyIsError, @@ -65,7 +65,7 @@ function handleRequest (err, request, reply) { function handler (request, reply) { try { if (request[kRouteContext].preValidation !== null) { - onPreValidationHookRunner( + preValidationHookRunner( request[kRouteContext].preValidation, request, reply, @@ -111,7 +111,7 @@ function validationCompleted (request, reply, validationErr) { // preHandler hook if (request[kRouteContext].preHandler !== null) { - onPreHandlerHookRunner( + preHandlerHookRunner( request[kRouteContext].preHandler, request, reply, diff --git a/lib/hooks.js b/lib/hooks.js index abe4b7064e9..8e6d545ede3 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -217,8 +217,8 @@ function onResponseHookIterator (fn, request, reply, next) { } const onResponseHookRunner = hookRunnerGenerator(onResponseHookIterator) -const onPreValidationHookRunner = hookRunnerGenerator(hookIterator) -const onPreHandlerHookRunner = hookRunnerGenerator(hookIterator) +const preValidationHookRunner = hookRunnerGenerator(hookIterator) +const preHandlerHookRunner = hookRunnerGenerator(hookIterator) const onTimeoutHookRunner = hookRunnerGenerator(hookIterator) const onRequestHookRunner = hookRunnerGenerator(hookIterator) @@ -267,6 +267,8 @@ function onSendHookRunner (functions, request, reply, payload, cb) { next() } +const preSerializationHookRunner = onSendHookRunner + function preParsingHookRunner (functions, request, reply, cb) { let i = 0 @@ -360,11 +362,12 @@ module.exports = { preParsingHookRunner, onResponseHookRunner, onSendHookRunner, + preSerializationHookRunner, onRequestAbortHookRunner, hookIterator, hookRunnerApplication, - onPreHandlerHookRunner, - onPreValidationHookRunner, + preHandlerHookRunner, + preValidationHookRunner, onRequestHookRunner, onTimeoutHookRunner, lifecycleHooks, diff --git a/lib/logger.js b/lib/logger.js index 1c89af3b555..a18c9b18067 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -144,7 +144,7 @@ function createChildLogger (context, logger, req, reqId, loggerOpts) { } const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req) - // Optimisation: bypass validation if the factory is our own default factory + // Optimization: bypass validation if the factory is our own default factory if (context.childLoggerFactory !== defaultChildLoggerFactory) { validateLogger(child, true) // throw if the child is not a valid logger } diff --git a/lib/reply.js b/lib/reply.js index e9f631bcd7b..ed2a555b4a1 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -23,7 +23,12 @@ const { kOptions, kRouteContext } = require('./symbols.js') -const { onSendHookRunner, onResponseHookRunner, onPreHandlerHookRunner } = require('./hooks') +const { + onSendHookRunner, + onResponseHookRunner, + preHandlerHookRunner, + preSerializationHookRunner +} = require('./hooks') const internals = require('./handleRequest')[Symbol.for('internals')] const loggerUtils = require('./logger') @@ -166,7 +171,7 @@ Reply.prototype.send = function (payload) { if (this[kReplySerializer] !== null) { if (typeof payload !== 'string') { - preserializeHook(this, payload) + preSerializationHook(this, payload) return this } else { payload = this[kReplySerializer](payload) @@ -189,7 +194,7 @@ Reply.prototype.send = function (payload) { } } if (typeof payload !== 'string') { - preserializeHook(this, payload) + preSerializationHook(this, payload) return this } } @@ -233,7 +238,7 @@ Reply.prototype.header = function (key, value = '') { key = key.toLowerCase() if (this[kReplyHeaders][key] && key === 'set-cookie') { - // https://tools.ietf.org/html/rfc7230#section-3.2.2 + // https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.2 if (typeof this[kReplyHeaders][key] === 'string') { this[kReplyHeaders][key] = [this[kReplyHeaders][key]] } @@ -262,7 +267,7 @@ Reply.prototype.headers = function (headers) { } // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer#directives -// https://httpwg.org/specs/rfc7230.html#chunked.trailer.part +// https://datatracker.ietf.org/doc/html/rfc7230.html#chunked.trailer.part const INVALID_TRAILERS = new Set([ 'transfer-encoding', 'content-length', @@ -476,21 +481,21 @@ Reply.prototype.then = function (fulfilled, rejected) { }) } -function preserializeHook (reply, payload) { +function preSerializationHook (reply, payload) { if (reply[kRouteContext].preSerialization !== null) { - onSendHookRunner( + preSerializationHookRunner( reply[kRouteContext].preSerialization, reply.request, reply, payload, - preserializeHookEnd + preSerializationHookEnd ) } else { - preserializeHookEnd(null, reply.request, reply, payload) + preSerializationHookEnd(null, reply.request, reply, payload) } } -function preserializeHookEnd (err, request, reply, payload) { +function preSerializationHookEnd (err, request, reply, payload) { if (err != null) { onErrorHook(reply, err) return @@ -505,7 +510,7 @@ function preserializeHookEnd (err, request, reply, payload) { payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode, reply[kReplyHeaders]['content-type']) } } catch (e) { - wrapSeralizationError(e, reply) + wrapSerializationError(e, reply) onErrorHook(reply, e) return } @@ -513,7 +518,7 @@ function preserializeHookEnd (err, request, reply, payload) { onSendHook(reply, payload) } -function wrapSeralizationError (error, reply) { +function wrapSerializationError (error, reply) { error.serialization = reply[kRouteContext].config } @@ -571,7 +576,7 @@ function onSendEnd (reply, payload) { } if (payload === undefined || payload === null) { - // according to https://tools.ietf.org/html/rfc7230#section-3.3.2 + // according to https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 // we cannot send a content-length for 304 and 204, and all status code // < 200 // A sender MUST NOT send a Content-Length header field in any message @@ -846,7 +851,7 @@ function notFound (reply) { // preHandler hook if (reply[kRouteContext].preHandler !== null) { - onPreHandlerHookRunner( + preHandlerHookRunner( reply[kRouteContext].preHandler, reply.request, reply, diff --git a/lib/request.js b/lib/request.js index 6a42210f6d3..bf5ccc7b5b0 100644 --- a/lib/request.js +++ b/lib/request.js @@ -51,8 +51,8 @@ function getTrustProxyFn (tp) { } if (typeof tp === 'string') { // Support comma-separated tps - const vals = tp.split(',').map(it => it.trim()) - return proxyAddr.compile(vals) + const values = tp.split(',').map(it => it.trim()) + return proxyAddr.compile(values) } return proxyAddr.compile(tp) } diff --git a/lib/schemas.js b/lib/schemas.js index 077b977d410..95382009a73 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -23,7 +23,7 @@ Schemas.prototype.add = function (inputSchema) { : inputSchema ) - // devs can add schemas without $id, but with $def instead + // developers can add schemas without $id, but with $def instead const id = schema.$id if (!id) { throw new FST_ERR_SCH_MISSING_ID() diff --git a/lib/server.js b/lib/server.js index 57dda785d65..5cecf409e43 100644 --- a/lib/server.js +++ b/lib/server.js @@ -132,7 +132,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o const isMainServerListening = mainServer.listening && serverOpts.serverFactory let binding = 0 - let binded = 0 + let bound = 0 if (!isMainServerListening) { const primaryAddress = mainServer.address() for (const adr of addresses) { @@ -142,7 +142,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o host: adr.address, port: primaryAddress.port, cb: (_ignoreErr) => { - binded++ + bound++ /* istanbul ignore next: the else won't be taken unless listening fails */ if (!_ignoreErr) { @@ -172,7 +172,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o }) } - if (binded === binding) { + if (bound === binding) { // regardless of the error, we are done onListen() } diff --git a/lib/validation.js b/lib/validation.js index d5b12efc4f1..fe684dff306 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -69,7 +69,7 @@ function compileSchemasForValidation (context, compile, isCustom) { context[headersSchema] = compile({ schema: headers, method, url, httpPart: 'headers' }) } else if (headers) { // The header keys are case insensitive - // https://tools.ietf.org/html/rfc2616#section-4.2 + // https://datatracker.ietf.org/doc/html/rfc2616#section-4.2 const headersSchemaLowerCase = {} Object.keys(headers).forEach(k => { headersSchemaLowerCase[k] = headers[k] }) if (headersSchemaLowerCase.required instanceof Array) { diff --git a/test/build/error-serializer.test.js b/test/build/error-serializer.test.js index 8e8c8b12a2e..3882890f88b 100644 --- a/test/build/error-serializer.test.js +++ b/test/build/error-serializer.test.js @@ -23,9 +23,9 @@ test('check generated code syntax', async (t) => { t.equal(result[0].fatalErrorCount, 0) }) -const isPrebublish = !!process.env.PREPUBLISH +const isPrepublish = !!process.env.PREPUBLISH -test('ensure the current error serializer is latest', { skip: !isPrebublish }, async (t) => { +test('ensure the current error serializer is latest', { skip: !isPrepublish }, async (t) => { t.plan(1) const current = await fs.promises.readFile(path.resolve('lib/error-serializer.js')) From 9b3ca58097954c4df20b253906ed6697461e2486 Mon Sep 17 00:00:00 2001 From: Alessandro Bocci Date: Wed, 30 Aug 2023 23:40:35 +0200 Subject: [PATCH 0440/1295] docs: typo on catch statement inside Postgrator migration example (#5007) --- docs/Guides/Database.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md index 4a626026a06..dc692c8f02f 100644 --- a/docs/Guides/Database.md +++ b/docs/Guides/Database.md @@ -308,7 +308,7 @@ async function migrate() { process.exitCode = 0 } catch(err) { - console.error(error) + console.error(err) process.exitCode = 1 } From 498b17cae350d86426b23060b6d96fb31cc3faff Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 31 Aug 2023 14:12:10 -0400 Subject: [PATCH 0441/1295] fix: post async regression with empty body (#5008) --- lib/server.js | 2 +- lib/wrapThenable.js | 5 +---- test/post-empty-body.test.js | 31 +++++++++++++++++++++++++++++++ test/wrapThenable.test.js | 22 ---------------------- 4 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 test/post-empty-body.test.js diff --git a/lib/server.js b/lib/server.js index 5cecf409e43..baf6c3c468c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -200,7 +200,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o // to the secondary servers. It is valid only when the user is // listening on localhost const originUnref = mainServer.unref - /* istanbul ignore next */ + /* c8 ignore next 4 */ mainServer.unref = function () { originUnref.call(mainServer) mainServer.emit('unref') diff --git a/lib/wrapThenable.js b/lib/wrapThenable.js index 0d4e7ba812f..b746a62e920 100644 --- a/lib/wrapThenable.js +++ b/lib/wrapThenable.js @@ -18,10 +18,7 @@ function wrapThenable (thenable, reply) { // the request may be terminated during the reply. in this situation, // it require an extra checking of request.aborted to see whether // the request is killed by client. - // Most of the times aborted will be true when destroyed is true, - // however there is a race condition where the request is not - // aborted but only destroyed. - if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false && reply.request.raw.destroyed === false)) { + if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) { // we use a try-catch internally to avoid adding a catch to another // promise, increase promise perf by 10% try { diff --git a/test/post-empty-body.test.js b/test/post-empty-body.test.js new file mode 100644 index 00000000000..b3cd5547132 --- /dev/null +++ b/test/post-empty-body.test.js @@ -0,0 +1,31 @@ +'use strict' + +const { test } = require('tap') +const fastify = require('../') +const { request, setGlobalDispatcher, Agent } = require('undici') + +setGlobalDispatcher(new Agent({ + keepAliveTimeout: 10, + keepAliveMaxTimeout: 10 +})) + +test('post empty body', async t => { + const app = fastify() + t.teardown(app.close.bind(app)) + + app.post('/bug', async (request, reply) => { + }) + + await app.listen({ port: 0 }) + + const res = await request(`http://127.0.0.1:${app.server.address().port}/bug`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ foo: 'bar' }) + }) + + t.equal(res.statusCode, 200) + t.equal(await res.body.text(), '') +}) diff --git a/test/wrapThenable.test.js b/test/wrapThenable.test.js index df0ba334589..e953d29c16c 100644 --- a/test/wrapThenable.test.js +++ b/test/wrapThenable.test.js @@ -27,25 +27,3 @@ test('should reject immediately when reply[kReplyHijacked] is true', t => { const thenable = Promise.reject(new Error('Reply sent already')) wrapThenable(thenable, reply) }) - -test('should not send the payload if the raw socket was destroyed but not aborted', async t => { - const reply = { - sent: false, - raw: { - headersSent: false - }, - request: { - raw: { - aborted: false, - destroyed: true - } - }, - send () { - t.fail('should not send') - } - } - const thenable = Promise.resolve() - wrapThenable(thenable, reply) - - await thenable -}) From 8162801b5f4a66106cbbb165bc5744959e9abcef Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 31 Aug 2023 20:14:30 +0200 Subject: [PATCH 0442/1295] Bumped v4.22.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index d3434500acf..13caf6d4d91 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.22.0' +const VERSION = '4.22.1' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 83c14a81382..f9094882fae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.22.0", + "version": "4.22.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 3aee2e74de5c366f1e3b2e80e55441057f48c64b Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 1 Sep 2023 14:31:19 +0200 Subject: [PATCH 0443/1295] Bumped v4.22.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 13caf6d4d91..30c671349ab 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.22.1' +const VERSION = '4.22.2' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index f9094882fae..e4693710942 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.22.1", + "version": "4.22.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From af5e3293047724af938b7972a2338a1fbce84145 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Fri, 1 Sep 2023 14:40:14 +0200 Subject: [PATCH 0444/1295] test: reduce output of reqIdGenFactory.test.js (#5012) --- test/internals/reqIdGenFactory.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/internals/reqIdGenFactory.test.js b/test/internals/reqIdGenFactory.test.js index f06b05deea1..b11c7263c0c 100644 --- a/test/internals/reqIdGenFactory.test.js +++ b/test/internals/reqIdGenFactory.test.js @@ -4,12 +4,16 @@ const { test } = require('tap') const { reqIdGenFactory } = require('../../lib/reqIdGenFactory') test('should create incremental ids deterministically', t => { - t.plan(9999) + t.plan(1) const reqIdGen = reqIdGenFactory() for (let i = 1; i < 1e4; ++i) { - t.equal(reqIdGen(), 'req-' + i.toString(36)) + if (reqIdGen() !== 'req-' + i.toString(36)) { + t.fail() + break + } } + t.pass() }) test('should have prefix "req-"', t => { From 5d09eb4d645fc2ff1ea64fa7eaad37c2e49d72cf Mon Sep 17 00:00:00 2001 From: Brenden Inhelder <96445076+BrendenInhelder@users.noreply.github.com> Date: Fri, 1 Sep 2023 05:46:56 -0700 Subject: [PATCH 0445/1295] feat: Add onListen Hook (#4899) --- docs/Reference/Hooks.md | 29 + examples/hooks.js | 3 + fastify.d.ts | 4 +- fastify.js | 6 +- lib/hooks.js | 55 ++ lib/server.js | 28 +- test/hooks.on-listen.test.js | 1090 ++++++++++++++++++++++++++++++++++ test/listen.test.js | 14 + test/types/hooks.test-d.ts | 11 + types/hooks.d.ts | 57 +- types/instance.d.ts | 12 +- 11 files changed, 1284 insertions(+), 25 deletions(-) create mode 100644 test/hooks.on-listen.test.js diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 72be5436974..d2faf2d7aa0 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -24,6 +24,7 @@ are Request/Reply hooks and application hooks: - [Respond to a request from a hook](#respond-to-a-request-from-a-hook) - [Application Hooks](#application-hooks) - [onReady](#onready) + - [onListen](#onlisten) - [onClose](#onclose) - [preClose](#preclose) - [onRoute](#onroute) @@ -388,6 +389,7 @@ fastify.addHook('preHandler', async (request, reply) => { You can hook into the application-lifecycle as well. - [onReady](#onready) +- [onListen](#onlisten) - [onClose](#onclose) - [preClose](#preclose) - [onRoute](#onroute) @@ -416,6 +418,33 @@ fastify.addHook('onReady', async function () { }) ``` +### onListen + +Triggered when the server starts listening for requests. The hooks run one +after another. If a hook function causes an error, it is logged and +ignored, allowing the queue of hooks to continue. Hook functions accept one +argument: a callback, `done`, to be invoked after the hook function is +complete. Hook functions are invoked with `this` bound to the associated +Fastify instance. + +This is an alternative to `fastify.server.on('listening', () => {})`. + +```js +// callback style +fastify.addHook('onListen', function (done) { + // Some code + const err = null; + done(err) +}) + +// or async/await style +fastify.addHook('onListen', async function () { + // Some async code +}) +``` + +> **Note** +> This hook will not run when the server is started using `fastify.inject()` or `fastify.ready()` ### onClose diff --git a/examples/hooks.js b/examples/hooks.js index e087b3a66c3..cc54229296a 100644 --- a/examples/hooks.js +++ b/examples/hooks.js @@ -68,6 +68,9 @@ fastify .addHook('onRoute', function (routeOptions) { console.log('onRoute') }) + .addHook('onListen', async function () { + console.log('onListen') + }) .addHook('onClose', function (instance, done) { console.log('onClose') done() diff --git a/fastify.d.ts b/fastify.d.ts index b5cb47906b5..5c0eb90f998 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -12,7 +12,7 @@ import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequest import { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser' import { FastifyContext, FastifyContextConfig } from './types/context' import { FastifyErrorCodes } from './types/errors' -import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler } from './types/hooks' +import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler } from './types/hooks' import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel, Bindings, ChildLoggerOptions } from './types/logger' import { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' @@ -182,7 +182,7 @@ declare namespace fastify { FastifyError, // '@fastify/error' FastifySchema, FastifySchemaCompiler, // './types/schema' HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault, // './types/utils' - DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, // './types/hooks' + DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, // './types/hooks' FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory' FastifyTypeProvider, FastifyTypeProviderDefault, // './types/type-provider' FastifyErrorCodes, // './types/errors' diff --git a/fastify.js b/fastify.js index 30c671349ab..41fdff70e2d 100644 --- a/fastify.js +++ b/fastify.js @@ -620,7 +620,7 @@ function fastify (options) { if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) { throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() } - } else if (name === 'onReady') { + } else if (name === 'onReady' || name === 'onListen') { if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) { throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() } @@ -636,9 +636,7 @@ function fastify (options) { if (name === 'onClose') { this.onClose(fn) - } else if (name === 'onReady') { - this[kHooks].add(name, fn) - } else if (name === 'onRoute') { + } else if (name === 'onReady' || name === 'onListen' || name === 'onRoute') { this[kHooks].add(name, fn) } else { this.after((err, done) => { diff --git a/lib/hooks.js b/lib/hooks.js index 8e6d545ede3..6f7357ca54a 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -4,6 +4,7 @@ const applicationHooks = [ 'onRoute', 'onRegister', 'onReady', + 'onListen', 'preClose', 'onClose' ] @@ -48,6 +49,7 @@ function Hooks () { this.onRoute = [] this.onRegister = [] this.onReady = [] + this.onListen = [] this.onTimeout = [] this.onRequestAbort = [] this.preClose = [] @@ -83,6 +85,7 @@ function buildHooks (h) { hooks.onTimeout = h.onTimeout.slice() hooks.onRequestAbort = h.onRequestAbort.slice() hooks.onReady = [] + hooks.onListen = [] hooks.preClose = [] return hooks } @@ -174,6 +177,57 @@ function hookRunnerApplication (hookName, boot, server, cb) { } } +function onListenHookRunner (server) { + const hooks = server[kHooks].onListen + const hooksLen = hooks.length + + if (hooksLen === 0) { + return + } + + let i = 0 + let c = 0 + + next() + + function next (err) { + err && server.log.error(err) + + if ( + i === hooksLen + ) { + if (c < server[kChildren].length) { + const child = server[kChildren][c++] + onListenHookRunner(child) + } + return + } + + wrap(hooks[i++], server, next) + } + + async function wrap (fn, server, done) { + if (fn.length === 1) { + try { + fn.call(server, done) + } catch (e) { + done(e) + } + return + } + try { + const ret = fn.call(server) + if (ret && typeof ret.then === 'function') { + ret.then(done, done) + return + } + done() + } catch (error) { + done(error) + } + } +} + function hookRunnerGenerator (iterator) { return function hookRunner (functions, request, reply, cb) { let i = 0 @@ -366,6 +420,7 @@ module.exports = { onRequestAbortHookRunner, hookIterator, hookRunnerApplication, + onListenHookRunner, preHandlerHookRunner, preValidationHookRunner, onRequestHookRunner, diff --git a/lib/server.js b/lib/server.js index baf6c3c468c..29a5b7c3589 100644 --- a/lib/server.js +++ b/lib/server.js @@ -6,6 +6,7 @@ const dns = require('dns') const warnings = require('./warnings') const { kState, kOptions, kServerBindings } = require('./symbols') +const { onListenHookRunner } = require('./hooks') const { FST_ERR_HTTP2_INVALID_VERSION, FST_ERR_REOPENED_CLOSE_SERVER, @@ -85,8 +86,20 @@ function createServer (options, httpHandler) { multipleBindings.call(this, server, httpHandler, options, listenOptions, () => { this[kState].listening = true cb(null, address) + onListenHookRunner(this) }) } + } else { + listenOptions.cb = (err, address) => { + // the server did not start + if (err) { + cb(err, address) + return + } + this[kState].listening = true + cb(null, address) + onListenHookRunner(this) + } } // https://github.com/nodejs/node/issues/9390 @@ -97,17 +110,20 @@ function createServer (options, httpHandler) { if (cb === undefined) { const listening = listenPromise.call(this, server, listenOptions) /* istanbul ignore else */ - if (host === 'localhost') { - return listening.then(address => { - return new Promise((resolve, reject) => { + return listening.then(address => { + return new Promise((resolve, reject) => { + if (host === 'localhost') { multipleBindings.call(this, server, httpHandler, options, listenOptions, () => { this[kState].listening = true resolve(address) + onListenHookRunner(this) }) - }) + } else { + resolve(address) + onListenHookRunner(this) + } }) - } - return listening + }) } this.ready(listenCallback.call(this, server, listenOptions)) diff --git a/test/hooks.on-listen.test.js b/test/hooks.on-listen.test.js new file mode 100644 index 00000000000..5ee2fc58312 --- /dev/null +++ b/test/hooks.on-listen.test.js @@ -0,0 +1,1090 @@ +'use strict' + +const { test, before } = require('tap') +const Fastify = require('../fastify') +const fp = require('fastify-plugin') +const dns = require('dns').promises +const split = require('split2') + +let localhost + +before(async function () { + const lookup = await dns.lookup('localhost') + localhost = lookup.address +}) + +test('onListen should not be processed when .ready() is called', t => { + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.addHook('onListen', function (done) { + t.fail() + done() + }) + + fastify.ready(err => t.error(err)) +}) + +test('localhost onListen should be called in order', t => { + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 2, '2nd called in root') + done() + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost async onListen should be called in order', async t => { + t.plan(3) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', async function () { + t.equal(++order, 1, '1st async called in root') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 2, '2nd async called in root') + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) + t.equal(order, 2, 'the onListen hooks are awaited') +}) + +test('localhost onListen sync should log errors as warnings and continue /1', async t => { + t.plan(8) + let order = 0 + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.equal(order, 2) + t.pass('Logged Error Message') + } + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, '1st call') + t.pass('called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 2, '2nd call') + t.pass('called onListen error') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 3, '3rd call') + t.pass('onListen hooks continue after error') + done() + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen sync should log errors as warnings and continue /2', t => { + t.plan(7) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + let order = 0 + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.pass('Logged Error Message') + } + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, '1st call') + t.pass('called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 2, '2nd call') + t.pass('called onListen error') + done(new Error('FAIL ON LISTEN')) + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 3, '3rd call') + t.pass('onListen hooks continue after error') + done() + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen async should log errors as warnings and continue', async t => { + t.plan(4) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.pass('Logged Error Message') + } + }) + + fastify.addHook('onListen', async function () { + t.pass('called in root') + }) + + fastify.addHook('onListen', async function () { + t.pass('called onListen error') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', async function () { + t.pass('onListen hooks continue after error') + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost Register onListen hook after a plugin inside a plugin', t => { + t.plan(3) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + + done() + })) + + fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', t => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('Plugin Error')) { + t.pass('Logged Error Message') + } + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function () { + t.pass('called') + throw new Error('Plugin Error') + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function () { + t.pass('called') + throw new Error('Plugin Error') + }) + + instance.addHook('onListen', function () { + t.pass('called') + throw new Error('Plugin Error') + }) + + done() + })) + + fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen encapsulation should be called in order', t => { + t.plan(6) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, 'called in root') + t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.equal(++order, 2, 'called in childOne') + t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.equal(++order, 3, 'called in childTwo') + t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen encapsulation should be called in order and should log errors as warnings and continue', t => { + t.plan(7) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('Error in onListen hook of childTwo')) { + t.pass('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, 'called in root') + t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.equal(++order, 2, 'called in childOne') + t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.equal(++order, 3, 'called in childTwo') + t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + throw new Error('Error in onListen hook of childTwo') + }) + }) + }) + fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('non-localhost onListen should be called in order', t => { + t.plan(2) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 2, '2nd called in root') + done() + }) + fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost async onListen should be called in order', async t => { + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', async function () { + t.equal(++order, 1, '1st async called in root') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 2, '2nd async called in root') + }) + + await fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost sync onListen should log errors as warnings and continue', t => { + t.plan(4) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.pass('Logged Error Message') + } + }) + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1) + done() + }) + + fastify.addHook('onListen', function () { + t.equal(++order, 2) + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 3, 'should still run') + done() + }) + + fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost async onListen should log errors as warnings and continue', async t => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.pass('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', async function () { + t.equal(++order, 1) + t.pass('called in root') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 2, '2nd async failed in root') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 3) + t.pass('should still run') + }) + + await fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost Register onListen hook after a plugin inside a plugin', t => { + t.plan(3) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + + done() + })) + + fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', t => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('Plugin Error')) { + t.pass('Logged Error Message') + } + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function () { + t.pass('called') + throw new Error('Plugin Error') + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function () { + t.pass('called') + throw new Error('Plugin Error') + }) + + instance.addHook('onListen', function () { + t.pass('called') + throw new Error('Plugin Error') + }) + + done() + })) + + fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost onListen encapsulation should be called in order', t => { + t.plan(6) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, 'called in root') + t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.equal(++order, 2, 'called in childOne') + t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.equal(++order, 3, 'called in childTwo') + t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost onListen encapsulation should be called in order and should log errors as warnings and continue', t => { + t.plan(7) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('Error in onListen hook of childTwo')) { + t.pass('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, 'called in root') + + t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.equal(++order, 2, 'called in childOne') + t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.equal(++order, 3, 'called in childTwo') + t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + throw new Error('Error in onListen hook of childTwo') + }) + }) + }) + fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('onListen localhost should work in order with callback', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 2, '2nd called in root') + done() + }) + + fastify.listen({ port: 0 }, (err) => { + t.equal(fastify.server.address().address, localhost) + t.error(err) + }) +}) + +test('onListen localhost should work in order with callback in async', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', async function () { + t.equal(++order, 1, '1st called in root') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 2, '2nd called in root') + }) + + fastify.listen({ host: 'localhost', port: 0 }, (err) => { + t.equal(fastify.server.address().address, localhost) + t.error(err) + }) +}) + +test('onListen localhost sync with callback should log errors as warnings and continue', t => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.pass('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function () { + t.equal(++order, 2, 'error sync called in root') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 3, '1st called in root') + done() + }) + + fastify.listen({ port: 0 }, (err) => { + t.error(err) + t.equal(fastify.server.address().address, localhost) + }) +}) + +test('onListen localhost async with callback should log errors as warnings and continue', t => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.pass('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', async function () { + t.pass('1st called in root') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 1, 'error sync called in root') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', async function () { + t.pass('3rd called in root') + }) + + fastify.listen({ port: 0 }, (err) => { + t.error(err) + t.equal(fastify.server.address().address, localhost) + }) +}) + +test('Register onListen hook localhost with callback after a plugin inside a plugin', t => { + t.plan(5) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + + done() + })) + + fastify.listen({ port: 0 }, (err) => { + t.equal(fastify.server.address().address, localhost) + t.error(err) + }) +}) + +test('onListen localhost with callback encapsulation should be called in order', t => { + t.plan(8) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, 'called in root') + t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.equal(++order, 2, 'called in childOne') + t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.equal(++order, 3, 'called in childTwo') + t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + fastify.listen({ port: 0 }, (err) => { + t.equal(fastify.server.address().address, localhost) + t.error(err) + }) +}) + +test('onListen non-localhost should work in order with callback in sync', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 2, '2nd called in root') + done() + }) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.equal(fastify.server.address().address, '::1') + t.error(err) + }) +}) + +test('onListen non-localhost should work in order with callback in async', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', async function () { + t.equal(++order, 1, '1st called in root') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 2, '2nd called in root') + }) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.equal(fastify.server.address().address, '::1') + t.error(err) + }) +}) + +test('onListen non-localhost sync with callback should log errors as warnings and continue', t => { + t.plan(8) + + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.pass('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1) + t.pass('1st called in root') + done() + }) + + fastify.addHook('onListen', function () { + t.equal(++order, 2) + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', function (done) { + t.equal(++order, 3) + t.pass('3rd called in root') + done() + }) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.error(err) + t.equal(fastify.server.address().address, '::1') + }) +}) + +test('onListen non-localhost async with callback should log errors as warnings and continue', t => { + t.plan(8) + + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.pass('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', async function () { + t.equal(++order, 1) + t.pass('1st called in root') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 2, 'error sync called in root') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 3) + t.pass('3rd called in root') + }) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.error(err) + t.equal(fastify.server.address().address, '::1') + }) +}) + +test('Register onListen hook non-localhost with callback after a plugin inside a plugin', t => { + t.plan(5) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + + instance.addHook('onListen', function (done) { + t.pass('called') + done() + }) + + done() + })) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.equal(fastify.server.address().address, '::1') + t.error(err) + }) +}) + +test('onListen non-localhost with callback encapsulation should be called in order', t => { + t.plan(8) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.equal(++order, 1, 'called in root') + t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.equal(++order, 2, 'called in childOne') + t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.equal(++order, 3, 'called in childTwo') + t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.equal(fastify.server.address().address, '::1') + t.error(err) + }) +}) + +test('onListen sync should work if user does not pass done', t => { + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', function () { + t.equal(++order, 1, '1st called in root') + }) + + fastify.addHook('onListen', function () { + t.equal(++order, 2, '2nd called in root') + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('async onListen does not need to be awaited', t => { + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + let order = 0 + + fastify.addHook('onListen', async function () { + t.equal(++order, 1, '1st async called in root') + }) + + fastify.addHook('onListen', async function () { + t.equal(++order, 2, '2nd async called in root') + t.end() + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('onListen hooks do not block /1', t => { + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.addHook('onListen', function (done) { + setTimeout(done, 500) + }) + + const startDate = new Date() + fastify.listen({ + host: 'localhost', + port: 0 + }, err => { + t.error(err) + t.ok(new Date() - startDate < 50) + }) +}) + +test('onListen hooks do not block /2', async t => { + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.addHook('onListen', async function () { + await new Promise(resolve => setTimeout(resolve, 500)) + }) + + const startDate = new Date() + await fastify.listen({ + host: 'localhost', + port: 0 + }) + t.ok(new Date() - startDate < 50) +}) diff --git a/test/listen.test.js b/test/listen.test.js index 46847713d68..53a4a1304f5 100644 --- a/test/listen.test.js +++ b/test/listen.test.js @@ -156,6 +156,20 @@ test('double listen errors callback with (err, address)', t => { }) }) +test('nonlocalhost double listen errors callback with (err, address)', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ host: '::1', port: 0 }, (err, address) => { + t.equal(address, `http://${'[::1]'}:${fastify.server.address().port}`) + t.error(err) + fastify.listen({ host: '::1', port: fastify.server.address().port }, (err2, address2) => { + t.equal(address2, null) + t.ok(err2) + }) + }) +}) + test('listen twice on the same port', t => { t.plan(4) const fastify = Fastify() diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 7e5f2f80810..45bf7219725 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -142,6 +142,12 @@ server.addHook('onReady', function (done) { expectType(done(new Error())) }) +server.addHook('onListen', function (done) { + expectType(this) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) +}) + server.addHook('onClose', (instance, done) => { expectType(instance) expectAssignable<(err?: FastifyError) => void>(done) @@ -223,6 +229,10 @@ server.addHook('onReady', async function () { expectType(this) }) +server.addHook('onListen', async function () { + expectType(this) +}) + server.addHook('onClose', async (instance) => { expectType(instance) }) @@ -396,6 +406,7 @@ server.addHook('preClose', async function () { expectError(server.addHook('onClose', async function (instance, done) {})) expectError(server.addHook('onError', async function (request, reply, error, done) {})) expectError(server.addHook('onReady', async function (done) {})) +expectError(server.addHook('onListen', async function (done) {})) expectError(server.addHook('onRequest', async function (request, reply, done) {})) expectError(server.addHook('onRequestAbort', async function (request, done) {})) expectError(server.addHook('onResponse', async function (request, reply, done) {})) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index aaf5054ef17..d80974ff948 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -560,6 +560,34 @@ export interface onReadyAsyncHookHandler< this: FastifyInstance, ): Promise; } + +/** + * Triggered when fastify.listen() is invoked to start the server. It is useful when plugins need a "onListen" event, for example to run logics after the server start listening for requests. + */ +export interface onListenHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, +> { + ( + this: FastifyInstance, + done: HookHandlerDoneFunction + ): void; +} + +export interface onListenAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, +> { + ( + this: FastifyInstance, + ): Promise; +} /** * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need a "shutdown" event, for example to close an open connection to a database. */ @@ -619,6 +647,7 @@ export interface preCloseAsyncHookHandler< export type ApplicationHook = 'onRoute' | 'onRegister' | 'onReady' +| 'onListen' | 'onClose' | 'preClose' @@ -626,23 +655,27 @@ export type ApplicationHookLookup = K extends 'onRegi ? onRegisterHookHandler : K extends 'onReady' ? onReadyHookHandler - : K extends 'onClose' - ? onCloseHookHandler - : K extends 'preClose' - ? preCloseHookHandler - : K extends 'onRoute' - ? onRouteHookHandler - : never + : K extends 'onListen' + ? onListenHookHandler + : K extends 'onClose' + ? onCloseHookHandler + : K extends 'preClose' + ? preCloseHookHandler + : K extends 'onRoute' + ? onRouteHookHandler + : never export type ApplicationHookAsyncLookup = K extends 'onRegister' ? onRegisterHookHandler : K extends 'onReady' ? onReadyAsyncHookHandler - : K extends 'onClose' - ? onCloseAsyncHookHandler - : K extends 'preClose' - ? preCloseAsyncHookHandler - : never + : K extends 'onListen' + ? onListenAsyncHookHandler + : K extends 'onClose' + ? onCloseAsyncHookHandler + : K extends 'preClose' + ? preCloseAsyncHookHandler + : never export type HookLookup = K extends ApplicationHook ? ApplicationHookLookup diff --git a/types/instance.d.ts b/types/instance.d.ts index 34e8f59f8a3..cd0677ef97a 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -3,7 +3,7 @@ import { ConstraintStrategy, HTTPVersion } from 'find-my-way' import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' -import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, preCloseHookHandler, preCloseAsyncHookHandler, LifecycleHook, ApplicationHook, HookAsyncLookup, HookLookup } from './hooks' +import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, preCloseHookHandler, preCloseAsyncHookHandler, LifecycleHook, ApplicationHook, HookAsyncLookup, HookLookup } from './hooks' import { FastifyBaseLogger, FastifyChildLoggerFactory } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' @@ -406,6 +406,16 @@ export interface FastifyInstance< hook: Fn extends unknown ? Fn extends AsyncFunction ? onReadyAsyncHookHandler : onReadyHookHandler : Fn, ): FastifyInstance; + /** + * Triggered when fastify.listen() is invoked to start the server. It is useful when plugins need a "onListen" event, for example to run logics after the server start listening for requests. + */ + addHook< + Fn extends onListenHookHandler | onListenAsyncHookHandler = onListenHookHandler + >( + name: 'onListen', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onListenAsyncHookHandler : onListenHookHandler : Fn, + ): FastifyInstance; + /** * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need a "shutdown" event, for example to close an open connection to a database. */ From 6e2e24ecf579ba0de2e15c243bcdea607dacd47b Mon Sep 17 00:00:00 2001 From: Antoine Neff <9216777+antoineneff@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:47:04 +0200 Subject: [PATCH 0446/1295] docs(readme): avoid line breaks in documentation links (#5014) --- README.md | 59 ++++++++++++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index a20167d1c3c..15a6111de72 100644 --- a/README.md +++ b/README.md @@ -230,39 +230,32 @@ The overhead that each framework has on your application depends on your application, you should __always__ benchmark if performance matters to you. ## Documentation -* Getting - Started -* Guides -* Server -* Routes -* Encapsulation -* Logging -* Middleware -* Hooks -* Decorators -* Validation - and Serialization -* Fluent Schema -* Lifecycle -* Reply -* Request -* Errors -* Content Type - Parser -* Plugins -* Testing -* Benchmarking -* How to write a good - plugin -* Plugins Guide -* HTTP2 -* Long Term Support -* TypeScript and types - support -* Serverless -* Recommendations +* [__`Getting Started`__](./docs/Guides/Getting-Started.md) +* [__`Guides`__](./docs/Guides/Index.md) +* [__`Server`__](./docs/Reference/Server.md) +* [__`Routes`__](./docs/Reference/Routes.md) +* [__`Encapsulation`__](./docs/Reference/Encapsulation.md) +* [__`Logging`__](./docs/Reference/Logging.md) +* [__`Middleware`__](./docs/Reference/Middleware.md) +* [__`Hooks`__](./docs/Reference/Hooks.md) +* [__`Decorators`__](./docs/Reference/Decorators.md) +* [__`Validation and Serialization`__](./docs/Reference/Validation-and-Serialization.md) +* [__`Fluent Schema`__](./docs/Guides/Fluent-Schema.md) +* [__`Lifecycle`__](./docs/Reference/Lifecycle.md) +* [__`Reply`__](./docs/Reference/Reply.md) +* [__`Request`__](./docs/Reference/Request.md) +* [__`Errors`__](./docs/Reference/Errors.md) +* [__`Content Type Parser`__](./docs/Reference/ContentTypeParser.md) +* [__`Plugins`__](./docs/Reference/Plugins.md) +* [__`Testing`__](./docs/Guides/Testing.md) +* [__`Benchmarking`__](./docs/Guides/Benchmarking.md) +* [__`How to write a good plugin`__](./docs/Guides/Write-Plugin.md) +* [__`Plugins Guide`__](./docs/Guides/Plugins-Guide.md) +* [__`HTTP2`__](./docs/Reference/HTTP2.md) +* [__`Long Term Support`__](./docs/Reference/LTS.md) +* [__`TypeScript and types support`__](./docs/Reference/TypeScript.md) +* [__`Serverless`__](./docs/Guides/Serverless.md) +* [__`Recommendations`__](./docs/Guides/Recommendations.md) 中文文档[地址](https://github.com/fastify/docs-chinese/blob/HEAD/README.md) From 7053ea7756bf05d9b7049342045a6e79d9c0351c Mon Sep 17 00:00:00 2001 From: turnerran <55016001+turnerran@users.noreply.github.com> Date: Fri, 1 Sep 2023 22:16:34 +0200 Subject: [PATCH 0447/1295] chore(examples): added curly braces to conditions for consistency (#5015) * added curly braces to conditions for consistency * Added logging for starting server --- examples/asyncawait.js | 4 +++- examples/http2.js | 4 +++- examples/https.js | 4 +++- examples/parser.js | 4 +++- examples/shared-schema.js | 4 +++- examples/simple-stream.js | 4 +++- examples/simple.js | 4 +++- examples/typescript-server.ts | 1 + examples/use-plugin.js | 4 +++- 9 files changed, 25 insertions(+), 8 deletions(-) diff --git a/examples/asyncawait.js b/examples/asyncawait.js index de5d57f7d9f..676285398b7 100644 --- a/examples/asyncawait.js +++ b/examples/asyncawait.js @@ -32,5 +32,7 @@ fastify }) fastify.listen({ port: 3000 }, err => { - if (err) throw err + if (err) { + throw err + } }) diff --git a/examples/http2.js b/examples/http2.js index 26bbcfe2aa4..50a357f3a9c 100644 --- a/examples/http2.js +++ b/examples/http2.js @@ -33,5 +33,7 @@ fastify }) fastify.listen({ port: 3000 }, err => { - if (err) throw err + if (err) { + throw err + } }) diff --git a/examples/https.js b/examples/https.js index 33266980adc..3b0e58304e2 100644 --- a/examples/https.js +++ b/examples/https.js @@ -32,5 +32,7 @@ fastify }) fastify.listen({ port: 3000 }, err => { - if (err) throw err + if (err) { + throw err + } }) diff --git a/examples/parser.js b/examples/parser.js index 2f8d25f9499..5ea678efa9e 100644 --- a/examples/parser.js +++ b/examples/parser.js @@ -47,5 +47,7 @@ fastify }) fastify.listen({ port: 3000 }, err => { - if (err) throw err + if (err) { + throw err + } }) diff --git a/examples/shared-schema.js b/examples/shared-schema.js index c50b7908099..82adb889ead 100644 --- a/examples/shared-schema.js +++ b/examples/shared-schema.js @@ -32,5 +32,7 @@ fastify }) fastify.listen({ port: 3000 }, err => { - if (err) throw err + if (err) { + throw err + } }) diff --git a/examples/simple-stream.js b/examples/simple-stream.js index 8a354ae0505..3e3c445d46e 100644 --- a/examples/simple-stream.js +++ b/examples/simple-stream.js @@ -13,6 +13,8 @@ fastify }) fastify.listen({ port: 3000 }, (err, address) => { - if (err) throw err + if (err) { + throw err + } fastify.log.info(`server listening on ${address}`) }) diff --git a/examples/simple.js b/examples/simple.js index 3c7495a1dfd..f2aeb9b889f 100644 --- a/examples/simple.js +++ b/examples/simple.js @@ -26,5 +26,7 @@ fastify }) fastify.listen({ port: 3000 }, (err, address) => { - if (err) throw err + if (err) { + throw err + } }) diff --git a/examples/typescript-server.ts b/examples/typescript-server.ts index 5be41d7bb3b..f8d3c2d9671 100644 --- a/examples/typescript-server.ts +++ b/examples/typescript-server.ts @@ -75,4 +75,5 @@ server.listen({ port: 8080 }, (err, address) => { console.error(err); process.exit(1); } + console.log(`server listening on ${address}`) }); diff --git a/examples/use-plugin.js b/examples/use-plugin.js index fdcf26213c0..a8bf33c9d3f 100644 --- a/examples/use-plugin.js +++ b/examples/use-plugin.js @@ -15,7 +15,9 @@ const opts = { } } fastify.register(require('./plugin'), opts, function (err) { - if (err) throw err + if (err) { + throw err + } }) fastify.listen({ port: 3000 }, function (err) { From 59050e57458274cdaa038cd50a387cf28e18dd8a Mon Sep 17 00:00:00 2001 From: "Cesar V. Sampaio" <37849741+cesarvspr@users.noreply.github.com> Date: Mon, 4 Sep 2023 06:09:35 -0400 Subject: [PATCH 0448/1295] feat: access handler name add properties to req route options (#4470) * fix: 'Error' is not assignable to parameter of type 'null' * chore: add handler, schema and config to req.routeOptions * rollback not needed change * Update lib/request.js Co-authored-by: Manuel Spigolon * chore: use deepFreeze Signed-off-by: cesarvspr * chore: use Object.defineProperty Signed-off-by: cesarvspr * chore: use defineProperties Signed-off-by: cesarvspr * chore: add tests Signed-off-by: cesarvspr * fix: test count exceeds plan Signed-off-by: cesarvspr * fix: test count exceeds plan Signed-off-by: cesarvspr * fix: add TODO Signed-off-by: cesarvspr * chore: review improvements * fix: proper warning codes * Update test/request.deprecated.test.js Co-authored-by: Manuel Spigolon * Apply suggestions from code review * add documentation * add missing documentation * Update docs/Reference/Request.md Co-authored-by: Manuel Spigolon --------- Signed-off-by: cesarvspr Co-authored-by: Manuel Spigolon Co-authored-by: Frazer Smith Co-authored-by: Uzlopak Co-authored-by: Carlos Fuentes --- docs/Reference/Request.md | 18 ++++++++++------ lib/request.js | 15 +++++++++++++ lib/warnings.js | 8 +++++++ test/request.deprecated.test.js | 38 +++++++++++++++++++++++++++++++++ test/router-options.test.js | 11 +++++----- 5 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 test/request.deprecated.test.js diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 23ac9a3c77c..df4287d7bab 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -29,10 +29,10 @@ Request is a core Fastify object containing the following fields: - `url` - the URL of the incoming request - `originalUrl` - similar to `url`, this allows you to access the original `url` in case of internal re-routing -- `routerMethod` - the method defined for the router that is handling the - request -- `routerPath` - the path pattern defined for the router that is handling the - request +- `routerMethod` - Deprecated, use `request.routeOptions.method` instead. The + method defined for the router that is handling the request +- `routerPath` - Deprecated, use `request.routeOptions.config.url` instead. The + path pattern defined for the router that is handling the request - `is404` - true if request is being handled by 404 handler, false if it is not - `connection` - Deprecated, use `socket` instead. The underlying connection of the incoming request. @@ -40,17 +40,21 @@ Request is a core Fastify object containing the following fields: - `context` - A Fastify internal object. You should not use it directly or modify it. It is useful to access one special key: - `context.config` - The route [`config`](./Routes.md#routes-config) object. -- `routeSchema` - the scheme definition set for the router that is - handling the request -- `routeConfig` - The route [`config`](./Routes.md#routes-config) +- `routeSchema` - Deprecated, use `request.routeOptions.schema` instead. The + scheme definition set for the router that is handling the request +- `routeConfig` - Deprecated, use `request.routeOptions.config` instead. The + route [`config`](./Routes.md#routes-config) object. - `routeOptions` - The route [`option`](./Routes.md#routes-options) object - `bodyLimit` - either server limit or route limit + - `config` - the [`config`](./Routes.md#routes-config) object for this route - `method` - the http method for the route - `url` - the path of the URL to match this route + - `handler` - the handler for this route - `attachValidation` - attach `validationError` to request (if there is a schema defined) - `logLevel` - log level defined for this route + - `schema` - the JSON schemas definition for this route - `version` - a semver compatible string that defines the version of the endpoint - `exposeHeadRoute` - creates a sibling HEAD route for any GET routes - `prefixTrailingSlash` - string used to determine how to handle passing / diff --git a/lib/request.js b/lib/request.js index bf5ccc7b5b0..bafd7ba897e 100644 --- a/lib/request.js +++ b/lib/request.js @@ -172,6 +172,7 @@ Object.defineProperties(Request.prototype, { }, routerPath: { get () { + warning.emit('FSTDEP017') return this[kRouteContext].config?.url } }, @@ -189,23 +190,37 @@ Object.defineProperties(Request.prototype, { logLevel: context.logLevel, exposeHeadRoute: context.exposeHeadRoute, prefixTrailingSlash: context.prefixTrailingSlash, + handler: context.handler, version } + + Object.defineProperties(options, { + config: { + get: () => context.config + }, + schema: { + get: () => context.schema + } + }) + return Object.freeze(options) } }, routerMethod: { get () { + warning.emit('FSTDEP018') return this[kRouteContext].config?.method } }, routeConfig: { get () { + warning.emit('FSTDEP016') return this[kRouteContext][kPublicRouteContext]?.config } }, routeSchema: { get () { + warning.emit('FSTDEP015') return this[kRouteContext][kPublicRouteContext].schema } }, diff --git a/lib/warnings.js b/lib/warnings.js index eb9e653a1cb..5b7862313b6 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -27,6 +27,14 @@ warning.create('FastifyDeprecation', 'FSTDEP013', 'Direct return of "trailers" f 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.') +warning.create('FastifyDeprecation', 'FSTDEP015', 'You are accessing the deprecated "request.routeSchema" property. Use "request.routeOptions.schema" instead. Property "req.routeSchema" will be removed in `fastify@5`.') + +warning.create('FastifyDeprecation', 'FSTDEP016', 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.') + +warning.create('FastifyDeprecation', 'FSTDEP017', 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.config.url" instead. Property "req.routerPath" will be removed in `fastify@5`.') + +warning.create('FastifyDeprecation', 'FSTDEP018', 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.config.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.') + warning.create('FastifyWarning', 'FSTWRN001', 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', { unlimited: true }) module.exports = warning diff --git a/test/request.deprecated.test.js b/test/request.deprecated.test.js new file mode 100644 index 00000000000..0a5bcdf1317 --- /dev/null +++ b/test/request.deprecated.test.js @@ -0,0 +1,38 @@ +'use strict' + +// Tests for some deprecated `request.*` keys. This file should be +// removed when the deprecation is complete. + +process.removeAllListeners('warning') + +const test = require('tap').test +const Fastify = require('../') + +test('Should expose router options via getters on request and reply', t => { + t.plan(7) + const fastify = Fastify() + const expectedSchema = { + params: { + id: { type: 'integer' } + } + } + + fastify.get('/test/:id', { + schema: expectedSchema + }, (req, reply) => { + t.equal(req.routeConfig.url, '/test/:id') + t.equal(req.routeConfig.method, 'GET') + t.same(req.routeSchema, expectedSchema) + t.equal(req.routerPath, '/test/:id') + t.equal(req.routerMethod, 'GET') + reply.send() + }) + + fastify.inject({ + method: 'GET', + url: '/test/123456789' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) +}) diff --git a/test/router-options.test.js b/test/router-options.test.js index da78fafa2df..3d2943a0c8b 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -92,7 +92,7 @@ test('Should honor maxParamLength option', t => { }) test('Should expose router options via getters on request and reply', t => { - t.plan(10) + t.plan(9) const fastify = Fastify() const expectedSchema = { params: { @@ -105,11 +105,10 @@ test('Should expose router options via getters on request and reply', t => { }, (req, reply) => { t.equal(reply.context.config.url, '/test/:id') t.equal(reply.context.config.method, 'GET') - t.equal(req.routeConfig.url, '/test/:id') - t.equal(req.routeConfig.method, 'GET') - t.same(req.routeSchema, expectedSchema) - t.equal(req.routerPath, '/test/:id') - t.equal(req.routerMethod, 'GET') + t.same(req.routeOptions.schema, expectedSchema) + t.equal(typeof req.routeOptions.handler, 'function') + t.equal(req.routeOptions.config.url, '/test/:id') + t.equal(req.routeOptions.config.method, 'GET') t.equal(req.is404, false) reply.send({ hello: 'world' }) }) From 07d8bb91f4c19363cc5db2f2e0af0a6b1d7a56b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:58:01 +0000 Subject: [PATCH 0449/1295] chore: Bump the dev-dependencies group with 1 update (#5018) Bumps the dev-dependencies group with 1 update: [tsd](https://github.com/SamVerschueren/tsd). - [Release notes](https://github.com/SamVerschueren/tsd/releases) - [Commits](https://github.com/SamVerschueren/tsd/compare/v0.28.1...v0.29.0) --- updated-dependencies: - dependency-name: tsd dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4693710942..d6516c90630 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "split2": "^4.2.0", "standard": "^17.0.0", "tap": "^16.3.4", - "tsd": "^0.28.1", + "tsd": "^0.29.0", "typescript": "^5.0.4", "undici": "^5.22.0", "vary": "^1.1.2", From 3d10495200d4e0c837001e2a4bee4aa2fbda559a Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 4 Sep 2023 17:49:15 +0200 Subject: [PATCH 0450/1295] docs: minor improvements (#5019) --- docs/Guides/Ecosystem.md | 2 ++ docs/Guides/Getting-Started.md | 9 ++++---- docs/Guides/Migration-Guide-V4.md | 33 ++++++++++++++++------------- docs/Reference/ContentTypeParser.md | 1 - 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 5764f8abe5b..b75c391d6b2 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -671,7 +671,9 @@ middlewares into Fastify plugins and lightweight Sequelize plugin for Fastify. - [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple and updated Typeorm plugin for use with Fastify. + #### [Community Tools](#community-tools) + - [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows) Reusable workflows for use in the Fastify plugin - [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index 2534b8cbb9a..d8104f702dc 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -14,11 +14,12 @@ Let's start! Install with npm: -``` +```sh npm i fastify ``` + Install with yarn: -``` +```sh yarn add fastify ``` @@ -208,7 +209,7 @@ Let's rewrite the above example with a database connection. First, install `fastify-plugin` and `@fastify/mongodb`: -``` +```sh npm i fastify-plugin @fastify/mongodb ``` @@ -550,7 +551,7 @@ Fastify also has CLI integration thanks to First, install `fastify-cli`: -``` +```sh npm i fastify-cli ``` diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 2eee6bffabf..9900bbedf31 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -184,25 +184,28 @@ strict mode: use allowUnionTypes to allow union type keyword at "#/properties/im ``` As such, schemas like below will need to be changed from: -``` -type: 'object', -properties: { - api_key: { type: 'string' }, - image: { type: ['object', 'array'] } - } +```js +{ + type: 'object', + properties: { + api_key: { type: 'string' }, + image: { type: ['object', 'array'] } + } } ``` Into: -``` -type: 'object', -properties: { - api_key: { type: 'string' }, - image: { - anyOf: [ - { type: 'array' }, - { type: 'object' } - ] +```js +{ + type: 'object', + properties: { + api_key: { type: 'string' }, + image: { + anyOf: [ + { type: 'array' }, + { type: 'object' } + ] + } } } ``` diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index 133853b88c0..c995299348f 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -97,7 +97,6 @@ if (!fastify.hasContentTypeParser('application/jsoff')){ } ``` -======= #### removeContentTypeParser With `removeContentTypeParser` a single or an array of content types can be From 4986bbfb26aac300b59031b5e3caefe62c14801a Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Wed, 6 Sep 2023 12:18:21 +0300 Subject: [PATCH 0451/1295] chore(deps): replace tiny-lru with toad-cache (#4668) Co-authored-by: Uzlopak Co-authored-by: Manuel Spigolon Co-authored-by: Carlos Fuentes --- lib/contentTypeParser.js | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 7bc98e6564b..0ab755a758e 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -1,7 +1,7 @@ 'use strict' const { AsyncResource } = require('async_hooks') -const lru = require('tiny-lru').lru +const { Fifo } = require('toad-cache') const { safeParse: safeParseContentType, defaultContentType } = require('fast-content-type-parse') const secureJson = require('secure-json-parse') const { @@ -36,7 +36,7 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser)) this.parserList = [new ParserListItem('application/json'), new ParserListItem('text/plain')] this.parserRegExpList = [] - this.cache = lru(100) + this.cache = new Fifo(100) } ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { @@ -136,7 +136,7 @@ ContentTypeParser.prototype.removeAll = function () { this.customParsers = new Map() this.parserRegExpList = [] this.parserList = [] - this.cache = lru(100) + this.cache = new Fifo(100) } ContentTypeParser.prototype.remove = function (contentType) { diff --git a/package.json b/package.json index d6516c90630..8db9411f896 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,7 @@ "rfdc": "^1.3.0", "secure-json-parse": "^2.5.0", "semver": "^7.5.0", - "tiny-lru": "^11.0.1" + "toad-cache": "^3.2.0" }, "standard": { "ignore": [ From 1cbd81d446cb869e6671f04cebca8c7946756ab2 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Wed, 6 Sep 2023 14:34:14 +0200 Subject: [PATCH 0452/1295] Update Ecosystem.md (#5021) --- docs/Guides/Ecosystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index b75c391d6b2..c80bfbc96cf 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -122,7 +122,7 @@ section. serving Swagger/OpenAPI documentation for Fastify, supporting dynamic generation. - [`@fastify/throttle`](https://github.com/fastify/fastify-throttle) Plugin for -- throttling the download speed of a request. + throttling the download speed of a request. - [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) Fastify [type provider](https://www.fastify.io/docs/latest/Reference/Type-Providers/) From 9cd5ec9d551ee03223149dade433ae53ca32019c Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 10 Sep 2023 11:29:25 +0100 Subject: [PATCH 0453/1295] perf: use `node:` prefix to bypass require.cache call for builtins (#5026) --- .github/scripts/lint-ecosystem.js | 6 ++-- build/build-error-serializer.js | 4 +-- build/build-validation.js | 4 +-- build/sync-version.js | 4 +-- docs/Guides/Database.md | 2 +- docs/Guides/Delay-Accepting-Requests.md | 4 +-- docs/Reference/HTTP2.md | 8 ++--- docs/Reference/Hooks.md | 2 +- docs/Reference/Middleware.md | 2 +- docs/Reference/Reply.md | 14 ++++----- docs/Reference/Server.md | 4 +-- examples/benchmark/parser.js | 2 +- examples/http2.js | 4 +-- examples/https.js | 4 +-- examples/parser.js | 2 +- examples/simple-stream.js | 2 +- fastify.js | 4 +-- lib/contentTypeParser.js | 2 +- lib/error-handler.js | 2 +- lib/pluginUtils.js | 2 +- lib/reply.js | 2 +- lib/server.js | 8 ++--- test/404s.test.js | 2 +- test/als.test.js | 2 +- test/async-await.test.js | 2 +- test/bodyLimit.test.js | 2 +- test/build/error-serializer.test.js | 4 +-- test/build/version.test.js | 4 +-- test/bundler/webpack/webpack.config.js | 2 +- test/client-timeout.test.js | 2 +- test/close.test.js | 4 +-- test/connectionTimeout.test.js | 2 +- test/custom-http-server.test.js | 4 +-- test/custom-parser.0.test.js | 2 +- test/custom-querystring-parser.test.js | 2 +- test/diagnostics-channel.test.js | 6 ++-- test/fastify-instance.test.js | 2 +- test/genReqId.test.js | 2 +- test/helper.js | 4 +-- test/hooks-async.test.js | 4 +-- test/hooks.on-listen.test.js | 2 +- test/hooks.on-ready.test.js | 2 +- test/hooks.test.js | 8 ++--- test/http2/closing.test.js | 6 ++-- test/http2/missing-http2-module.test.js | 2 +- test/https/custom-https-server.test.js | 4 +-- test/inject.test.js | 6 ++-- test/internals/contentTypeParser.test.js | 2 +- test/internals/errors.test.js | 4 +-- test/internals/initialConfig.test.js | 2 +- test/internals/logger.test.js | 2 +- test/internals/reply.test.js | 14 ++++----- test/internals/server.test.js | 8 ++--- test/keepAliveTimeout.test.js | 2 +- test/listen.deprecated.test.js | 2 +- test/listen.test.js | 10 +++--- test/maxRequestsPerSocket.test.js | 2 +- test/plugin.name.display.js | 2 +- test/reply-error.test.js | 8 ++--- test/reply-trailers.test.js | 6 ++-- test/request-error.test.js | 2 +- test/requestTimeout.test.js | 2 +- test/route-hooks.test.js | 2 +- test/route.test.js | 2 +- test/serial/logger.0.test.js | 4 +-- test/serial/logger.1.test.js | 10 +++--- test/skip-reply-send.test.js | 2 +- test/stream.test.js | 40 ++++++++++++------------ test/trust-proxy.test.js | 2 +- test/unsupported-httpversion.test.js | 2 +- test/upgrade.test.js | 6 ++-- test/versioned-routes.test.js | 2 +- 72 files changed, 155 insertions(+), 155 deletions(-) diff --git a/.github/scripts/lint-ecosystem.js b/.github/scripts/lint-ecosystem.js index 033c5a4c51a..946d432e0ec 100644 --- a/.github/scripts/lint-ecosystem.js +++ b/.github/scripts/lint-ecosystem.js @@ -1,8 +1,8 @@ 'use strict' -const path = require('path') -const fs = require('fs') -const readline = require('readline') +const path = require('node:path') +const fs = require('node:fs') +const readline = require('node:readline') const basePathEcosystemDocFile = path.join('docs', 'Guides', 'Ecosystem.md') const ecosystemDocFile = path.join(__dirname, '..', '..', basePathEcosystemDocFile) diff --git a/build/build-error-serializer.js b/build/build-error-serializer.js index d666bd38ea1..95f8c3fd16e 100644 --- a/build/build-error-serializer.js +++ b/build/build-error-serializer.js @@ -2,8 +2,8 @@ 'use strict' const FJS = require('fast-json-stringify') -const path = require('path') -const fs = require('fs') +const path = require('node:path') +const fs = require('node:fs') const code = FJS({ type: 'object', diff --git a/build/build-validation.js b/build/build-validation.js index 2ed914f69b1..0bd2a5b1c6c 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -2,8 +2,8 @@ const AjvStandaloneCompiler = require('@fastify/ajv-compiler/standalone') const { _ } = require('ajv') -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const factory = AjvStandaloneCompiler({ readMode: false, diff --git a/build/sync-version.js b/build/sync-version.js index 5d00f216ed9..b26cced3834 100644 --- a/build/sync-version.js +++ b/build/sync-version.js @@ -1,7 +1,7 @@ 'use strict' -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') // package.json:version -> fastify.js:VERSION const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8')) diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md index dc692c8f02f..7b597a9e1ba 100644 --- a/docs/Guides/Database.md +++ b/docs/Guides/Database.md @@ -275,7 +275,7 @@ CREATE TABLE IF NOT EXISTS users ( ```javascript const pg = require('pg') const Postgrator = require('postgrator') -const path = require('path') +const path = require('node:path') async function migrate() { const client = new pg.Client({ diff --git a/docs/Guides/Delay-Accepting-Requests.md b/docs/Guides/Delay-Accepting-Requests.md index 9a45ec26db5..d2134c53af7 100644 --- a/docs/Guides/Delay-Accepting-Requests.md +++ b/docs/Guides/Delay-Accepting-Requests.md @@ -135,7 +135,7 @@ follows: ```js const { fetch } = require('undici') -const { setTimeout } = require('timers/promises') +const { setTimeout } = require('node:timers/promises') const MAGIC_KEY = '12345' @@ -249,7 +249,7 @@ server.listen({ port: '1234' }) ```js const { fetch } = require('undici') -const { setTimeout } = require('timers/promises') +const { setTimeout } = require('node:timers/promises') const MAGIC_KEY = '12345' diff --git a/docs/Reference/HTTP2.md b/docs/Reference/HTTP2.md index 11da8427fc0..69c7cbfb026 100644 --- a/docs/Reference/HTTP2.md +++ b/docs/Reference/HTTP2.md @@ -15,8 +15,8 @@ HTTP2 is supported in all modern browsers __only over a secure connection__: ```js 'use strict' -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const fastify = require('fastify')({ http2: true, https: { @@ -42,8 +42,8 @@ box: ```js 'use strict' -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const fastify = require('fastify')({ http2: true, https: { diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index d2faf2d7aa0..51c5b4aa4e3 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -840,7 +840,7 @@ tools first" fashion. ```js const tracer = /* retrieved from elsewhere in the package */ -const dc = require('diagnostics_channel') +const dc = require('node:diagnostics_channel') const channel = dc.channel('fastify.initialization') const spans = new WeakMap() diff --git a/docs/Reference/Middleware.md b/docs/Reference/Middleware.md index 51d77a97214..8664aa6fe3b 100644 --- a/docs/Reference/Middleware.md +++ b/docs/Reference/Middleware.md @@ -54,7 +54,7 @@ the first parameter to `use` and you are done! `/user/:id/comments`) and wildcards are not supported in multiple paths.* ```js -const path = require('path') +const path = require('node:path') const serveStatic = require('serve-static') // Single path diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 7ce8274e38d..0bb0cac6c9c 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -242,7 +242,7 @@ reply.trailer('server-timing', function() { return 'db;dur=53, app;dur=47.2' }) -const { createHash } = require('crypto') +const { createHash } = require('node:crypto') // trailer function also receive two argument // @param {object} reply fastify reply // @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent @@ -668,7 +668,7 @@ fastify.get('/json', options, function (request, reply) { `'application/octet-stream'`. ```js fastify.get('/streams', function (request, reply) { - const fs = require('fs') + const fs = require('node:fs') const stream = fs.createReadStream('some-file', 'utf8') reply.header('Content-Type', 'application/octet-stream') reply.send(stream) @@ -677,7 +677,7 @@ fastify.get('/streams', function (request, reply) { When using async-await you will need to return or await the reply object: ```js fastify.get('/streams', async function (request, reply) { - const fs = require('fs') + const fs = require('node:fs') const stream = fs.createReadStream('some-file', 'utf8') reply.header('Content-Type', 'application/octet-stream') return reply.send(stream) @@ -690,7 +690,7 @@ fastify.get('/streams', async function (request, reply) { If you are sending a buffer and you have not set a `'Content-Type'` header, *send* will set it to `'application/octet-stream'`. ```js -const fs = require('fs') +const fs = require('node:fs') fastify.get('/streams', function (request, reply) { fs.readFile('some-file', (err, fileBuffer) => { reply.send(err || fileBuffer) @@ -700,7 +700,7 @@ fastify.get('/streams', function (request, reply) { When using async-await you will need to return or await the reply object: ```js -const fs = require('fs') +const fs = require('node:fs') fastify.get('/streams', async function (request, reply) { fs.readFile('some-file', (err, fileBuffer) => { reply.send(err || fileBuffer) @@ -715,7 +715,7 @@ fastify.get('/streams', async function (request, reply) { `send` manages TypedArray and sets the `'Content-Type'=application/octet-stream'` header if not already set. ```js -const fs = require('fs') +const fs = require('node:fs') fastify.get('/streams', function (request, reply) { const typedArray = new Uint16Array(10) reply.send(typedArray) @@ -842,7 +842,7 @@ Fastify natively handles promises and supports async-await. *Note that in the following examples we are not using reply.send.* ```js -const { promisify } = require('util') +const { promisify } = require('node:util') const delay = promisify(setTimeout) fastify.get('/promises', options, function (request, reply) { diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index aacc502e913..b5d910c2357 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -635,7 +635,7 @@ You can also use Fastify's default parser but change some handling behavior, like the example below for case insensitive keys and values: ```js -const querystring = require('querystring') +const querystring = require('node:querystring') const fastify = require('fastify')({ querystringParser: str => querystring.parse(str.toLowerCase()) }) @@ -1892,7 +1892,7 @@ The properties that can currently be exposed are: - http2SessionTimeout ```js -const { readFileSync } = require('fs') +const { readFileSync } = require('node:fs') const Fastify = require('fastify') const fastify = Fastify({ diff --git a/examples/benchmark/parser.js b/examples/benchmark/parser.js index 54109722505..665c8a832e6 100644 --- a/examples/benchmark/parser.js +++ b/examples/benchmark/parser.js @@ -5,7 +5,7 @@ const fastify = require('../../fastify')({ }) const jsonParser = require('fast-json-body') -const querystring = require('querystring') +const querystring = require('node:querystring') // Handled by fastify // curl -X POST -d '{"hello":"world"}' -H'Content-type: application/json' http://localhost:3000/ diff --git a/examples/http2.js b/examples/http2.js index 50a357f3a9c..e1a1bd0d2f6 100644 --- a/examples/http2.js +++ b/examples/http2.js @@ -1,7 +1,7 @@ 'use strict' -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const fastify = require('../fastify')({ http2: true, https: { diff --git a/examples/https.js b/examples/https.js index 3b0e58304e2..2255552d4cf 100644 --- a/examples/https.js +++ b/examples/https.js @@ -1,7 +1,7 @@ 'use strict' -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const fastify = require('../fastify')({ https: { key: fs.readFileSync(path.join(__dirname, '../test/https/fastify.key')), diff --git a/examples/parser.js b/examples/parser.js index 5ea678efa9e..2dd50b07c65 100644 --- a/examples/parser.js +++ b/examples/parser.js @@ -2,7 +2,7 @@ const fastify = require('../fastify')({ logger: true }) const jsonParser = require('fast-json-body') -const querystring = require('querystring') +const querystring = require('node:querystring') // Handled by fastify // curl -X POST -d '{"hello":"world"}' -H'Content-type: application/json' http://localhost:3000/ diff --git a/examples/simple-stream.js b/examples/simple-stream.js index 3e3c445d46e..2b7fe851b07 100644 --- a/examples/simple-stream.js +++ b/examples/simple-stream.js @@ -4,7 +4,7 @@ const fastify = require('../fastify')({ logger: false }) -const Readable = require('stream').Readable +const Readable = require('node:stream').Readable fastify .get('/', function (req, reply) { diff --git a/fastify.js b/fastify.js index 41fdff70e2d..819984cf3e2 100644 --- a/fastify.js +++ b/fastify.js @@ -3,7 +3,7 @@ const VERSION = '4.22.2' const Avvio = require('avvio') -const http = require('http') +const http = require('node:http') let lightMyRequest const { @@ -496,7 +496,7 @@ function fastify (options) { server.on('clientError', options.clientErrorHandler.bind(fastify)) try { - const dc = require('diagnostics_channel') + const dc = require('node:diagnostics_channel') const initChannel = dc.channel('fastify.initialization') if (initChannel.hasSubscribers) { initChannel.publish({ fastify }) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 0ab755a758e..ec21048a30c 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -1,6 +1,6 @@ 'use strict' -const { AsyncResource } = require('async_hooks') +const { AsyncResource } = require('node:async_hooks') const { Fifo } = require('toad-cache') const { safeParse: safeParseContentType, defaultContentType } = require('fast-content-type-parse') const secureJson = require('secure-json-parse') diff --git a/lib/error-handler.js b/lib/error-handler.js index 52f4862b859..32cee751072 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -1,6 +1,6 @@ 'use strict' -const statusCodes = require('http').STATUS_CODES +const statusCodes = require('node:http').STATUS_CODES const wrapThenable = require('./wrapThenable') const { kReplyHeaders, diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 45d58296bbc..70be3fb0ee6 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -1,7 +1,7 @@ 'use strict' const semver = require('semver') -const assert = require('assert') +const assert = require('node:assert') const kRegisteredPlugins = Symbol.for('registered-plugin') const { kTestInternals diff --git a/lib/reply.js b/lib/reply.js index ed2a555b4a1..b744b1a8863 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -1,6 +1,6 @@ 'use strict' -const eos = require('stream').finished +const eos = require('node:stream').finished const { kFourOhFourContext, diff --git a/lib/server.js b/lib/server.js index 29a5b7c3589..6f2d06514cc 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,8 +1,8 @@ 'use strict' -const http = require('http') -const https = require('https') -const dns = require('dns') +const http = require('node:http') +const https = require('node:https') +const dns = require('node:dns') const warnings = require('./warnings') const { kState, kOptions, kServerBindings } = require('./symbols') @@ -426,7 +426,7 @@ function logServerAddress (server, listenTextResolver) { function http2 () { try { - return require('http2') + return require('node:http2') } catch (err) { throw new FST_ERR_HTTP2_INVALID_VERSION() } diff --git a/test/404s.test.js b/test/404s.test.js index 030bb14a4b1..4a2cc2d0b3d 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -1031,7 +1031,7 @@ test('setNotFoundHandler should not suppress duplicated routes checking', t => { test('log debug for 404', t => { t.plan(1) - const Writable = require('stream').Writable + const Writable = require('node:stream').Writable const logStream = new Writable() logStream.logs = [] diff --git a/test/als.test.js b/test/als.test.js index 7bc9ba06185..37102b11a9f 100644 --- a/test/als.test.js +++ b/test/als.test.js @@ -1,6 +1,6 @@ 'use strict' -const { AsyncLocalStorage } = require('async_hooks') +const { AsyncLocalStorage } = require('node:async_hooks') const t = require('tap') const Fastify = require('..') const sget = require('simple-get').concat diff --git a/test/async-await.test.js b/test/async-await.test.js index 6024cdd8cca..75ba89d130f 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -6,7 +6,7 @@ const sget = require('simple-get').concat const Fastify = require('..') const split = require('split2') const pino = require('pino') -const statusCodes = require('http').STATUS_CODES +const statusCodes = require('node:http').STATUS_CODES const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) const opts = { diff --git a/test/bodyLimit.test.js b/test/bodyLimit.test.js index 0936b7b7b25..a5eb0cafd6f 100644 --- a/test/bodyLimit.test.js +++ b/test/bodyLimit.test.js @@ -2,7 +2,7 @@ const Fastify = require('..') const sget = require('simple-get').concat -const zlib = require('zlib') +const zlib = require('node:zlib') const t = require('tap') const test = t.test diff --git a/test/build/error-serializer.test.js b/test/build/error-serializer.test.js index 3882890f88b..4db32ae8127 100644 --- a/test/build/error-serializer.test.js +++ b/test/build/error-serializer.test.js @@ -2,8 +2,8 @@ const t = require('tap') const test = t.test -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const { code } = require('../../build/build-error-serializer') diff --git a/test/build/version.test.js b/test/build/version.test.js index 84b474ca1fc..fe6403cb7e1 100644 --- a/test/build/version.test.js +++ b/test/build/version.test.js @@ -1,7 +1,7 @@ 'use strict' -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const t = require('tap') const test = t.test const fastify = require('../../fastify')() diff --git a/test/bundler/webpack/webpack.config.js b/test/bundler/webpack/webpack.config.js index e1470690a0b..dee222c2422 100644 --- a/test/bundler/webpack/webpack.config.js +++ b/test/bundler/webpack/webpack.config.js @@ -1,4 +1,4 @@ -const path = require('path') +const path = require('node:path') module.exports = { entry: { success: './src/index.js', failPlugin: './src/fail-plugin-version.js' }, diff --git a/test/client-timeout.test.js b/test/client-timeout.test.js index bc08792f7eb..b88aee8a918 100644 --- a/test/client-timeout.test.js +++ b/test/client-timeout.test.js @@ -2,7 +2,7 @@ const { test } = require('tap') const fastify = require('..')({ requestTimeout: 5, http: { connectionsCheckingInterval: 1000 } }) -const { connect } = require('net') +const { connect } = require('node:net') test('requestTimeout should return 408', t => { t.plan(1) diff --git a/test/close.test.js b/test/close.test.js index 9e6bfb8c43b..336fda2b278 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -1,7 +1,7 @@ 'use strict' -const net = require('net') -const http = require('http') +const net = require('node:net') +const http = require('node:http') const { test } = require('tap') const Fastify = require('..') const { Client } = require('undici') diff --git a/test/connectionTimeout.test.js b/test/connectionTimeout.test.js index ad1ea44a410..6c2c0192097 100644 --- a/test/connectionTimeout.test.js +++ b/test/connectionTimeout.test.js @@ -1,7 +1,7 @@ 'use strict' const Fastify = require('..') -const http = require('http') +const http = require('node:http') const t = require('tap') const test = t.test diff --git a/test/custom-http-server.test.js b/test/custom-http-server.test.js index bf9b02bb092..923773b7e1a 100644 --- a/test/custom-http-server.test.js +++ b/test/custom-http-server.test.js @@ -3,10 +3,10 @@ const t = require('tap') const test = t.test const Fastify = require('..') -const http = require('http') +const http = require('node:http') const { FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE } = require('../lib/errors') const sget = require('simple-get').concat -const dns = require('dns').promises +const dns = require('node:dns').promises test('Should support a custom http server', async t => { const localAddresses = await dns.lookup('localhost', { all: true }) diff --git a/test/custom-parser.0.test.js b/test/custom-parser.0.test.js index d8b10835491..8a683d618dd 100644 --- a/test/custom-parser.0.test.js +++ b/test/custom-parser.0.test.js @@ -1,6 +1,6 @@ 'use strict' -const fs = require('fs') +const fs = require('node:fs') const t = require('tap') const test = t.test const sget = require('simple-get').concat diff --git a/test/custom-querystring-parser.test.js b/test/custom-querystring-parser.test.js index 80280d99c50..fd88dfe7f18 100644 --- a/test/custom-querystring-parser.test.js +++ b/test/custom-querystring-parser.test.js @@ -2,7 +2,7 @@ const t = require('tap') const test = t.test -const querystring = require('querystring') +const querystring = require('node:querystring') const sget = require('simple-get').concat const Fastify = require('..') diff --git a/test/diagnostics-channel.test.js b/test/diagnostics-channel.test.js index 9b8e301cea6..dc2ed8b4b40 100644 --- a/test/diagnostics-channel.test.js +++ b/test/diagnostics-channel.test.js @@ -24,7 +24,7 @@ test('diagnostics_channel when present and subscribers', t => { } const fastify = proxyquire('../fastify', { - diagnostics_channel: dc + 'node:diagnostics_channel': dc })() t.equal(fastifyInHook, fastify) }) @@ -46,7 +46,7 @@ test('diagnostics_channel when present and no subscribers', t => { } proxyquire('../fastify', { - diagnostics_channel: dc + 'node:diagnostics_channel': dc })() }) @@ -55,7 +55,7 @@ test('diagnostics_channel when not present', t => { t.doesNotThrow(() => { proxyquire('../fastify', { - diagnostics_channel: null + 'node:diagnostics_channel': null })() }) }) diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index 4435da0245e..e7c0cc65163 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -3,7 +3,7 @@ const t = require('tap') const test = t.test const Fastify = require('..') -const os = require('os') +const os = require('node:os') const { kOptions, diff --git a/test/genReqId.test.js b/test/genReqId.test.js index 7dce2b8ac87..102aaa72cd6 100644 --- a/test/genReqId.test.js +++ b/test/genReqId.test.js @@ -1,6 +1,6 @@ 'use strict' -const { Readable } = require('stream') +const { Readable } = require('node:stream') const { test } = require('tap') const Fastify = require('..') diff --git a/test/helper.js b/test/helper.js index 9451adbbdbc..4763357b8a1 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,8 +1,8 @@ 'use strict' const sget = require('simple-get').concat -const dns = require('dns').promises -const stream = require('stream') +const dns = require('node:dns').promises +const stream = require('node:stream') const symbols = require('../lib/symbols') /** diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index a992024e155..7f24099b46b 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -1,11 +1,11 @@ 'use strict' -const { Readable } = require('stream') +const { Readable } = require('node:stream') const t = require('tap') const test = t.test const sget = require('simple-get').concat const Fastify = require('../fastify') -const fs = require('fs') +const fs = require('node:fs') const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) process.removeAllListeners('warning') diff --git a/test/hooks.on-listen.test.js b/test/hooks.on-listen.test.js index 5ee2fc58312..38079770661 100644 --- a/test/hooks.on-listen.test.js +++ b/test/hooks.on-listen.test.js @@ -3,7 +3,7 @@ const { test, before } = require('tap') const Fastify = require('../fastify') const fp = require('fastify-plugin') -const dns = require('dns').promises +const dns = require('node:dns').promises const split = require('split2') let localhost diff --git a/test/hooks.on-ready.test.js b/test/hooks.on-ready.test.js index 08bbb0d0786..16553ba7b5a 100644 --- a/test/hooks.on-ready.test.js +++ b/test/hooks.on-ready.test.js @@ -2,7 +2,7 @@ const t = require('tap') const Fastify = require('../fastify') -const immediate = require('util').promisify(setImmediate) +const immediate = require('node:util').promisify(setImmediate) t.test('onReady should be called in order', t => { t.plan(7) diff --git a/test/hooks.test.js b/test/hooks.test.js index 6666bd8a63e..9b527d4abee 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3,16 +3,16 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const stream = require('stream') +const stream = require('node:stream') const Fastify = require('..') const fp = require('fastify-plugin') -const fs = require('fs') +const fs = require('node:fs') const split = require('split2') const symbols = require('../lib/symbols.js') const payload = { hello: 'world' } const proxyquire = require('proxyquire') -const { promisify } = require('util') -const { connect } = require('net') +const { promisify } = require('node:util') +const { connect } = require('node:net') const sleep = promisify(setTimeout) diff --git a/test/http2/closing.test.js b/test/http2/closing.test.js index 37a8af693e2..12184e488b5 100644 --- a/test/http2/closing.test.js +++ b/test/http2/closing.test.js @@ -2,10 +2,10 @@ const t = require('tap') const Fastify = require('../..') -const http2 = require('http2') -const { promisify } = require('util') +const http2 = require('node:http2') +const { promisify } = require('node:util') const connect = promisify(http2.connect) -const { once } = require('events') +const { once } = require('node:events') const { buildCertificate } = require('../build-certificate') t.before(buildCertificate) diff --git a/test/http2/missing-http2-module.test.js b/test/http2/missing-http2-module.test.js index ab9a5caf779..c2e3564418c 100644 --- a/test/http2/missing-http2-module.test.js +++ b/test/http2/missing-http2-module.test.js @@ -3,7 +3,7 @@ const t = require('tap') const test = t.test const proxyquire = require('proxyquire') -const server = proxyquire('../../lib/server', { http2: null }) +const server = proxyquire('../../lib/server', { 'node:http2': null }) const Fastify = proxyquire('../..', { './lib/server.js': server }) test('should throw when http2 module cannot be found', t => { diff --git a/test/https/custom-https-server.test.js b/test/https/custom-https-server.test.js index 87694f9e67a..51a58404a96 100644 --- a/test/https/custom-https-server.test.js +++ b/test/https/custom-https-server.test.js @@ -3,9 +3,9 @@ const t = require('tap') const test = t.test const Fastify = require('../..') -const https = require('https') +const https = require('node:https') const sget = require('simple-get').concat -const dns = require('dns').promises +const dns = require('node:dns').promises const { buildCertificate } = require('../build-certificate') t.before(buildCertificate) diff --git a/test/inject.test.js b/test/inject.test.js index 23810e23615..1158d8ecb7e 100644 --- a/test/inject.test.js +++ b/test/inject.test.js @@ -2,11 +2,11 @@ const t = require('tap') const test = t.test -const Stream = require('stream') -const util = require('util') +const Stream = require('node:stream') +const util = require('node:util') const Fastify = require('..') const FormData = require('form-data') -const { Readable } = require('stream') +const { Readable } = require('node:stream') test('inject should exist', t => { t.plan(2) diff --git a/test/internals/contentTypeParser.test.js b/test/internals/contentTypeParser.test.js index 54547132bbd..a7a0956fbce 100644 --- a/test/internals/contentTypeParser.test.js +++ b/test/internals/contentTypeParser.test.js @@ -3,7 +3,7 @@ const t = require('tap') const proxyquire = require('proxyquire') const test = t.test -const { Readable } = require('stream') +const { Readable } = require('node:stream') const { kTestInternals, kRouteContext } = require('../../lib/symbols') const Request = require('../../lib/request') const Reply = require('../../lib/reply') diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 7a20abd6fca..fe207bf8879 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -2,8 +2,8 @@ const { test } = require('tap') const errors = require('../../lib/errors') -const { readFileSync } = require('fs') -const { resolve } = require('path') +const { readFileSync } = require('node:fs') +const { resolve } = require('node:path') test('should expose 78 errors', t => { t.plan(1) diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index e58219f37ca..37380d2eee7 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -3,7 +3,7 @@ const { test, before } = require('tap') const Fastify = require('../..') const helper = require('../helper') -const http = require('http') +const http = require('node:http') const pino = require('pino') const split = require('split2') const deepClone = require('rfdc')({ circles: true, proto: false }) diff --git a/test/internals/logger.test.js b/test/internals/logger.test.js index dec0863fe6b..ae66daed183 100644 --- a/test/internals/logger.test.js +++ b/test/internals/logger.test.js @@ -97,7 +97,7 @@ Queue.prototype.run = function run () { test('The logger should error if both stream and file destination are given', t => { t.plan(2) - const stream = require('stream').Writable + const stream = require('node:stream').Writable try { Fastify({ diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 1fc300bd57d..805da05b083 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -3,11 +3,11 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const http = require('http') +const http = require('node:http') const NotFound = require('http-errors').NotFound const Reply = require('../../lib/reply') const Fastify = require('../..') -const { Readable, Writable } = require('stream') +const { Readable, Writable } = require('node:stream') const { kReplyErrorHandlerCalled, kReplyHeaders, @@ -16,8 +16,8 @@ const { kReplySerializerDefault, kRouteContext } = require('../../lib/symbols') -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const warning = require('../../lib/warnings') const agent = new http.Agent({ keepAlive: false }) @@ -262,7 +262,7 @@ test('within an instance', t => { reply.code(200) reply.type('text/plain') reply.serializer(function (body) { - return require('querystring').stringify(body) + return require('node:querystring').stringify(body) }) reply.send({ hello: 'world!' }) }) @@ -612,8 +612,8 @@ test('stream using reply.raw.writeHead should return customize headers', t => { t.plan(6) const fastify = Fastify() - const fs = require('fs') - const path = require('path') + const fs = require('node:fs') + const path = require('node:path') const streamPath = path.join(__dirname, '..', '..', 'package.json') const stream = fs.createReadStream(streamPath) diff --git a/test/internals/server.test.js b/test/internals/server.test.js index 941e8815eaf..615604ed98d 100644 --- a/test/internals/server.test.js +++ b/test/internals/server.test.js @@ -20,7 +20,7 @@ test('start listening', async t => { test('DNS errors does not stop the main server on localhost - promise interface', async t => { const { createServer } = proxyquire('../../lib/server', { - dns: { + 'node:dns': { lookup: (hostname, options, cb) => { cb(new Error('DNS error')) } @@ -35,7 +35,7 @@ test('DNS errors does not stop the main server on localhost - promise interface' test('DNS errors does not stop the main server on localhost - callback interface', t => { t.plan(2) const { createServer } = proxyquire('../../lib/server', { - dns: { + 'node:dns': { lookup: (hostname, options, cb) => { cb(new Error('DNS error')) } @@ -52,7 +52,7 @@ test('DNS errors does not stop the main server on localhost - callback interface test('DNS returns empty binding', t => { t.plan(2) const { createServer } = proxyquire('../../lib/server', { - dns: { + 'node:dns': { lookup: (hostname, options, cb) => { cb(null, []) } @@ -69,7 +69,7 @@ test('DNS returns empty binding', t => { test('DNS returns more than two binding', t => { t.plan(2) const { createServer } = proxyquire('../../lib/server', { - dns: { + 'node:dns': { lookup: (hostname, options, cb) => { cb(null, [ { address: '::1', family: 6 }, diff --git a/test/keepAliveTimeout.test.js b/test/keepAliveTimeout.test.js index f322f04aee6..16eab7d9011 100644 --- a/test/keepAliveTimeout.test.js +++ b/test/keepAliveTimeout.test.js @@ -1,7 +1,7 @@ 'use strict' const Fastify = require('..') -const http = require('http') +const http = require('node:http') const t = require('tap') const test = t.test diff --git a/test/listen.deprecated.test.js b/test/listen.deprecated.test.js index 01f6cc7aa94..8f15dfc47b7 100644 --- a/test/listen.deprecated.test.js +++ b/test/listen.deprecated.test.js @@ -4,7 +4,7 @@ // removed when the deprecation is complete. const { test, before } = require('tap') -const dns = require('dns').promises +const dns = require('node:dns').promises const Fastify = require('..') let localhost diff --git a/test/listen.test.js b/test/listen.test.js index 53a4a1304f5..32fa1e1d0d9 100644 --- a/test/listen.test.js +++ b/test/listen.test.js @@ -1,11 +1,11 @@ 'use strict' -const os = require('os') -const path = require('path') -const fs = require('fs') +const os = require('node:os') +const path = require('node:path') +const fs = require('node:fs') const { test, before } = require('tap') -const dns = require('dns').promises -const dnsCb = require('dns') +const dns = require('node:dns').promises +const dnsCb = require('node:dns') const sget = require('simple-get').concat const Fastify = require('..') diff --git a/test/maxRequestsPerSocket.test.js b/test/maxRequestsPerSocket.test.js index 9de7676c7c1..ea98e4a619e 100644 --- a/test/maxRequestsPerSocket.test.js +++ b/test/maxRequestsPerSocket.test.js @@ -1,6 +1,6 @@ 'use strict' -const net = require('net') +const net = require('node:net') const { test } = require('tap') const semver = require('semver') const Fastify = require('../fastify') diff --git a/test/plugin.name.display.js b/test/plugin.name.display.js index 05927b604bd..0d42e052501 100644 --- a/test/plugin.name.display.js +++ b/test/plugin.name.display.js @@ -1,6 +1,6 @@ 'use strict' -const assert = require('assert') +const assert = require('node:assert') module.exports = function (fastify, opts, done) { assert.strictEqual(fastify.pluginName, 'test-plugin') diff --git a/test/reply-error.test.js b/test/reply-error.test.js index 7afb49e77f1..2db6ef4ff67 100644 --- a/test/reply-error.test.js +++ b/test/reply-error.test.js @@ -2,12 +2,12 @@ const t = require('tap') const test = t.test -const net = require('net') +const net = require('node:net') const Fastify = require('..') -const statusCodes = require('http').STATUS_CODES +const statusCodes = require('node:http').STATUS_CODES const split = require('split2') -const fs = require('fs') -const path = require('path') +const fs = require('node:fs') +const path = require('node:path') const codes = Object.keys(statusCodes) codes.forEach(code => { diff --git a/test/reply-trailers.test.js b/test/reply-trailers.test.js index 1e50fe90d0f..40b47e21b0b 100644 --- a/test/reply-trailers.test.js +++ b/test/reply-trailers.test.js @@ -3,9 +3,9 @@ const t = require('tap') const test = t.test const Fastify = require('..') -const { Readable } = require('stream') -const { createHash } = require('crypto') -const { promisify } = require('util') +const { Readable } = require('node:stream') +const { createHash } = require('node:crypto') +const { promisify } = require('node:util') const sleep = promisify(setTimeout) test('send trailers when payload is empty string', t => { diff --git a/test/request-error.test.js b/test/request-error.test.js index b64b5f59546..4cb98fa68aa 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -1,6 +1,6 @@ 'use strict' -const { connect } = require('net') +const { connect } = require('node:net') const sget = require('simple-get').concat const t = require('tap') const test = t.test diff --git a/test/requestTimeout.test.js b/test/requestTimeout.test.js index 6f62a63bac5..af811143be9 100644 --- a/test/requestTimeout.test.js +++ b/test/requestTimeout.test.js @@ -1,6 +1,6 @@ 'use strict' -const http = require('http') +const http = require('node:http') const { test } = require('tap') const Fastify = require('../fastify') diff --git a/test/route-hooks.test.js b/test/route-hooks.test.js index aa90f1d8d57..efef7553ced 100644 --- a/test/route-hooks.test.js +++ b/test/route-hooks.test.js @@ -1,6 +1,6 @@ 'use strict' -const { Readable } = require('stream') +const { Readable } = require('node:stream') const test = require('tap').test const sget = require('simple-get').concat const Fastify = require('../') diff --git a/test/route.test.js b/test/route.test.js index 9fce94d8ed6..ca4b37f1e25 100644 --- a/test/route.test.js +++ b/test/route.test.js @@ -1,6 +1,6 @@ 'use strict' -const stream = require('stream') +const stream = require('node:stream') const split = require('split2') const t = require('tap') const test = t.test diff --git a/test/serial/logger.0.test.js b/test/serial/logger.0.test.js index f210afd6127..3ac6c2ad987 100644 --- a/test/serial/logger.0.test.js +++ b/test/serial/logger.0.test.js @@ -1,7 +1,7 @@ 'use strict' -const http = require('http') -const stream = require('stream') +const http = require('node:http') +const stream = require('node:stream') const t = require('tap') const split = require('split2') diff --git a/test/serial/logger.1.test.js b/test/serial/logger.1.test.js index 206de71fb86..03a82cb53a2 100644 --- a/test/serial/logger.1.test.js +++ b/test/serial/logger.1.test.js @@ -1,14 +1,14 @@ 'use strict' -const http = require('http') -const stream = require('stream') -const os = require('os') -const fs = require('fs') +const http = require('node:http') +const stream = require('node:stream') +const os = require('node:os') +const fs = require('node:fs') const t = require('tap') const split = require('split2') const pino = require('pino') -const path = require('path') +const path = require('node:path') const { streamSym } = require('pino/lib/symbols') const Fastify = require('../../fastify') diff --git a/test/skip-reply-send.test.js b/test/skip-reply-send.test.js index 4ce58c471e3..fa9a418967d 100644 --- a/test/skip-reply-send.test.js +++ b/test/skip-reply-send.test.js @@ -2,7 +2,7 @@ const { test } = require('tap') const split = require('split2') -const net = require('net') +const net = require('node:net') const Fastify = require('../fastify') process.removeAllListeners('warning') diff --git a/test/stream.test.js b/test/stream.test.js index b5da8d0776e..b5d34bc9ff1 100644 --- a/test/stream.test.js +++ b/test/stream.test.js @@ -4,15 +4,15 @@ const t = require('tap') const test = t.test const proxyquire = require('proxyquire') const sget = require('simple-get').concat -const fs = require('fs') -const resolve = require('path').resolve -const zlib = require('zlib') -const pipeline = require('stream').pipeline +const fs = require('node:fs') +const resolve = require('node:path').resolve +const zlib = require('node:zlib') +const pipeline = require('node:stream').pipeline const Fastify = require('..') const errors = require('http-errors') const JSONStream = require('JSONStream') const send = require('send') -const Readable = require('stream').Readable +const Readable = require('node:stream').Readable const split = require('split2') const semver = require('semver') const { kDisableRequestLogging } = require('../lib/symbols.js') @@ -164,7 +164,7 @@ test('onSend hook stream should work even if payload is not a proper stream', t t.plan(1) const reply = proxyquire('../lib/reply', { - stream: { + 'node:stream': { finished: (...args) => { if (args.length === 2) { args[1](new Error('test-error')) } } @@ -205,7 +205,7 @@ test('onSend hook stream should work on payload with "close" ending function', t t.plan(1) const reply = proxyquire('../lib/reply', { - stream: { + 'node:stream': { finished: (...args) => { if (args.length === 2) { args[1](new Error('test-error')) } } @@ -251,8 +251,8 @@ test('Destroying streams prematurely', t => { } catch (e) { t.fail() } - const stream = require('stream') - const http = require('http') + const stream = require('node:stream') + const http = require('node:http') // Test that "premature close" errors are logged with level warn logStream.on('data', line => { @@ -313,8 +313,8 @@ test('Destroying streams prematurely should call close method', t => { } catch (e) { t.fail() } - const stream = require('stream') - const http = require('http') + const stream = require('node:stream') + const http = require('node:http') // Test that "premature close" errors are logged with level warn logStream.on('data', line => { @@ -375,8 +375,8 @@ test('Destroying streams prematurely should call close method when destroy is no } catch (e) { t.fail() } - const stream = require('stream') - const http = require('http') + const stream = require('node:stream') + const http = require('node:http') // Test that "premature close" errors are logged with level warn logStream.on('data', line => { @@ -437,8 +437,8 @@ test('Destroying streams prematurely should call abort method', t => { } catch (e) { t.fail() } - const stream = require('stream') - const http = require('http') + const stream = require('node:stream') + const http = require('node:http') // Test that "premature close" errors are logged with level warn logStream.on('data', line => { @@ -496,8 +496,8 @@ test('Destroying streams prematurely, log is disabled', t => { } catch (e) { t.fail() } - const stream = require('stream') - const http = require('http') + const stream = require('node:stream') + const http = require('node:http') fastify.get('/', function (request, reply) { reply.log[kDisableRequestLogging] = true @@ -632,7 +632,7 @@ test('should support send module 200 and 404', { skip: semver.gte(process.versio test('should destroy stream when response is ended', t => { t.plan(4) - const stream = require('stream') + const stream = require('node:stream') const fastify = Fastify() fastify.get('/error', function (req, reply) { @@ -729,7 +729,7 @@ test('reply.send handles aborted requests', t => { t.teardown(() => { fastify.close() }) const port = fastify.server.address().port - const http = require('http') + const http = require('node:http') const req = http.get(`http://localhost:${port}`) .on('error', (err) => { t.equal(err.code, 'ECONNRESET') @@ -782,7 +782,7 @@ test('request terminated should not crash fastify', t => { t.teardown(() => { fastify.close() }) const port = fastify.server.address().port - const http = require('http') + const http = require('node:http') const req = http.get(`http://localhost:${port}`, function (res) { const { statusCode, headers } = res t.equal(statusCode, 200) diff --git a/test/trust-proxy.test.js b/test/trust-proxy.test.js index fb6c52d6818..64f9c5a59d9 100644 --- a/test/trust-proxy.test.js +++ b/test/trust-proxy.test.js @@ -4,7 +4,7 @@ const t = require('tap') const { test, before } = t const sget = require('simple-get').concat const fastify = require('..') -const dns = require('dns').promises +const dns = require('node:dns').promises const sgetForwardedRequest = (app, forHeader, path, protoHeader) => { const headers = { diff --git a/test/unsupported-httpversion.test.js b/test/unsupported-httpversion.test.js index 9d6b9b17919..f6f05b438e9 100644 --- a/test/unsupported-httpversion.test.js +++ b/test/unsupported-httpversion.test.js @@ -1,6 +1,6 @@ 'use strict' -const net = require('net') +const net = require('node:net') const t = require('tap') const Fastify = require('../fastify') diff --git a/test/upgrade.test.js b/test/upgrade.test.js index fe727c7b2fd..74c5f794244 100644 --- a/test/upgrade.test.js +++ b/test/upgrade.test.js @@ -1,10 +1,10 @@ 'use strict' const { test, skip } = require('tap') -const { lookup } = require('dns').promises +const { lookup } = require('node:dns').promises const Fastify = require('..') -const { connect } = require('net') -const { once } = require('events') +const { connect } = require('node:net') +const { once } = require('node:events') async function setup () { const results = await lookup('localhost', { all: true }) diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js index 8d79b6129db..25954dcb032 100644 --- a/test/versioned-routes.test.js +++ b/test/versioned-routes.test.js @@ -4,7 +4,7 @@ const { test, before } = require('tap') const helper = require('./helper') const Fastify = require('..') const sget = require('simple-get').concat -const http = require('http') +const http = require('node:http') const split = require('split2') const append = require('vary').append const proxyquire = require('proxyquire') From eaf7b7b7442ef81c370d6714859626aa141977c5 Mon Sep 17 00:00:00 2001 From: Menkveld <43108191+Menkveld-24@users.noreply.github.com> Date: Sun, 10 Sep 2023 20:46:41 +0200 Subject: [PATCH 0454/1295] Moonkeypatch typo (#5027) --- docs/Reference/Principles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Principles.md b/docs/Reference/Principles.md index 2279ab3f062..7439bf7eea6 100644 --- a/docs/Reference/Principles.md +++ b/docs/Reference/Principles.md @@ -65,7 +65,7 @@ Testing Fastify applications should be a first-class concern. ## Do not monkeypatch core -Moonkeypatch Node.js APIs or installing globals that alter the behavior of the +Monkeypatch Node.js APIs or installing globals that alter the behavior of the runtime makes building modular applications harder, and limit the use cases of Fastify. Other frameworks do this and we do not. From 5d802200b5219c52b8c58b5a0f557d7564919b84 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 11 Sep 2023 08:36:13 +0200 Subject: [PATCH 0455/1295] chore: change website to .dev instead of .io (#5028) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8db9411f896..baf577ac89d 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "bugs": { "url": "https://github.com/fastify/fastify/issues" }, - "homepage": "https://www.fastify.io/", + "homepage": "https://www.fastify.dev/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", "@sinclair/typebox": "^0.31.1", From 9e903132ddfcec84e26982cd247d8d28c5573d8e Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 11 Sep 2023 11:02:34 +0200 Subject: [PATCH 0456/1295] chore: add gurgunday and uzlopak as contributors (#5029) * chore: add gurgunday and uzlopak as contributors * chore: add gurgunday and uzlopak as contributors --- README.md | 4 ++++ package.json | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/README.md b/README.md index 15a6111de72..797a9411bde 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,8 @@ listed in alphabetical order. , * [__James Sumners__](https://github.com/jsumners), , +* [__Aras Abbasi__](https://github.com/uzlopak), + ### Fastify Plugins team * [__Matteo Collina__](https://github.com/mcollina), @@ -340,6 +342,8 @@ listed in alphabetical order. , * [__Simone Busoli__](https://github.com/simoneb), , +* [__Gürgün Dayıoğlu__](https://github.com/gurgunday), + ### Great Contributors Great contributors on a specific area in the Fastify ecosystem will be invited diff --git a/package.json b/package.json index baf577ac89d..c00b77c196d 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,15 @@ "name": "Carlos Fuentes", "email": "me@metcoder.dev", "url": "https://metcoder.dev" + }, + { + "name": "Gürgün Dayıoğlu", + "email": "gurgun.dayioglu@icloud.com", + "url": "https://heyhey.to/G" + }, + { + "name": "Aras Abbasi", + "email": "aras.abbasi@gmail.com" } ], "license": "MIT", From e92e3913d42d851ef60355f8e8e42d8adcc4f0ff Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 11 Sep 2023 11:37:23 +0200 Subject: [PATCH 0457/1295] Add a citgm command to customize what we run in CITGM (#5030) Signed-off-by: Matteo Collina --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c00b77c196d..abe814aae68 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "test:watch": "npm run unit -- -w --no-coverage-report -R terse", "unit": "c8 tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", - "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap" + "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap", + "citgm": "tap" }, "repository": { "type": "git", From cea0536c49af460f7e6da0f76fb892576f12f2bb Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 11 Sep 2023 11:40:09 +0200 Subject: [PATCH 0458/1295] Bumped v4.23.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 819984cf3e2..9fd43c96a7c 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.22.2' +const VERSION = '4.23.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index abe814aae68..89d0590b00a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.22.2", + "version": "4.23.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 6b9b09318397b23c2feeb8c7d8699fb0c17eb48e Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 12 Sep 2023 09:03:15 +0200 Subject: [PATCH 0459/1295] fix: correct warnings for request.routerPath and request.routerMethod (#5032) * fix: correct warnings for request.routerPath and request.routerMethod * fix: Request.md --- docs/Reference/Request.md | 2 +- lib/warnings.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index df4287d7bab..4b524e6d2ef 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -31,7 +31,7 @@ Request is a core Fastify object containing the following fields: original `url` in case of internal re-routing - `routerMethod` - Deprecated, use `request.routeOptions.method` instead. The method defined for the router that is handling the request -- `routerPath` - Deprecated, use `request.routeOptions.config.url` instead. The +- `routerPath` - Deprecated, use `request.routeOptions.url` instead. The path pattern defined for the router that is handling the request - `is404` - true if request is being handled by 404 handler, false if it is not - `connection` - Deprecated, use `socket` instead. The underlying connection of diff --git a/lib/warnings.js b/lib/warnings.js index 5b7862313b6..01cee1c4154 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -31,9 +31,9 @@ warning.create('FastifyDeprecation', 'FSTDEP015', 'You are accessing the depreca warning.create('FastifyDeprecation', 'FSTDEP016', 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.') -warning.create('FastifyDeprecation', 'FSTDEP017', 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.config.url" instead. Property "req.routerPath" will be removed in `fastify@5`.') +warning.create('FastifyDeprecation', 'FSTDEP017', 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.url" instead. Property "req.routerPath" will be removed in `fastify@5`.') -warning.create('FastifyDeprecation', 'FSTDEP018', 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.config.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.') +warning.create('FastifyDeprecation', 'FSTDEP018', 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.') warning.create('FastifyWarning', 'FSTWRN001', 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', { unlimited: true }) From 30c2dffc165e6fc87fbe6ea4b17ec2bc1926e28a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 13 Sep 2023 11:50:43 +0200 Subject: [PATCH 0460/1295] Add routeOptions.config to types (#5034) Signed-off-by: Matteo Collina --- test/types/request.test-d.ts | 5 ++++- types/request.d.ts | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 420bd2e528a..bda58d6adc5 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -3,6 +3,7 @@ import { expectAssignable, expectType } from 'tsd' import fastify, { ContextConfigDefault, FastifyContext, + FastifyContextConfig, FastifyLogFn, FastifySchema, FastifyTypeProviderDefault, @@ -18,7 +19,7 @@ import { FastifyInstance } from '../../types/instance' import { FastifyLoggerInstance } from '../../types/logger' import { FastifyReply } from '../../types/reply' import { FastifyRequest, RequestRouteOptions } from '../../types/request' -import { RouteGenericInterface } from '../../types/route' +import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' import { RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from '../../types/utils' interface RequestBody { @@ -77,6 +78,8 @@ const getHandler: RouteHandler = function (request, _reply) { expectType>(request.context) expectType['config']>(request.context.config) expectType['config']>(request.routeConfig) + expectType['config']>(request.routeOptions.config) + expectType(request.routeOptions.config) expectType(request.routeSchema) expectType(request.headers) diff --git a/types/request.d.ts b/types/request.d.ts index b019bd3bab5..53bb62127ab 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,8 +1,8 @@ import { ErrorObject } from '@fastify/ajv-compiler' -import { FastifyContext } from './context' +import { FastifyContext, FastifyContextConfig } from './context' import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' -import { RouteGenericInterface } from './route' +import { RouteGenericInterface, FastifyRouteConfig } from './route' import { FastifySchema } from './schema' import { FastifyRequestType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyRequestType } from './type-provider' import { ContextConfigDefault, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './utils' @@ -20,7 +20,7 @@ export interface ValidationFunction { errors?: null | ErrorObject[]; } -export interface RequestRouteOptions { +export interface RequestRouteOptions { method: string, url: string, bodyLimit:number, @@ -28,7 +28,8 @@ export interface RequestRouteOptions { logLevel:string, version: string | undefined, exposeHeadRoute: boolean, - prefixTrailingSlash: string + prefixTrailingSlash: string, + config: FastifyContextConfig & FastifyRouteConfig & ContextConfig } /** @@ -78,7 +79,7 @@ export interface FastifyRequest + readonly routeOptions: Readonly> readonly is404: boolean; readonly socket: RawRequest['socket']; From 5f09e059235ffa4c1a77bcd3187f22dd67b57eaa Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 13 Sep 2023 10:58:02 +0100 Subject: [PATCH 0461/1295] Bumped v4.23.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 9fd43c96a7c..3f59aec326d 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.23.0' +const VERSION = '4.23.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 89d0590b00a..59965fc531e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.23.0", + "version": "4.23.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 128b6d4ffa8fb064abd0c9a498bed9ff35fc694c Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Wed, 13 Sep 2023 12:21:09 +0200 Subject: [PATCH 0462/1295] fix: add routeOptions.schema to types (#5035) --- test/types/request.test-d.ts | 1 + types/request.d.ts | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index bda58d6adc5..6fe20926f8d 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -81,6 +81,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType['config']>(request.routeOptions.config) expectType(request.routeOptions.config) expectType(request.routeSchema) + expectType(request.routeOptions.schema) expectType(request.headers) request.headers = {} diff --git a/types/request.d.ts b/types/request.d.ts index 53bb62127ab..1bb39d07184 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -20,16 +20,17 @@ export interface ValidationFunction { errors?: null | ErrorObject[]; } -export interface RequestRouteOptions { - method: string, - url: string, - bodyLimit:number, - attachValidation:boolean, - logLevel:string, - version: string | undefined, - exposeHeadRoute: boolean, - prefixTrailingSlash: string, - config: FastifyContextConfig & FastifyRouteConfig & ContextConfig +export interface RequestRouteOptions { + method: string; + url: string; + bodyLimit:number; + attachValidation:boolean; + logLevel:string; + version: string | undefined; + exposeHeadRoute: boolean; + prefixTrailingSlash: string; + config: FastifyContextConfig & FastifyRouteConfig & ContextConfig; + schema: SchemaCompiler; } /** @@ -79,7 +80,7 @@ export interface FastifyRequest> + readonly routeOptions: Readonly> readonly is404: boolean; readonly socket: RawRequest['socket']; From c08b67e0bfedc9935b51c787ae4cd6b250ad303c Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 14 Sep 2023 09:33:41 +0100 Subject: [PATCH 0463/1295] Bumped v4.23.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 3f59aec326d..c74a2f8d8ce 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.23.1' +const VERSION = '4.23.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 59965fc531e..271c2479eb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.23.1", + "version": "4.23.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 73f98aa86b0aa7db31eb57ac771a90d9a6b57625 Mon Sep 17 00:00:00 2001 From: Yoshiki Kadoshita Date: Mon, 18 Sep 2023 20:45:51 +0900 Subject: [PATCH 0464/1295] Add blank line before onclose hook heading (#5042) --- docs/Reference/Hooks.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 51c5b4aa4e3..34b53c696c7 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -445,6 +445,7 @@ fastify.addHook('onListen', async function () { > **Note** > This hook will not run when the server is started using `fastify.inject()` or `fastify.ready()` + ### onClose From edd220373743b2b8f42945e479d254d7f98c07ec Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 19 Sep 2023 07:06:09 +0100 Subject: [PATCH 0465/1295] build(dependabot): ignore tap major updates (#5047) --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2ca8454fe4f..7582bcdc022 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -32,3 +32,6 @@ updates: # Development dependencies dev-dependencies: dependency-type: "development" + ignore: + - dependency-name: tap + update-types: ["version-update:semver-major"] From cc6c04ec8ef05799733f6dfa9055a0d357eac4f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 06:32:19 +0000 Subject: [PATCH 0466/1295] chore: Bump actions/checkout from 3 to 4 (#5048) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark-parser.yml | 4 ++-- .github/workflows/benchmark.yml | 4 ++-- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/links-check.yml | 2 +- .github/workflows/lint-ecosystem-order.yml | 2 +- .github/workflows/md-lint.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 58c2d5c99f8..f28cf5d6e50 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -21,7 +21,7 @@ jobs: matrix: node-version: [16, 18, 20] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false ref: ${{github.event.pull_request.head.sha}} @@ -45,7 +45,7 @@ jobs: echo 'EOF' >> $GITHUB_OUTPUT # main benchmark - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false ref: 'main' diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 81c1f6490e3..d467c5c46dc 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -21,7 +21,7 @@ jobs: matrix: node-version: [16, 18, 20] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false ref: ${{github.event.pull_request.head.sha}} @@ -45,7 +45,7 @@ jobs: echo 'EOF' >> $GITHUB_OUTPUT # main benchmark - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false ref: 'main' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 724e566d622..e048e737646 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: contents: read steps: - name: Check out repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: persist-credentials: false @@ -42,7 +42,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false @@ -67,7 +67,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false @@ -117,7 +117,7 @@ jobs: node-version: 14 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false @@ -146,7 +146,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false @@ -174,7 +174,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false - name: Use Node.js diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index ba3bfb422a2..74f3dbf3614 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -9,7 +9,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 776410d7946..3a2112243e5 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -9,7 +9,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 9fa53622435..f9e785ec009 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -33,7 +33,7 @@ jobs: pnpm-version: 7 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 6b678589812..37c4d27e524 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index e4aa09c4ee3..4d9c7d73723 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index 7e6c861ba86..77be1ce2b73 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 22bd6f0a0f8..291e60b826f 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -25,7 +25,7 @@ jobs: pnpm-version: 7 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false From ffcffb2fe05513f275155c77e64e07ae69fa59f8 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 19 Sep 2023 09:57:40 +0200 Subject: [PATCH 0467/1295] chore: more perf (#5016) * chore: genId perf optimization * chore: perf syntax * chore: perf syntax reply * chore: Update lib/reply.js Co-authored-by: Uzlopak * rollback --------- Co-authored-by: Uzlopak --- lib/reply.js | 4 ++-- lib/reqIdGenFactory.js | 24 +++++++++++++++--------- lib/request.js | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/reply.js b/lib/reply.js index b744b1a8863..be691b7e077 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -244,7 +244,7 @@ Reply.prototype.header = function (key, value = '') { } if (Array.isArray(value)) { - this[kReplyHeaders][key].push(...value) + Array.prototype.push.apply(this[kReplyHeaders][key], value) } else { this[kReplyHeaders][key].push(value) } @@ -810,7 +810,7 @@ function onResponseCallback (err, request, reply) { } function buildReply (R) { - const props = [...R.props] + const props = R.props.slice() function _Reply (res, request, log) { this.raw = res diff --git a/lib/reqIdGenFactory.js b/lib/reqIdGenFactory.js index 68a2dad314b..8fc5d3e2a50 100644 --- a/lib/reqIdGenFactory.js +++ b/lib/reqIdGenFactory.js @@ -12,6 +12,16 @@ * @returns {GenerateRequestId} */ function reqIdGenFactory (requestIdHeader, optGenReqId) { + const genReqId = optGenReqId || buildDefaultGenReqId() + + if (requestIdHeader) { + return buildOptionalHeaderReqId(requestIdHeader, genReqId) + } + + return genReqId +} + +function buildDefaultGenReqId () { // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8). // With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days. // This is very likely to happen in real-world applications, hence the limit is enforced. @@ -20,20 +30,16 @@ function reqIdGenFactory (requestIdHeader, optGenReqId) { const maxInt = 2147483647 let nextReqId = 0 - function defaultGenReqId (_req) { + return function defaultGenReqId () { nextReqId = (nextReqId + 1) & maxInt return `req-${nextReqId.toString(36)}` } +} - const genReqId = optGenReqId || defaultGenReqId - - if (requestIdHeader) { - return function (req) { - return req.headers[requestIdHeader] || genReqId(req) - } +function buildOptionalHeaderReqId (requestIdHeader, genReqId) { + return function (req) { + return req.headers[requestIdHeader] || genReqId(req) } - - return genReqId } module.exports = { diff --git a/lib/request.js b/lib/request.js index bafd7ba897e..46c3174c232 100644 --- a/lib/request.js +++ b/lib/request.js @@ -66,7 +66,7 @@ function buildRequest (R, trustProxy) { } function buildRegularRequest (R) { - const props = [...R.props] + const props = R.props.slice() function _Request (id, params, req, query, log, context) { this.id = id this[kRouteContext] = context From f33db238f4eda4602517a6d19f537751b5193395 Mon Sep 17 00:00:00 2001 From: Zac Rosenbauer Date: Tue, 19 Sep 2023 05:49:21 -0400 Subject: [PATCH 0468/1295] docs(ecosystem): add fastify-prisma (#5041) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index c80bfbc96cf..92c939e1373 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -181,6 +181,8 @@ section. to go! A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine on Fastify +- [`@joggr/fastify-prisma`](https://github.com/joggrdocs/fastify-prisma) + A plugin for accessing an instantiated PrismaClient on your server. - [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit) A plugin to close the server gracefully - [`@mgcrea/fastify-request-logger`](https://github.com/mgcrea/fastify-request-logger) From 9c632091d9d5f1d0edde716dd721c21f3ad66c0f Mon Sep 17 00:00:00 2001 From: aarontravass <43901677+aarontravass@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:22:31 -0400 Subject: [PATCH 0469/1295] feat: new logger api (#5020) Co-authored-by: Carlos Fuentes --- docs/Reference/Errors.md | 17 +++++ fastify.d.ts | 3 +- lib/errors.js | 21 ++++++ lib/logger.js | 28 ++++++-- test/async-await.test.js | 2 +- test/internals/errors.test.js | 40 +++++++++-- test/internals/initialConfig.test.js | 2 +- test/serial/logger.0.test.js | 22 +++--- test/serial/logger.1.test.js | 101 ++++++++++++++++++++++++--- test/stream.test.js | 6 +- test/types/logger.test-d.ts | 44 +++++++++++- test/types/register.test-d.ts | 2 +- test/types/request.test-d.ts | 2 +- 13 files changed, 248 insertions(+), 42 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 46386bba045..df9f3ee4a66 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -290,6 +290,23 @@ The logger accepts either a `'stream'` or a `'file'` as the destination. The logger should have all these methods: `'info'`, `'error'`, `'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`. +#### FST_ERR_LOG_INVALID_LOGGER_INSTANCE + + +The loggerInstance only accepts a logger instance, not a configuration object. + +#### FST_ERR_LOG_INVALID_LOGGER_CONFIG + + +The logger option only accepts a configuration object, not a logger instance. +To pass an instance, use `'loggerInstance'` instead. + +#### FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED + + +You cannot provide both `'logger'` and `'loggerInstance'`. Please provide +only one option. + #### FST_ERR_REP_INVALID_PAYLOAD_TYPE diff --git a/fastify.d.ts b/fastify.d.ts index 5c0eb90f998..d3602820f7d 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -102,7 +102,8 @@ declare namespace fastify { exposeHeadRoutes?: boolean, onProtoPoisoning?: ProtoAction, onConstructorPoisoning?: ConstructorAction, - logger?: boolean | FastifyLoggerOptions & PinoLoggerOptions | Logger, + logger?: boolean | FastifyLoggerOptions & PinoLoggerOptions, + loggerInstance?: Logger serializerOpts?: FJSOptions | Record, serverFactory?: FastifyServerFactory, caseSensitive?: boolean, diff --git a/lib/errors.js b/lib/errors.js index 43558374ca5..a3bf9be595c 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -203,6 +203,27 @@ const codes = { TypeError ), + FST_ERR_LOG_INVALID_LOGGER_INSTANCE: createError( + 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE', + 'loggerInstance only accepts a logger instance.', + 500, + TypeError + ), + + FST_ERR_LOG_INVALID_LOGGER_CONFIG: createError( + 'FST_ERR_LOG_INVALID_LOGGER_CONFIG', + 'logger options only accepts a configuration object.', + 500, + TypeError + ), + + FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED: createError( + 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED', + 'You cannot provide both logger and loggerInstance. Please provide only one.', + 500, + TypeError + ), + /** * reply */ diff --git a/lib/logger.js b/lib/logger.js index ddf81c9c2c9..75c2288e972 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -11,7 +11,10 @@ const pino = require('pino') const { serializersSym } = pino.symbols const { FST_ERR_LOG_INVALID_DESTINATION, - FST_ERR_LOG_INVALID_LOGGER + FST_ERR_LOG_INVALID_LOGGER, + FST_ERR_LOG_INVALID_LOGGER_INSTANCE, + FST_ERR_LOG_INVALID_LOGGER_CONFIG, + FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED } = require('./errors') function createPinoLogger (opts) { @@ -70,20 +73,35 @@ function now () { } function createLogger (options) { - if (!options.logger) { + // if no logger is provided, then create a default logger + if (!options.loggerInstance && !options.logger) { const logger = nullLogger logger.child = () => logger return { logger, hasLogger: false } } - if (validateLogger(options.logger)) { + if (options.logger && options.loggerInstance) { + throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED() + } + + // check if the logger instance has all required properties + if (validateLogger(options.loggerInstance)) { const logger = createPinoLogger({ - logger: options.logger, - serializers: Object.assign({}, serializers, options.logger.serializers) + logger: options.loggerInstance, + serializers: Object.assign({}, serializers, options.loggerInstance.serializers) }) return { logger, hasLogger: true } } + // if a logger instance is passed to logger, throw an exception + if (validateLogger(options.logger)) { + throw FST_ERR_LOG_INVALID_LOGGER_CONFIG() + } + + if (options.loggerInstance) { + throw FST_ERR_LOG_INVALID_LOGGER_INSTANCE() + } + const localLoggerOptions = {} if (Object.prototype.toString.call(options.logger) === '[object Object]') { Reflect.ownKeys(options.logger).forEach(prop => { diff --git a/test/async-await.test.js b/test/async-await.test.js index f1dbce9e143..8e3d5cf9643 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -179,7 +179,7 @@ test('server logs an error if reply.send is called and a value is returned via a const logger = pino(splitStream) const fastify = Fastify({ - logger + loggerInstance: logger }) fastify.get('/', async (req, reply) => { diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index aba92651b18..2b73ecf0f35 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -test('should expose 77 errors', t => { +test('should expose 80 errors', t => { t.plan(1) const exportedKeys = Object.keys(errors) let counter = 0 @@ -14,11 +14,11 @@ test('should expose 77 errors', t => { counter++ } } - t.equal(counter, 77) + t.equal(counter, 80) }) test('ensure name and codes of Errors are identical', t => { - t.plan(77) + t.plan(80) const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -327,6 +327,36 @@ test('FST_ERR_LOG_INVALID_LOGGER', t => { t.ok(error instanceof TypeError) }) +test('FST_ERR_LOG_INVALID_LOGGER_INSTANCE', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_INVALID_LOGGER_INSTANCE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE') + t.equal(error.message, 'loggerInstance only accepts a logger instance.') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_LOG_INVALID_LOGGER_CONFIG', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_INVALID_LOGGER_CONFIG() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_LOG_INVALID_LOGGER_CONFIG') + t.equal(error.message, 'logger options only accepts a configuration object.') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + +test('FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED') + t.equal(error.message, 'You cannot provide both logger and loggerInstance. Please provide only one.') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_REP_INVALID_PAYLOAD_TYPE() @@ -808,7 +838,7 @@ test('FST_ERR_LISTEN_OPTIONS_INVALID', t => { }) test('Ensure that all errors are in Errors.md documented', t => { - t.plan(77) + t.plan(80) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -820,7 +850,7 @@ test('Ensure that all errors are in Errors.md documented', t => { }) test('Ensure that non-existing errors are not in Errors.md documented', t => { - t.plan(77) + t.plan(80) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /#### ([0-9a-zA-Z_]+)\n/g diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index d5d2d9af3ba..95a0c08164f 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -103,7 +103,7 @@ test('Fastify.initialConfig should expose all options', t => { genReqId: function (req) { return reqId++ }, - logger: pino({ level: 'info' }), + loggerInstance: pino({ level: 'info' }), constraints: { version: versionStrategy }, diff --git a/test/serial/logger.0.test.js b/test/serial/logger.0.test.js index 3ac6c2ad987..6536940b18c 100644 --- a/test/serial/logger.0.test.js +++ b/test/serial/logger.0.test.js @@ -164,7 +164,7 @@ t.test('test log stream', (t) => { const logger = require('pino')(stream) - const fastify = Fastify({ logger }) + const fastify = Fastify({ loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) fastify.get('/foo', function (req, reply) { @@ -202,7 +202,7 @@ t.test('test log stream', (t) => { }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -238,7 +238,7 @@ t.test('test log stream', (t) => { } try { - const fastify = Fastify({ logger: loggerInstance }) + const fastify = Fastify({ loggerInstance }) await fastify.ready() } catch (err) { t.equal( @@ -253,7 +253,7 @@ t.test('test log stream', (t) => { t.plan(1) try { - const fastify = Fastify({ logger: console }) + const fastify = Fastify({ loggerInstance: console }) await fastify.ready() } catch (err) { t.equal( @@ -531,7 +531,7 @@ t.test('test log stream', (t) => { const logger = pino(stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -588,7 +588,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'error' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -634,7 +634,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'error' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -670,7 +670,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'error' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -732,7 +732,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'info' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -791,7 +791,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'info' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -829,7 +829,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'info' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) diff --git a/test/serial/logger.1.test.js b/test/serial/logger.1.test.js index 37d3f42dc4c..36fe47f5669 100644 --- a/test/serial/logger.1.test.js +++ b/test/serial/logger.1.test.js @@ -56,7 +56,7 @@ t.test('test log stream', (t) => { let localhost let localhostForURL - t.plan(24) + t.plan(28) t.before(async function () { [localhost, localhostForURL] = await helper.getLoopbackHost() @@ -74,7 +74,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'info' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -126,7 +126,7 @@ t.test('test log stream', (t) => { } }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -170,7 +170,7 @@ t.test('test log stream', (t) => { } }, stream) - const fastify = Fastify({ logger }) + const fastify = Fastify({ loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } }) @@ -210,7 +210,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'info' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -246,7 +246,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'warn' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -281,7 +281,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'warn' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -321,7 +321,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'error' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -364,7 +364,7 @@ t.test('test log stream', (t) => { const logger = pino({ level: 'trace' }, stream) const fastify = Fastify({ - logger + loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) @@ -811,7 +811,7 @@ t.test('test log stream', (t) => { child: () => logger } - const fastify = Fastify({ logger }) + const fastify = Fastify({ loggerInstance: logger }) t.teardown(fastify.close.bind(fastify)) fastify.log.fatal('fatal') @@ -859,4 +859,85 @@ t.test('test log stream', (t) => { t.same(lines[0].req, {}) }) + + t.test('Should throw an error if logger instance is passed to `logger`', async (t) => { + t.plan(2) + const stream = split(JSON.parse) + + const logger = require('pino')(stream) + + try { + Fastify({ logger }) + } catch (err) { + t.ok(err) + t.equal(err.code, 'FST_ERR_LOG_INVALID_LOGGER_CONFIG') + } + }) + + t.test('Should throw an error if options are passed to `loggerInstance`', async (t) => { + t.plan(2) + try { + Fastify({ loggerInstance: { level: 'log' } }) + } catch (err) { + t.ok(err) + t.equal(err.code, 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE') + } + }) + + t.test('If both `loggerInstance` and `logger` are provided, an error should be thrown', async (t) => { + t.plan(2) + const loggerInstanceStream = split(JSON.parse) + const loggerInstance = pino({ level: 'error' }, loggerInstanceStream) + const loggerStream = split(JSON.parse) + try { + Fastify({ + logger: { + stream: loggerStream, + level: 'info' + }, + loggerInstance + }) + } catch (err) { + t.ok(err) + t.equal(err.code, 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED') + } + }) + + t.test('`logger` should take pino configuration and create a pino logger', async (t) => { + const lines = ['hello', 'world'] + t.plan(2 * lines.length + 2) + const loggerStream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream: loggerStream, + level: 'error' + } + }) + t.teardown(fastify.close.bind(fastify)) + fastify.get('/hello', (req, reply) => { + req.log.error('hello') + reply.code(404).send() + }) + + fastify.get('/world', (req, reply) => { + req.log.error('world') + reply.code(201).send() + }) + + await fastify.ready() + { + const response = await fastify.inject({ method: 'GET', url: '/hello' }) + t.equal(response.statusCode, 404) + } + { + const response = await fastify.inject({ method: 'GET', url: '/world' }) + t.equal(response.statusCode, 201) + } + + for await (const [line] of on(loggerStream, 'data')) { + t.equal(line.level, 50) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) }) diff --git a/test/stream.test.js b/test/stream.test.js index b5d34bc9ff1..20b73e359ba 100644 --- a/test/stream.test.js +++ b/test/stream.test.js @@ -186,7 +186,7 @@ test('onSend hook stream should work even if payload is not a proper stream', t child: () => { return spyLogger } } - const fastify = Fastify({ logger: spyLogger }) + const fastify = Fastify({ loggerInstance: spyLogger }) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) @@ -710,7 +710,7 @@ test('reply.send handles aborted requests', t => { child: () => { return spyLogger } } const fastify = Fastify({ - logger: spyLogger + loggerInstance: spyLogger }) fastify.get('/', (req, reply) => { @@ -758,7 +758,7 @@ test('request terminated should not crash fastify', t => { child: () => { return spyLogger } } const fastify = Fastify({ - logger: spyLogger + loggerInstance: spyLogger }) fastify.get('/', async (req, reply) => { diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index 472aef280b6..16f6eba5186 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -107,14 +107,52 @@ const serverAutoInferringTypes = fastify({ expectType(serverAutoInferringTypes.log) -const serverWithAutoInferredPino = fastify({ - logger: P({ +const serverWithLoggerInstance = fastify({ + loggerInstance: P({ level: 'info', redact: ['x-userinfo'] }) }) -expectType(serverWithAutoInferredPino.log) +expectType(serverWithLoggerInstance.log) + +const serverWithPinoConfig = fastify({ + logger: { + level: 'info', + serializers: { + req (IncomingMessage) { + expectType(IncomingMessage) + return { + method: 'method', + url: 'url', + version: 'version', + host: 'hostname', + remoteAddress: 'remoteAddress', + remotePort: 80, + other: '' + } + }, + res (ServerResponse) { + expectType>(ServerResponse) + expectAssignable & Pick>(ServerResponse) + expectNotAssignable(ServerResponse) + return { + statusCode: 'statusCode' + } + }, + err (FastifyError) { + return { + other: '', + type: 'type', + message: 'msg', + stack: 'stack' + } + } + } + } +}) + +expectType(serverWithPinoConfig.log) const serverAutoInferredFileOption = fastify({ logger: { diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index 3519d3c3b49..d1ddc10e6ba 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -115,7 +115,7 @@ const customLogger = { silent: () => { } } const serverWithTypeProviderAndLogger = fastify({ - logger: customLogger + loggerInstance: customLogger }).withTypeProvider() type ServerWithTypeProviderAndLogger = FastifyInstance const testPluginWithTypeProviderAndLogger: FastifyPluginCallback = function (instance, opts, done) { } diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 0e90223e0f3..88908070a61 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -150,7 +150,7 @@ const customLogger: CustomLoggerInterface = { child: () => customLogger as pino.Logger } -const serverWithCustomLogger = fastify({ logger: customLogger }) +const serverWithCustomLogger = fastify({ loggerInstance: customLogger }) expectType< FastifyInstance & PromiseLike> From 7634272db1698c0e70909850ef28009d2f09562d Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 24 Sep 2023 13:54:22 +0200 Subject: [PATCH 0470/1295] fix test: yup breaking (#5058) --- test/input-validation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/input-validation.js b/test/input-validation.js index 4dbfc8196f8..ce8a40574c2 100644 --- a/test/input-validation.js +++ b/test/input-validation.js @@ -72,7 +72,7 @@ module.exports.payloadMethod = function (method, t) { const result = schema.validateSync(data, yupOptions) return { value: result } } catch (e) { - return { error: e } + return { error: [e] } } } } @@ -286,9 +286,9 @@ module.exports.payloadMethod = function (method, t) { }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 400) - t.same(body, { + t.match(body, { error: 'Bad Request', - message: 'hello must be a `string` type, but the final value was: `44`.', + message: /body hello must be a `string` type, but the final value was: `44`./, statusCode: 400, code: 'FST_ERR_VALIDATION' }) From 5a98e7e264f197326993730a3a30a4bda9a8fe8e Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sun, 24 Sep 2023 16:55:45 +0300 Subject: [PATCH 0471/1295] perf: optimize split params (#5057) Co-authored-by: Uzlopak --- lib/contentTypeParser.js | 2 +- lib/schemas.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index ec21048a30c..dcbcedbc94e 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -406,7 +406,7 @@ function ParserListItem (contentType) { // because it would become a match-all handler if (this.isEssence === false && parsed.type === '') { // handle semicolon or empty string - const tmp = contentType.split(';')[0] + const tmp = contentType.split(';', 1)[0] this.type = tmp === '' ? contentType : tmp } else { this.type = parsed.type diff --git a/lib/schemas.js b/lib/schemas.js index 95382009a73..c9a603e8e0a 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -158,7 +158,7 @@ function getSchemaSerializer (context, statusCode, contentType) { } if (responseSchemaDef[statusCode]) { if (responseSchemaDef[statusCode].constructor === Object && contentType) { - const mediaName = contentType.split(';')[0] + const mediaName = contentType.split(';', 1)[0] if (responseSchemaDef[statusCode][mediaName]) { return responseSchemaDef[statusCode][mediaName] } @@ -170,7 +170,7 @@ function getSchemaSerializer (context, statusCode, contentType) { const fallbackStatusCode = (statusCode + '')[0] + 'xx' if (responseSchemaDef[fallbackStatusCode]) { if (responseSchemaDef[fallbackStatusCode].constructor === Object && contentType) { - const mediaName = contentType.split(';')[0] + const mediaName = contentType.split(';', 1)[0] if (responseSchemaDef[fallbackStatusCode][mediaName]) { return responseSchemaDef[fallbackStatusCode][mediaName] } @@ -182,7 +182,7 @@ function getSchemaSerializer (context, statusCode, contentType) { } if (responseSchemaDef.default) { if (responseSchemaDef.default.constructor === Object && contentType) { - const mediaName = contentType.split(';')[0] + const mediaName = contentType.split(';', 1)[0] if (responseSchemaDef.default[mediaName]) { return responseSchemaDef.default[mediaName] } From 14a9ccc485328134b6fe489458bccf565e0cabf1 Mon Sep 17 00:00:00 2001 From: DemonHa <91160178+DemonHa@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:09:40 +0200 Subject: [PATCH 0472/1295] chore: implicitly infer SchemaCompiler as readonly (#5060) * chore: explicitly infer SchemaCompiler as readonly * test: missing test case --- docs/Reference/Type-Providers.md | 4 +- test/types/type-provider.test-d.ts | 89 ++++++++++++++++++++++++++++++ types/instance.d.ts | 2 +- types/route.d.ts | 6 +- 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 49a155c445d..85d5a430930 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -47,7 +47,7 @@ server.get('/route', { }, required: ['foo', 'bar'] } - } as const // don't forget to use const ! + } }, (request, reply) => { @@ -141,7 +141,7 @@ function pluginWithJsonSchema(fastify: FastifyInstance, _opts, done): void { y: { type: 'number' }, z: { type: 'boolean' } }, - } as const + } } }, (req) => { const { x, y, z } = req.body // type safe diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index f856c1088ad..e7894fb4229 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -105,6 +105,8 @@ expectAssignable(server.withTypeProvider()) interface JsonSchemaToTsProvider extends FastifyTypeProvider { output: this['input'] extends JSONSchema ? FromSchema : unknown } +// explicitly setting schema `as const` + expectAssignable(server.withTypeProvider().get( '/', { @@ -134,6 +136,93 @@ expectAssignable(server.withTypeProvider().get( } )) +expectAssignable(server.withTypeProvider().route({ + url: '/', + method: 'POST', + schema: { + body: { + type: 'object', + properties: { + x: { type: 'number' }, + y: { type: 'string' }, + z: { type: 'boolean' } + } + } + } as const, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + }, + handler: (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +})) + +// infering schema `as const` + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: { + type: 'object', + properties: { + x: { type: 'number' }, + y: { type: 'string' }, + z: { type: 'boolean' } + } + } + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + } + }, + (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +)) + +expectAssignable(server.withTypeProvider().route({ + url: '/', + method: 'POST', + schema: { + body: { + type: 'object', + properties: { + x: { type: 'number' }, + y: { type: 'string' }, + z: { type: 'boolean' } + } + } + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + }, + handler: (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +})) + expectAssignable(server.withTypeProvider()) // ------------------------------------------------------------------- diff --git a/types/instance.d.ts b/types/instance.d.ts index cd0677ef97a..293d078d947 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -196,7 +196,7 @@ export interface FastifyInstance< route< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, + const SchemaCompiler extends FastifySchema = FastifySchema, >(opts: RouteOptions): FastifyInstance; get: RouteShorthandMethod; diff --git a/types/route.d.ts b/types/route.d.ts index aca35eafe0f..652a09e764b 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -121,16 +121,16 @@ export interface RouteShorthandMethod< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, > { - ( + ( path: string, opts: RouteShorthandOptions, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, opts: RouteShorthandOptionsWithHandler ): FastifyInstance; From 7d97dcdf2643bcfbdcf4850867139db8189b82e2 Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Fri, 29 Sep 2023 10:53:22 +0200 Subject: [PATCH 0473/1295] test(logger): splitting existing log tests (#5064) * test(logger): splitting existing tests to avoid pipeline failing for timeout * Apply suggestions from code review Co-authored-by: Manuel Spigolon * test(logger): used dash syntax to name utils --------- Co-authored-by: Aras Abbasi Co-authored-by: Manuel Spigolon --- test/logger/instantiation.test.js | 338 ++++++++ test/logger/logger-test-utils.js | 47 ++ test/logger/logging.test.js | 406 +++++++++ test/logger/options.test.js | 500 +++++++++++ test/logger/request.test.js | 292 +++++++ test/logger/response.test.js | 184 +++++ test/{serial => logger}/tap-parallel-not-ok | 0 test/serial/logger.0.test.js | 866 -------------------- test/serial/logger.1.test.js | 862 ------------------- 9 files changed, 1767 insertions(+), 1728 deletions(-) create mode 100644 test/logger/instantiation.test.js create mode 100644 test/logger/logger-test-utils.js create mode 100644 test/logger/logging.test.js create mode 100644 test/logger/options.test.js create mode 100644 test/logger/request.test.js create mode 100644 test/logger/response.test.js rename test/{serial => logger}/tap-parallel-not-ok (100%) delete mode 100644 test/serial/logger.0.test.js delete mode 100644 test/serial/logger.1.test.js diff --git a/test/logger/instantiation.test.js b/test/logger/instantiation.test.js new file mode 100644 index 00000000000..37cceed26bc --- /dev/null +++ b/test/logger/instantiation.test.js @@ -0,0 +1,338 @@ +'use strict' + +const stream = require('node:stream') +const os = require('node:os') +const fs = require('node:fs') + +const t = require('tap') +const split = require('split2') + +const { streamSym } = require('pino/lib/symbols') + +const Fastify = require('../../fastify') +const helper = require('../helper') +const { FST_ERR_LOG_INVALID_LOGGER } = require('../../lib/errors') +const { once, on } = stream +const { createTempFile, request } = require('./logger-test-utils') + +t.test('logger instantiation', (t) => { + t.setTimeout(60000) + + let localhost + let localhostForURL + + t.plan(11) + t.before(async function () { + [localhost, localhostForURL] = await helper.getLoopbackHost() + }) + + t.test('can use external logger instance', async (t) => { + const lines = [/^Server listening at /, /^incoming request$/, /^log success$/, /^request completed$/] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = require('pino')(stream) + + const fastify = Fastify({ logger }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/foo', function (req, reply) { + t.ok(req.log) + req.log.info('log success') + reply.send({ hello: 'world' }) + }) + + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo') + + for await (const [line] of on(stream, 'data')) { + const regex = lines.shift() + t.ok(regex.test(line.msg), '"' + line.msg + '" dont match "' + regex + '"') + if (lines.length === 0) break + } + }) + + t.test('should create a default logger if provided one is invalid', (t) => { + t.plan(8) + + const logger = new Date() + + const fastify = Fastify({ logger }) + t.teardown(fastify.close.bind(fastify)) + + t.equal(typeof fastify.log, 'object') + t.equal(typeof fastify.log.fatal, 'function') + t.equal(typeof fastify.log.error, 'function') + t.equal(typeof fastify.log.warn, 'function') + t.equal(typeof fastify.log.info, 'function') + t.equal(typeof fastify.log.debug, 'function') + t.equal(typeof fastify.log.trace, 'function') + t.equal(typeof fastify.log.child, 'function') + }) + + t.test('expose the logger', async (t) => { + t.plan(2) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + + t.ok(fastify.log) + t.same(typeof fastify.log, 'object') + }) + + t.test('Wrap IPv6 address in listening log message', async (t) => { + t.plan(1) + + const interfaces = os.networkInterfaces() + const ipv6 = Object.keys(interfaces) + .filter(name => name.substr(0, 2) === 'lo') + .map(name => interfaces[name]) + .reduce((list, set) => list.concat(set), []) + .filter(info => info.family === 'IPv6') + .map(info => info.address) + .shift() + + if (ipv6 === undefined) { + t.pass('No IPv6 loopback interface') + } else { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + await fastify.listen({ port: 0, host: ipv6 }) + + { + const [line] = await once(stream, 'data') + t.same(line.msg, `Server listening at http://[${ipv6}]:${fastify.server.address().port}`) + } + } + }) + + t.test('Do not wrap IPv4 address', async (t) => { + t.plan(1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + await fastify.listen({ port: 0, host: '127.0.0.1' }) + + { + const [line] = await once(stream, 'data') + t.same(line.msg, `Server listening at http://127.0.0.1:${fastify.server.address().port}`) + } + }) + + t.test('file option', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { reqId: /req-/, req: { method: 'GET', url: '/' }, msg: 'incoming request' }, + { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) + const { file, cleanup } = createTempFile(t) + + const fastify = Fastify({ + logger: { file } + }) + t.teardown(() => { + // cleanup the file after sonic-boom closed + // otherwise we may face racing condition + fastify.log[streamSym].once('close', cleanup) + // we must flush the stream ourself + // otherwise buffer may whole sonic-boom + fastify.log[streamSym].flushSync() + // end after flushing to actually close file + fastify.log[streamSym].end() + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (req, reply) { + t.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + // we already own the full log + const stream = fs.createReadStream(file).pipe(split(JSON.parse)) + t.teardown(stream.resume.bind(stream)) + + let id + for await (const [line] of on(stream, 'data')) { + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.equal(line.reqId, id) + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should be able to use a custom logger', (t) => { + t.plan(7) + + const logger = { + fatal: (msg) => { t.equal(msg, 'fatal') }, + error: (msg) => { t.equal(msg, 'error') }, + warn: (msg) => { t.equal(msg, 'warn') }, + info: (msg) => { t.equal(msg, 'info') }, + debug: (msg) => { t.equal(msg, 'debug') }, + trace: (msg) => { t.equal(msg, 'trace') }, + child: () => logger + } + + const fastify = Fastify({ logger }) + t.teardown(fastify.close.bind(fastify)) + + fastify.log.fatal('fatal') + fastify.log.error('error') + fastify.log.warn('warn') + fastify.log.info('info') + fastify.log.debug('debug') + fastify.log.trace('trace') + const child = fastify.log.child() + t.equal(child, logger) + }) + + t.test('should throw in case a partially matching logger is provided', async (t) => { + t.plan(1) + + try { + const fastify = Fastify({ logger: console }) + await fastify.ready() + } catch (err) { + t.equal( + err instanceof FST_ERR_LOG_INVALID_LOGGER, + true, + "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'." + ) + } + }) + + t.test('can use external logger instance with custom serializer', async (t) => { + const lines = [['level', 30], ['req', { url: '/foo' }], ['level', 30], ['res', { statusCode: 200 }]] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const logger = require('pino')({ + level: 'info', + serializers: { + req: function (req) { + return { + url: req.url + } + } + } + }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/foo', function (req, reply) { + t.ok(req.log) + req.log.info('log success') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo') + + for await (const [line] of on(stream, 'data')) { + const check = lines.shift() + const key = check[0] + const value = check[1] + t.same(line[key], value) + if (lines.length === 0) break + } + }) + + t.test('The logger should accept custom serializer', async (t) => { + const lines = [ + { msg: /^Server listening at / }, + { req: { url: '/custom' }, msg: 'incoming request' }, + { res: { statusCode: 500 }, msg: 'kaboom' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info', + serializers: { + req: function (req) { + return { + url: req.url + } + } + } + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/custom', function (req, reply) { + t.ok(req.log) + reply.send(new Error('kaboom')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/custom') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should throw in case the external logger provided does not have a child method', async (t) => { + t.plan(1) + const loggerInstance = { + info: console.info, + error: console.error, + debug: console.debug, + fatal: console.error, + warn: console.warn, + trace: console.trace + } + + try { + const fastify = Fastify({ logger: loggerInstance }) + await fastify.ready() + } catch (err) { + t.equal( + err instanceof FST_ERR_LOG_INVALID_LOGGER, + true, + "Invalid logger object provided. The logger instance should have these functions(s): 'child'." + ) + } + }) +}) diff --git a/test/logger/logger-test-utils.js b/test/logger/logger-test-utils.js new file mode 100644 index 00000000000..a62df088964 --- /dev/null +++ b/test/logger/logger-test-utils.js @@ -0,0 +1,47 @@ +'use strict' + +const http = require('node:http') +const os = require('node:os') +const fs = require('node:fs') + +const path = require('node:path') + +function createDeferredPromise () { + const promise = {} + promise.promise = new Promise(function (resolve) { + promise.resolve = resolve + }) + return promise +} + +let count = 0 +function createTempFile () { + const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${process.hrtime().toString()}-${count++}`) + function cleanup () { + try { + fs.unlinkSync(file) + } catch { } + } + return { file, cleanup } +} + +function request (url, cleanup = () => { }) { + const promise = createDeferredPromise() + http.get(url, (res) => { + const chunks = [] + // we consume the response + res.on('data', function (chunk) { + chunks.push(chunk) + }) + res.once('end', function () { + cleanup(res, Buffer.concat(chunks).toString()) + promise.resolve() + }) + }) + return promise.promise +} + +module.exports = { + request, + createTempFile +} diff --git a/test/logger/logging.test.js b/test/logger/logging.test.js new file mode 100644 index 00000000000..da4945ed9b1 --- /dev/null +++ b/test/logger/logging.test.js @@ -0,0 +1,406 @@ +'use strict' + +const stream = require('node:stream') + +const t = require('tap') +const split = require('split2') +const pino = require('pino') + +const Fastify = require('../../fastify') +const helper = require('../helper') +const { once, on } = stream +const { request } = require('./logger-test-utils') + +t.test('logging', (t) => { + t.setTimeout(60000) + + let localhost + let localhostForURL + + t.plan(12) + + t.before(async function () { + [localhost, localhostForURL] = await helper.getLoopbackHost() + }) + + t.test('The default 404 handler logs the incoming request', async (t) => { + const lines = ['incoming request', 'Route GET:/not-found not found', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'trace' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/not-found' }) + t.equal(response.statusCode, 404) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should not rely on raw request to log errors', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { level: 30, msg: 'incoming request' }, + { res: { statusCode: 415 }, msg: 'something happened' }, + { res: { statusCode: 415 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.status(415).send(new Error('something happened')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should log the error if no error handler is defined', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { msg: 'incoming request' }, + { level: 50, msg: 'a generic error' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.send(new Error('a generic error')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should log as info if error status code >= 400 and < 500 if no error handler is defined', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { msg: 'incoming request' }, + { level: 30, msg: 'a 400 error' }, + { res: { statusCode: 400 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/400', function (req, reply) { + t.ok(req.log) + reply.send(Object.assign(new Error('a 400 error'), { statusCode: 400 })) + }) + fastify.get('/503', function (req, reply) { + t.ok(req.log) + reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/400') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should log as error if error status code >= 500 if no error handler is defined', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { msg: 'incoming request' }, + { level: 50, msg: 'a 503 error' }, + { res: { statusCode: 503 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + fastify.get('/503', function (req, reply) { + t.ok(req.log) + reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/503') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should not log the error if error handler is defined and it does not error', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { level: 30, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 2) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.send(new Error('something happened')) + }) + fastify.setErrorHandler((err, req, reply) => { + t.ok(err) + reply.send('something bad happened') + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('reply.send logs an error if called twice in a row', async (t) => { + const lines = [ + 'incoming request', + 'request completed', + 'Reply was already sent, did you forget to "return reply" in "/" (GET)?', + 'Reply was already sent, did you forget to "return reply" in "/" (GET)?' + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const logger = pino(stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + reply.send({ hello: 'world2' }) + reply.send({ hello: 'world3' }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + + for await (const [line] of on(stream, 'data')) { + t.same(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should not log incoming request and outgoing response when disabled', async (t) => { + t.plan(3) + const stream = split(JSON.parse) + const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/500', (req, reply) => { + reply.code(500).send(Error('500 error')) + }) + + await fastify.ready() + + await fastify.inject({ method: 'GET', url: '/500' }) + + { + const [line] = await once(stream, 'data') + t.ok(line.reqId, 'reqId is defined') + t.equal(line.msg, '500 error', 'message is set') + } + + // no more readable data + t.equal(stream.readableLength, 0) + }) + + t.test('should not log incoming request and outgoing response for 404 onBadUrl when disabled', async (t) => { + t.plan(3) + const stream = split(JSON.parse) + const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) + t.teardown(fastify.close.bind(fastify)) + + await fastify.ready() + + await fastify.inject({ method: 'GET', url: '/%c0' }) + + { + const [line] = await once(stream, 'data') + t.ok(line.reqId, 'reqId is defined') + t.equal(line.msg, 'Route GET:/%c0 not found', 'message is set') + } + + // no more readable data + t.equal(stream.readableLength, 0) + }) + + t.test('defaults to info level', async (t) => { + const lines = [ + { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, + { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length * 2 + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (req, reply) { + t.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0 }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + let id + for await (const [line] of on(stream, 'data')) { + // we skip the non-request log + if (typeof line.reqId !== 'string') continue + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.equal(line.reqId, id) + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('test log stream', async (t) => { + const lines = [ + { msg: /^Server listening at / }, + { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, + { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (req, reply) { + t.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + let id + for await (const [line] of on(stream, 'data')) { + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.equal(line.reqId, id) + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('test error log stream', async (t) => { + const lines = [ + { msg: /^Server listening at / }, + { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, + { reqId: /req-/, res: { statusCode: 500 }, msg: 'kaboom' }, + { reqId: /req-/, res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.send(new Error('kaboom')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + let id + for await (const [line] of on(stream, 'data')) { + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.equal(line.reqId, id) + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) +}) diff --git a/test/logger/options.test.js b/test/logger/options.test.js new file mode 100644 index 00000000000..ba665821c36 --- /dev/null +++ b/test/logger/options.test.js @@ -0,0 +1,500 @@ +'use strict' + +const stream = require('node:stream') + +const t = require('tap') +const split = require('split2') +const pino = require('pino') + +const Fastify = require('../../fastify') +const { on } = stream + +t.test('logger options', (t) => { + t.setTimeout(60000) + + t.plan(12) + + t.test('logger can be silented', (t) => { + t.plan(17) + const fastify = Fastify({ + logger: false + }) + t.teardown(fastify.close.bind(fastify)) + t.ok(fastify.log) + t.equal(typeof fastify.log, 'object') + t.equal(typeof fastify.log.fatal, 'function') + t.equal(typeof fastify.log.error, 'function') + t.equal(typeof fastify.log.warn, 'function') + t.equal(typeof fastify.log.info, 'function') + t.equal(typeof fastify.log.debug, 'function') + t.equal(typeof fastify.log.trace, 'function') + t.equal(typeof fastify.log.child, 'function') + + const childLog = fastify.log.child() + + t.equal(typeof childLog, 'object') + t.equal(typeof childLog.fatal, 'function') + t.equal(typeof childLog.error, 'function') + t.equal(typeof childLog.warn, 'function') + t.equal(typeof childLog.info, 'function') + t.equal(typeof childLog.debug, 'function') + t.equal(typeof childLog.trace, 'function') + t.equal(typeof childLog.child, 'function') + }) + + t.test('Should set a custom logLevel for a plugin', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed'] + t.plan(lines.length + 2) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + req.log.info('Not Exist') // we should not see this log + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/plugin', (req, reply) => { + req.log.info('Hello') // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/plugin' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.same(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set a custom logSerializers for a plugin', async (t) => { + const lines = ['incoming request', 'XHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.get('/plugin', (req, reply) => { + req.log.info({ test: 'Hello' }) // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info', logSerializers: { test: value => 'X' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/plugin' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + // either test or msg + t.equal(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set a custom logLevel for every plugin', async (t) => { + const lines = ['incoming request', 'info', 'request completed', 'incoming request', 'debug', 'request completed'] + t.plan(lines.length * 2 + 3) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + req.log.warn('Hello') // we should not see this log + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/info', (req, reply) => { + req.log.info('info') // we should see this log + req.log.debug('hidden log') + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info' }) + + fastify.register(function (instance, opts, done) { + instance.get('/debug', (req, reply) => { + req.log.debug('debug') // we should see this log + req.log.trace('hidden log') + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'debug' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/info' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/debug' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.ok(line.level === 30 || line.level === 20) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set a custom logSerializers for every plugin', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed', 'incoming request', 'XHello', 'request completed', 'incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 3) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + req.log.warn({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/test1', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + fastify.register(function (instance, opts, done) { + instance.get('/test2', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'Z' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/test1' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/test2' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should override serializers from route', async (t) => { + const lines = ['incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.get('/', { + logSerializers: { + test: value => 'Z' + value // should override + } + }, (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should override serializers from plugin', async (t) => { + const lines = ['incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.register(context1, { + logSerializers: { + test: value => 'Z' + value // should override + } + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + function context1 (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should increase the log level for a specific plugin', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.error('Hello') // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'error' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.level, 50) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set the log level for the customized 404 handler', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'warn' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.setNotFoundHandler(function (req, reply) { + req.log.error('Hello') + reply.code(404).send() + }) + done() + }, { logLevel: 'error' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + t.equal(response.statusCode, 404) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.level, 50) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set the log level for the customized 500 handler', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'warn' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.error('kaboom') + reply.send(new Error('kaboom')) + }) + + instance.setErrorHandler(function (e, request, reply) { + reply.log.fatal('Hello') + reply.code(500).send() + }) + done() + }, { logLevel: 'fatal' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + t.equal(response.statusCode, 500) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.level, 60) + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should set a custom log level for a specific route', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed'] + t.plan(lines.length + 2) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/log', { logLevel: 'info' }, (req, reply) => { + req.log.info('Hello') + reply.send({ hello: 'world' }) + }) + + fastify.get('/no-log', (req, reply) => { + req.log.info('Hello') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/log' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/no-log' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.equal(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should pass when using unWritable props in the logger option', (t) => { + t.plan(8) + const fastify = Fastify({ + logger: Object.defineProperty({}, 'level', { value: 'info' }) + }) + t.teardown(fastify.close.bind(fastify)) + + t.equal(typeof fastify.log, 'object') + t.equal(typeof fastify.log.fatal, 'function') + t.equal(typeof fastify.log.error, 'function') + t.equal(typeof fastify.log.warn, 'function') + t.equal(typeof fastify.log.info, 'function') + t.equal(typeof fastify.log.debug, 'function') + t.equal(typeof fastify.log.trace, 'function') + t.equal(typeof fastify.log.child, 'function') + }) +}) diff --git a/test/logger/request.test.js b/test/logger/request.test.js new file mode 100644 index 00000000000..a1702a4fe9e --- /dev/null +++ b/test/logger/request.test.js @@ -0,0 +1,292 @@ +'use strict' + +const stream = require('node:stream') + +const t = require('tap') +const split = require('split2') + +const Fastify = require('../../fastify') +const helper = require('../helper') +const { on } = stream +const { request } = require('./logger-test-utils') + +t.test('request', (t) => { + t.setTimeout(60000) + + let localhost + + t.plan(7) + t.before(async function () { + [localhost] = await helper.getLoopbackHost() + }) + + t.test('The request id header key can be customized', async (t) => { + const lines = ['incoming request', 'some log message', 'request completed'] + t.plan(lines.length * 2 + 2) + const REQUEST_ID = '42' + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id' + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + t.equal(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, REQUEST_ID) + + for await (const [line] of on(stream, 'data')) { + t.equal(line.reqId, REQUEST_ID) + t.equal(line.msg, lines.shift(), 'message is set') + if (lines.length === 0) break + } + }) + + t.test('The request id header key can be ignored', async (t) => { + const lines = ['incoming request', 'some log message', 'request completed'] + t.plan(lines.length * 2 + 2) + const REQUEST_ID = 'ignore-me' + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: false + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', (req, reply) => { + t.equal(req.id, 'req-1') + req.log.info('some log message') + reply.send({ id: req.id }) + }) + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, 'req-1') + + for await (const [line] of on(stream, 'data')) { + t.equal(line.reqId, 'req-1') + t.equal(line.msg, lines.shift(), 'message is set') + if (lines.length === 0) break + } + }) + + t.test('The request id header key can be customized along with a custom id generator', async (t) => { + const REQUEST_ID = '42' + const matches = [ + { reqId: REQUEST_ID, msg: /incoming request/ }, + { reqId: REQUEST_ID, msg: /some log message/ }, + { reqId: REQUEST_ID, msg: /request completed/ }, + { reqId: 'foo', msg: /incoming request/ }, + { reqId: 'foo', msg: /some log message 2/ }, + { reqId: 'foo', msg: /request completed/ } + ] + t.plan(matches.length + 4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id', + genReqId (req) { + return 'foo' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/one', (req, reply) => { + t.equal(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + fastify.get('/two', (req, reply) => { + t.equal(req.id, 'foo') + req.log.info('some log message 2') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, REQUEST_ID) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/two' }) + const body = await response.json() + t.equal(body.id, 'foo') + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, matches.shift()) + if (matches.length === 0) break + } + }) + + t.test('The request id header key can be ignored along with a custom id generator', async (t) => { + const REQUEST_ID = 'ignore-me' + const matches = [ + { reqId: 'foo', msg: /incoming request/ }, + { reqId: 'foo', msg: /some log message/ }, + { reqId: 'foo', msg: /request completed/ }, + { reqId: 'foo', msg: /incoming request/ }, + { reqId: 'foo', msg: /some log message 2/ }, + { reqId: 'foo', msg: /request completed/ } + ] + t.plan(matches.length + 4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: false, + genReqId (req) { + return 'foo' + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/one', (req, reply) => { + t.equal(req.id, 'foo') + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + fastify.get('/two', (req, reply) => { + t.equal(req.id, 'foo') + req.log.info('some log message 2') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, 'foo') + } + + { + const response = await fastify.inject({ method: 'GET', url: '/two' }) + const body = await response.json() + t.equal(body.id, 'foo') + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, matches.shift()) + if (matches.length === 0) break + } + }) + + t.test('The request id log label can be changed', async (t) => { + const REQUEST_ID = '42' + const matches = [ + { traceId: REQUEST_ID, msg: /incoming request/ }, + { traceId: REQUEST_ID, msg: /some log message/ }, + { traceId: REQUEST_ID, msg: /request completed/ } + ] + t.plan(matches.length + 2) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id', + requestIdLogLabel: 'traceId' + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/one', (req, reply) => { + t.equal(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.equal(body.id, REQUEST_ID) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, matches.shift()) + if (matches.length === 0) break + } + }) + + t.test('should redact the authorization header if so specified', async (t) => { + const lines = [ + { msg: /Server listening at/ }, + { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + redact: ['req.headers.authorization'], + level: 'info', + serializers: { + req (req) { + return { + method: req.method, + url: req.url, + headers: req.headers, + hostname: req.hostname, + remoteAddress: req.ip, + remotePort: req.socket.remotePort + } + } + } + } + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (req, reply) { + t.same(req.headers.authorization, 'Bearer abcde') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request({ + method: 'GET', + path: '/', + host: localhost, + port: fastify.server.address().port, + headers: { + authorization: 'Bearer abcde' + } + }, function (response, body) { + t.equal(response.statusCode, 200) + t.same(body, JSON.stringify({ hello: 'world' })) + }) + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should not throw error when serializing custom req', (t) => { + t.plan(1) + + const lines = [] + const dest = new stream.Writable({ + write: function (chunk, enc, cb) { + lines.push(JSON.parse(chunk)) + cb() + } + }) + const fastify = Fastify({ logger: { level: 'info', stream: dest } }) + t.teardown(fastify.close.bind(fastify)) + + fastify.log.info({ req: {} }) + + t.same(lines[0].req, {}) + }) +}) diff --git a/test/logger/response.test.js b/test/logger/response.test.js new file mode 100644 index 00000000000..5371287f9b5 --- /dev/null +++ b/test/logger/response.test.js @@ -0,0 +1,184 @@ +'use strict' + +const stream = require('node:stream') + +const t = require('tap') +const split = require('split2') +const pino = require('pino') + +const Fastify = require('../../fastify') +const { on } = stream + +t.test('response serialization', (t) => { + t.setTimeout(60000) + + t.plan(4) + + t.test('Should use serializers from plugin and route', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ level: 'info' }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(context1, { + logSerializers: { test: value => 'X' + value } + }) + + function context1 (instance, opts, done) { + instance.get('/', { + logSerializers: { + test2: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should use serializers from instance fastify and route', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ + level: 'info', + serializers: { + test: value => 'X' + value, + test2: value => 'This should be override - ' + value + } + }, stream) + const fastify = Fastify({ + logger + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', { + logSerializers: { + test2: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('Should use serializers inherit from contexts', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'YHello', test3: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const logger = pino({ + level: 'info', + serializers: { + test: value => 'X' + value + } + }, stream) + + const fastify = Fastify({ logger }) + t.teardown(fastify.close.bind(fastify)) + + fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } }) + + function context1 (instance, opts, done) { + instance.get('/', { + logSerializers: { + test3: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello', test3: 'Hello' }) // { test: 'XHello', test2: 'YHello', test3: 'ZHello' } + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.same(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) + + t.test('should serialize request and response', async (t) => { + const lines = [ + { req: { method: 'GET', url: '/500' }, msg: 'incoming request' }, + { req: { method: 'GET', url: '/500' }, msg: '500 error' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const fastify = Fastify({ logger: { level: 'info', stream } }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/500', (req, reply) => { + reply.code(500).send(Error('500 error')) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/500' }) + t.equal(response.statusCode, 500) + } + + for await (const [line] of on(stream, 'data')) { + t.match(line, lines.shift()) + if (lines.length === 0) break + } + }) +}) diff --git a/test/serial/tap-parallel-not-ok b/test/logger/tap-parallel-not-ok similarity index 100% rename from test/serial/tap-parallel-not-ok rename to test/logger/tap-parallel-not-ok diff --git a/test/serial/logger.0.test.js b/test/serial/logger.0.test.js deleted file mode 100644 index 3ac6c2ad987..00000000000 --- a/test/serial/logger.0.test.js +++ /dev/null @@ -1,866 +0,0 @@ -'use strict' - -const http = require('node:http') -const stream = require('node:stream') - -const t = require('tap') -const split = require('split2') -const pino = require('pino') - -const Fastify = require('../../fastify') -const helper = require('../helper') -const { FST_ERR_LOG_INVALID_LOGGER } = require('../../lib/errors') -const { on } = stream - -function createDeferredPromise () { - const promise = {} - promise.promise = new Promise(function (resolve) { - promise.resolve = resolve - }) - return promise -} - -function request (url, cleanup = () => { }) { - const promise = createDeferredPromise() - http.get(url, (res) => { - const chunks = [] - // we consume the response - res.on('data', function (chunk) { - chunks.push(chunk) - }) - res.once('end', function () { - cleanup(res, Buffer.concat(chunks).toString()) - promise.resolve() - }) - }) - return promise.promise -} - -t.test('test log stream', (t) => { - t.setTimeout(60000) - - let localhost - let localhostForURL - - t.plan(22) - - t.before(async function () { - [localhost, localhostForURL] = await helper.getLoopbackHost() - }) - - t.test('defaults to info level', async (t) => { - const lines = [ - { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, - { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } - ] - t.plan(lines.length * 2 + 1) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', function (req, reply) { - t.ok(req.log) - reply.send({ hello: 'world' }) - }) - - await fastify.ready() - await fastify.listen({ port: 0 }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port) - - let id - for await (const [line] of on(stream, 'data')) { - // we skip the non-request log - if (typeof line.reqId !== 'string') continue - if (id === undefined && line.reqId) id = line.reqId - if (id !== undefined && line.reqId) t.equal(line.reqId, id) - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('test log stream', async (t) => { - const lines = [ - { msg: /^Server listening at / }, - { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, - { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } - ] - t.plan(lines.length + 3) - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', function (req, reply) { - t.ok(req.log) - reply.send({ hello: 'world' }) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port) - - let id - for await (const [line] of on(stream, 'data')) { - if (id === undefined && line.reqId) id = line.reqId - if (id !== undefined && line.reqId) t.equal(line.reqId, id) - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('test error log stream', async (t) => { - const lines = [ - { msg: /^Server listening at / }, - { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, - { reqId: /req-/, res: { statusCode: 500 }, msg: 'kaboom' }, - { reqId: /req-/, res: { statusCode: 500 }, msg: 'request completed' } - ] - t.plan(lines.length + 4) - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/error', function (req, reply) { - t.ok(req.log) - reply.send(new Error('kaboom')) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') - - let id - for await (const [line] of on(stream, 'data')) { - if (id === undefined && line.reqId) id = line.reqId - if (id !== undefined && line.reqId) t.equal(line.reqId, id) - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('can use external logger instance', async (t) => { - const lines = [/^Server listening at /, /^incoming request$/, /^log success$/, /^request completed$/] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - - const logger = require('pino')(stream) - - const fastify = Fastify({ logger }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/foo', function (req, reply) { - t.ok(req.log) - req.log.info('log success') - reply.send({ hello: 'world' }) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo') - - for await (const [line] of on(stream, 'data')) { - const regex = lines.shift() - t.ok(regex.test(line.msg), '"' + line.msg + '" dont match "' + regex + '"') - if (lines.length === 0) break - } - }) - - t.test('can use external logger instance with custom serializer', async (t) => { - const lines = [['level', 30], ['req', { url: '/foo' }], ['level', 30], ['res', { statusCode: 200 }]] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - const logger = require('pino')({ - level: 'info', - serializers: { - req: function (req) { - return { - url: req.url - } - } - } - }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/foo', function (req, reply) { - t.ok(req.log) - req.log.info('log success') - reply.send({ hello: 'world' }) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo') - - for await (const [line] of on(stream, 'data')) { - const check = lines.shift() - const key = check[0] - const value = check[1] - t.same(line[key], value) - if (lines.length === 0) break - } - }) - - t.test('should throw in case the external logger provided does not have a child method', async (t) => { - t.plan(1) - const loggerInstance = { - info: console.info, - error: console.error, - debug: console.debug, - fatal: console.error, - warn: console.warn, - trace: console.trace - } - - try { - const fastify = Fastify({ logger: loggerInstance }) - await fastify.ready() - } catch (err) { - t.equal( - err instanceof FST_ERR_LOG_INVALID_LOGGER, - true, - "Invalid logger object provided. The logger instance should have these functions(s): 'child'." - ) - } - }) - - t.test('should throw in case a partially matching logger is provided', async (t) => { - t.plan(1) - - try { - const fastify = Fastify({ logger: console }) - await fastify.ready() - } catch (err) { - t.equal( - err instanceof FST_ERR_LOG_INVALID_LOGGER, - true, - "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'." - ) - } - }) - - t.test('expose the logger', async (t) => { - t.plan(2) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - - await fastify.ready() - - t.ok(fastify.log) - t.same(typeof fastify.log, 'object') - }) - - t.test('The request id header key can be customized', async (t) => { - const lines = ['incoming request', 'some log message', 'request completed'] - t.plan(lines.length * 2 + 2) - const REQUEST_ID = '42' - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: 'my-custom-request-id' - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } }) - const body = await response.json() - t.equal(body.id, REQUEST_ID) - - for await (const [line] of on(stream, 'data')) { - t.equal(line.reqId, REQUEST_ID) - t.equal(line.msg, lines.shift(), 'message is set') - if (lines.length === 0) break - } - }) - - t.test('The request id header key can be ignored', async (t) => { - const lines = ['incoming request', 'some log message', 'request completed'] - t.plan(lines.length * 2 + 2) - const REQUEST_ID = 'ignore-me' - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: false - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', (req, reply) => { - t.equal(req.id, 'req-1') - req.log.info('some log message') - reply.send({ id: req.id }) - }) - const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'request-id': REQUEST_ID } }) - const body = await response.json() - t.equal(body.id, 'req-1') - - for await (const [line] of on(stream, 'data')) { - t.equal(line.reqId, 'req-1') - t.equal(line.msg, lines.shift(), 'message is set') - if (lines.length === 0) break - } - }) - - t.test('The request id header key can be customized along with a custom id generator', async (t) => { - const REQUEST_ID = '42' - const matches = [ - { reqId: REQUEST_ID, msg: /incoming request/ }, - { reqId: REQUEST_ID, msg: /some log message/ }, - { reqId: REQUEST_ID, msg: /request completed/ }, - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message 2/ }, - { reqId: 'foo', msg: /request completed/ } - ] - t.plan(matches.length + 4) - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: 'my-custom-request-id', - genReqId (req) { - return 'foo' - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/one', (req, reply) => { - t.equal(req.id, REQUEST_ID) - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - fastify.get('/two', (req, reply) => { - t.equal(req.id, 'foo') - req.log.info('some log message 2') - reply.send({ id: req.id }) - }) - - { - const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) - const body = await response.json() - t.equal(body.id, REQUEST_ID) - } - - { - const response = await fastify.inject({ method: 'GET', url: '/two' }) - const body = await response.json() - t.equal(body.id, 'foo') - } - - for await (const [line] of on(stream, 'data')) { - t.match(line, matches.shift()) - if (matches.length === 0) break - } - }) - - t.test('The request id header key can be ignored along with a custom id generator', async (t) => { - const REQUEST_ID = 'ignore-me' - const matches = [ - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message/ }, - { reqId: 'foo', msg: /request completed/ }, - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message 2/ }, - { reqId: 'foo', msg: /request completed/ } - ] - t.plan(matches.length + 4) - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: false, - genReqId (req) { - return 'foo' - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/one', (req, reply) => { - t.equal(req.id, 'foo') - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - fastify.get('/two', (req, reply) => { - t.equal(req.id, 'foo') - req.log.info('some log message 2') - reply.send({ id: req.id }) - }) - - { - const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'request-id': REQUEST_ID } }) - const body = await response.json() - t.equal(body.id, 'foo') - } - - { - const response = await fastify.inject({ method: 'GET', url: '/two' }) - const body = await response.json() - t.equal(body.id, 'foo') - } - - for await (const [line] of on(stream, 'data')) { - t.match(line, matches.shift()) - if (matches.length === 0) break - } - }) - - t.test('The request id log label can be changed', async (t) => { - const REQUEST_ID = '42' - const matches = [ - { traceId: REQUEST_ID, msg: /incoming request/ }, - { traceId: REQUEST_ID, msg: /some log message/ }, - { traceId: REQUEST_ID, msg: /request completed/ } - ] - t.plan(matches.length + 2) - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { stream, level: 'info' }, - requestIdHeader: 'my-custom-request-id', - requestIdLogLabel: 'traceId' - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/one', (req, reply) => { - t.equal(req.id, REQUEST_ID) - req.log.info('some log message') - reply.send({ id: req.id }) - }) - - { - const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) - const body = await response.json() - t.equal(body.id, REQUEST_ID) - } - - for await (const [line] of on(stream, 'data')) { - t.match(line, matches.shift()) - if (matches.length === 0) break - } - }) - - t.test('The logger should accept custom serializer', async (t) => { - const lines = [ - { msg: /^Server listening at / }, - { req: { url: '/custom' }, msg: 'incoming request' }, - { res: { statusCode: 500 }, msg: 'kaboom' }, - { res: { statusCode: 500 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info', - serializers: { - req: function (req) { - return { - url: req.url - } - } - } - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/custom', function (req, reply) { - t.ok(req.log) - reply.send(new Error('kaboom')) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/custom') - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('reply.send logs an error if called twice in a row', async (t) => { - const lines = [ - 'incoming request', - 'request completed', - 'Reply was already sent, did you forget to "return reply" in "/" (GET)?', - 'Reply was already sent, did you forget to "return reply" in "/" (GET)?' - ] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - const logger = pino(stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', (req, reply) => { - reply.send({ hello: 'world' }) - reply.send({ hello: 'world2' }) - reply.send({ hello: 'world3' }) - }) - - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - - for await (const [line] of on(stream, 'data')) { - t.same(line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('logger can be silented', (t) => { - t.plan(17) - const fastify = Fastify({ - logger: false - }) - t.teardown(fastify.close.bind(fastify)) - t.ok(fastify.log) - t.equal(typeof fastify.log, 'object') - t.equal(typeof fastify.log.fatal, 'function') - t.equal(typeof fastify.log.error, 'function') - t.equal(typeof fastify.log.warn, 'function') - t.equal(typeof fastify.log.info, 'function') - t.equal(typeof fastify.log.debug, 'function') - t.equal(typeof fastify.log.trace, 'function') - t.equal(typeof fastify.log.child, 'function') - - const childLog = fastify.log.child() - - t.equal(typeof childLog, 'object') - t.equal(typeof childLog.fatal, 'function') - t.equal(typeof childLog.error, 'function') - t.equal(typeof childLog.warn, 'function') - t.equal(typeof childLog.info, 'function') - t.equal(typeof childLog.debug, 'function') - t.equal(typeof childLog.trace, 'function') - t.equal(typeof childLog.child, 'function') - }) - - t.test('Should set a custom logLevel for a plugin', async (t) => { - const lines = ['incoming request', 'Hello', 'request completed'] - t.plan(lines.length + 2) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'error' }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', (req, reply) => { - req.log.info('Not Exist') // we should not see this log - reply.send({ hello: 'world' }) - }) - - fastify.register(function (instance, opts, done) { - instance.get('/plugin', (req, reply) => { - req.log.info('Hello') // we should see this log - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'info' }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - { - const response = await fastify.inject({ method: 'GET', url: '/plugin' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.same(line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should set a custom logSerializers for a plugin', async (t) => { - const lines = ['incoming request', 'XHello', 'request completed'] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'error' }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.register(function (instance, opts, done) { - instance.get('/plugin', (req, reply) => { - req.log.info({ test: 'Hello' }) // we should see this log - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'info', logSerializers: { test: value => 'X' + value } }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/plugin' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - // either test or msg - t.equal(line.test || line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should set a custom logLevel for every plugin', async (t) => { - const lines = ['incoming request', 'info', 'request completed', 'incoming request', 'debug', 'request completed'] - t.plan(lines.length * 2 + 3) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'error' }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', (req, reply) => { - req.log.warn('Hello') // we should not see this log - reply.send({ hello: 'world' }) - }) - - fastify.register(function (instance, opts, done) { - instance.get('/info', (req, reply) => { - req.log.info('info') // we should see this log - req.log.debug('hidden log') - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'info' }) - - fastify.register(function (instance, opts, done) { - instance.get('/debug', (req, reply) => { - req.log.debug('debug') // we should see this log - req.log.trace('hidden log') - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'debug' }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - { - const response = await fastify.inject({ method: 'GET', url: '/info' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - { - const response = await fastify.inject({ method: 'GET', url: '/debug' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.ok(line.level === 30 || line.level === 20) - t.equal(line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should set a custom logSerializers for every plugin', async (t) => { - const lines = ['incoming request', 'Hello', 'request completed', 'incoming request', 'XHello', 'request completed', 'incoming request', 'ZHello', 'request completed'] - t.plan(lines.length + 3) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'info' }, stream) - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', (req, reply) => { - req.log.warn({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - - fastify.register(function (instance, opts, done) { - instance.get('/test1', (req, reply) => { - req.log.info({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - done() - }, { logSerializers: { test: value => 'X' + value } }) - - fastify.register(function (instance, opts, done) { - instance.get('/test2', (req, reply) => { - req.log.info({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - done() - }, { logSerializers: { test: value => 'Z' + value } }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - { - const response = await fastify.inject({ method: 'GET', url: '/test1' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - { - const response = await fastify.inject({ method: 'GET', url: '/test2' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.equal(line.test || line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should override serializers from route', async (t) => { - const lines = ['incoming request', 'ZHello', 'request completed'] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'info' }, stream) - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.register(function (instance, opts, done) { - instance.get('/', { - logSerializers: { - test: value => 'Z' + value // should override - } - }, (req, reply) => { - req.log.info({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - done() - }, { logSerializers: { test: value => 'X' + value } }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.equal(line.test || line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should override serializers from plugin', async (t) => { - const lines = ['incoming request', 'ZHello', 'request completed'] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'info' }, stream) - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.register(function (instance, opts, done) { - instance.register(context1, { - logSerializers: { - test: value => 'Z' + value // should override - } - }) - done() - }, { logSerializers: { test: value => 'X' + value } }) - - function context1 (instance, opts, done) { - instance.get('/', (req, reply) => { - req.log.info({ test: 'Hello' }) - reply.send({ hello: 'world' }) - }) - done() - } - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.equal(line.test || line.msg, lines.shift()) - if (lines.length === 0) break - } - }) -}) diff --git a/test/serial/logger.1.test.js b/test/serial/logger.1.test.js deleted file mode 100644 index 03a82cb53a2..00000000000 --- a/test/serial/logger.1.test.js +++ /dev/null @@ -1,862 +0,0 @@ -'use strict' - -const http = require('node:http') -const stream = require('node:stream') -const os = require('node:os') -const fs = require('node:fs') - -const t = require('tap') -const split = require('split2') -const pino = require('pino') -const path = require('node:path') -const { streamSym } = require('pino/lib/symbols') - -const Fastify = require('../../fastify') -const helper = require('../helper') -const { once, on } = stream - -function createDeferredPromise () { - const promise = {} - promise.promise = new Promise(function (resolve) { - promise.resolve = resolve - }) - return promise -} - -let count = 0 -function createTempFile () { - const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${process.hrtime().toString()}-${count++}`) - function cleanup () { - try { - fs.unlinkSync(file) - } catch { } - } - return { file, cleanup } -} - -function request (url, cleanup = () => { }) { - const promise = createDeferredPromise() - http.get(url, (res) => { - const chunks = [] - // we consume the response - res.on('data', function (chunk) { - chunks.push(chunk) - }) - res.once('end', function () { - cleanup(res, Buffer.concat(chunks).toString()) - promise.resolve() - }) - }) - return promise.promise -} - -t.test('test log stream', (t) => { - t.setTimeout(60000) - - let localhost - let localhostForURL - - t.plan(24) - - t.before(async function () { - [localhost, localhostForURL] = await helper.getLoopbackHost() - }) - - t.test('Should use serializers from plugin and route', async (t) => { - const lines = [ - { msg: 'incoming request' }, - { test: 'XHello', test2: 'ZHello' }, - { msg: 'request completed' } - ] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'info' }, stream) - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.register(context1, { - logSerializers: { test: value => 'X' + value } - }) - - function context1 (instance, opts, done) { - instance.get('/', { - logSerializers: { - test2: value => 'Z' + value - } - }, (req, reply) => { - req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } - reply.send({ hello: 'world' }) - }) - done() - } - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should use serializers from instance fastify and route', async (t) => { - const lines = [ - { msg: 'incoming request' }, - { test: 'XHello', test2: 'ZHello' }, - { msg: 'request completed' } - ] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - - const logger = pino({ - level: 'info', - serializers: { - test: value => 'X' + value, - test2: value => 'This should be override - ' + value - } - }, stream) - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', { - logSerializers: { - test2: value => 'Z' + value - } - }, (req, reply) => { - req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } - reply.send({ hello: 'world' }) - }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should use serializers inherit from contexts', async (t) => { - const lines = [ - { msg: 'incoming request' }, - { test: 'XHello', test2: 'YHello', test3: 'ZHello' }, - { msg: 'request completed' } - ] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - - const logger = pino({ - level: 'info', - serializers: { - test: value => 'X' + value - } - }, stream) - - const fastify = Fastify({ logger }) - t.teardown(fastify.close.bind(fastify)) - - fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } }) - - function context1 (instance, opts, done) { - instance.get('/', { - logSerializers: { - test3: value => 'Z' + value - } - }, (req, reply) => { - req.log.info({ test: 'Hello', test2: 'Hello', test3: 'Hello' }) // { test: 'XHello', test2: 'YHello', test3: 'ZHello' } - reply.send({ hello: 'world' }) - }) - done() - } - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should increase the log level for a specific plugin', async (t) => { - const lines = ['Hello'] - t.plan(lines.length * 2 + 1) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'info' }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.register(function (instance, opts, done) { - instance.get('/', (req, reply) => { - req.log.error('Hello') // we should see this log - reply.send({ hello: 'world' }) - }) - done() - }, { logLevel: 'error' }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.equal(line.level, 50) - t.equal(line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should set the log level for the customized 404 handler', async (t) => { - const lines = ['Hello'] - t.plan(lines.length * 2 + 1) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'warn' }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.register(function (instance, opts, done) { - instance.setNotFoundHandler(function (req, reply) { - req.log.error('Hello') - reply.code(404).send() - }) - done() - }, { logLevel: 'error' }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - t.equal(response.statusCode, 404) - } - - for await (const [line] of on(stream, 'data')) { - t.equal(line.level, 50) - t.equal(line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should set the log level for the customized 500 handler', async (t) => { - const lines = ['Hello'] - t.plan(lines.length * 2 + 1) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'warn' }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.register(function (instance, opts, done) { - instance.get('/', (req, reply) => { - req.log.error('kaboom') - reply.send(new Error('kaboom')) - }) - - instance.setErrorHandler(function (e, request, reply) { - reply.log.fatal('Hello') - reply.code(500).send() - }) - done() - }, { logLevel: 'fatal' }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/' }) - t.equal(response.statusCode, 500) - } - - for await (const [line] of on(stream, 'data')) { - t.equal(line.level, 60) - t.equal(line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Should set a custom log level for a specific route', async (t) => { - const lines = ['incoming request', 'Hello', 'request completed'] - t.plan(lines.length + 2) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'error' }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/log', { logLevel: 'info' }, (req, reply) => { - req.log.info('Hello') - reply.send({ hello: 'world' }) - }) - - fastify.get('/no-log', (req, reply) => { - req.log.info('Hello') - reply.send({ hello: 'world' }) - }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/log' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - { - const response = await fastify.inject({ method: 'GET', url: '/no-log' }) - const body = await response.json() - t.same(body, { hello: 'world' }) - } - - for await (const [line] of on(stream, 'data')) { - t.equal(line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('The default 404 handler logs the incoming request', async (t) => { - const lines = ['incoming request', 'Route GET:/not-found not found', 'request completed'] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - - const logger = pino({ level: 'trace' }, stream) - - const fastify = Fastify({ - logger - }) - t.teardown(fastify.close.bind(fastify)) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/not-found' }) - t.equal(response.statusCode, 404) - } - - for await (const [line] of on(stream, 'data')) { - t.equal(line.msg, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('should serialize request and response', async (t) => { - const lines = [ - { req: { method: 'GET', url: '/500' }, msg: 'incoming request' }, - { req: { method: 'GET', url: '/500' }, msg: '500 error' }, - { msg: 'request completed' } - ] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - const fastify = Fastify({ logger: { level: 'info', stream } }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/500', (req, reply) => { - reply.code(500).send(Error('500 error')) - }) - - await fastify.ready() - - { - const response = await fastify.inject({ method: 'GET', url: '/500' }) - t.equal(response.statusCode, 500) - } - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('Wrap IPv6 address in listening log message', async (t) => { - t.plan(1) - - const interfaces = os.networkInterfaces() - const ipv6 = Object.keys(interfaces) - .filter(name => name.substr(0, 2) === 'lo') - .map(name => interfaces[name]) - .reduce((list, set) => list.concat(set), []) - .filter(info => info.family === 'IPv6') - .map(info => info.address) - .shift() - - if (ipv6 === undefined) { - t.pass('No IPv6 loopback interface') - } else { - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - - await fastify.ready() - await fastify.listen({ port: 0, host: ipv6 }) - - { - const [line] = await once(stream, 'data') - t.same(line.msg, `Server listening at http://[${ipv6}]:${fastify.server.address().port}`) - } - } - }) - - t.test('Do not wrap IPv4 address', async (t) => { - t.plan(1) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - - await fastify.ready() - await fastify.listen({ port: 0, host: '127.0.0.1' }) - - { - const [line] = await once(stream, 'data') - t.same(line.msg, `Server listening at http://127.0.0.1:${fastify.server.address().port}`) - } - }) - - t.test('file option', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { reqId: /req-/, req: { method: 'GET', url: '/' }, msg: 'incoming request' }, - { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } - ] - t.plan(lines.length + 3) - const { file, cleanup } = createTempFile(t) - - const fastify = Fastify({ - logger: { file } - }) - t.teardown(() => { - // cleanup the file after sonic-boom closed - // otherwise we may face racing condition - fastify.log[streamSym].once('close', cleanup) - // we must flush the stream ourself - // otherwise buffer may whole sonic-boom - fastify.log[streamSym].flushSync() - // end after flushing to actually close file - fastify.log[streamSym].end() - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', function (req, reply) { - t.ok(req.log) - reply.send({ hello: 'world' }) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port) - - // we already own the full log - const stream = fs.createReadStream(file).pipe(split(JSON.parse)) - t.teardown(stream.resume.bind(stream)) - - let id - for await (const [line] of on(stream, 'data')) { - if (id === undefined && line.reqId) id = line.reqId - if (id !== undefined && line.reqId) t.equal(line.reqId, id) - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('should log the error if no error handler is defined', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { msg: 'incoming request' }, - { level: 50, msg: 'a generic error' }, - { res: { statusCode: 500 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) - - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/error', function (req, reply) { - t.ok(req.log) - reply.send(new Error('a generic error')) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('should log as info if error status code >= 400 and < 500 if no error handler is defined', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { msg: 'incoming request' }, - { level: 30, msg: 'a 400 error' }, - { res: { statusCode: 400 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/400', function (req, reply) { - t.ok(req.log) - reply.send(Object.assign(new Error('a 400 error'), { statusCode: 400 })) - }) - fastify.get('/503', function (req, reply) { - t.ok(req.log) - reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/400') - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('should log as error if error status code >= 500 if no error handler is defined', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { msg: 'incoming request' }, - { level: 50, msg: 'a 503 error' }, - { res: { statusCode: 503 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - fastify.get('/503', function (req, reply) { - t.ok(req.log) - reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/503') - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('should not log the error if error handler is defined and it does not error', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { level: 30, msg: 'incoming request' }, - { res: { statusCode: 200 }, msg: 'request completed' } - ] - t.plan(lines.length + 2) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - fastify.get('/error', function (req, reply) { - t.ok(req.log) - reply.send(new Error('something happened')) - }) - fastify.setErrorHandler((err, req, reply) => { - t.ok(err) - reply.send('something bad happened') - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('should not rely on raw request to log errors', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { level: 30, msg: 'incoming request' }, - { res: { statusCode: 415 }, msg: 'something happened' }, - { res: { statusCode: 415 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) - fastify.get('/error', function (req, reply) { - t.ok(req.log) - reply.status(415).send(new Error('something happened')) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('should redact the authorization header if so specified', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' }, - { res: { statusCode: 200 }, msg: 'request completed' } - ] - t.plan(lines.length + 3) - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - redact: ['req.headers.authorization'], - level: 'info', - serializers: { - req (req) { - return { - method: req.method, - url: req.url, - headers: req.headers, - hostname: req.hostname, - remoteAddress: req.ip, - remotePort: req.socket.remotePort - } - } - } - } - }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/', function (req, reply) { - t.same(req.headers.authorization, 'Bearer abcde') - reply.send({ hello: 'world' }) - }) - - await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) - - await request({ - method: 'GET', - path: '/', - host: localhost, - port: fastify.server.address().port, - headers: { - authorization: 'Bearer abcde' - } - }, function (response, body) { - t.equal(response.statusCode, 200) - t.same(body, JSON.stringify({ hello: 'world' })) - }) - - for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) - if (lines.length === 0) break - } - }) - - t.test('should not log incoming request and outgoing response when disabled', async (t) => { - t.plan(3) - const stream = split(JSON.parse) - const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) - t.teardown(fastify.close.bind(fastify)) - - fastify.get('/500', (req, reply) => { - reply.code(500).send(Error('500 error')) - }) - - await fastify.ready() - - await fastify.inject({ method: 'GET', url: '/500' }) - - { - const [line] = await once(stream, 'data') - t.ok(line.reqId, 'reqId is defined') - t.equal(line.msg, '500 error', 'message is set') - } - - // no more readable data - t.equal(stream.readableLength, 0) - }) - - t.test('should not log incoming request and outgoing response for 404 onBadUrl when disabled', async (t) => { - t.plan(3) - const stream = split(JSON.parse) - const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) - t.teardown(fastify.close.bind(fastify)) - - await fastify.ready() - - await fastify.inject({ method: 'GET', url: '/%c0' }) - - { - const [line] = await once(stream, 'data') - t.ok(line.reqId, 'reqId is defined') - t.equal(line.msg, 'Route GET:/%c0 not found', 'message is set') - } - - // no more readable data - t.equal(stream.readableLength, 0) - }) - - t.test('should pass when using unWritable props in the logger option', (t) => { - t.plan(8) - const fastify = Fastify({ - logger: Object.defineProperty({}, 'level', { value: 'info' }) - }) - t.teardown(fastify.close.bind(fastify)) - - t.equal(typeof fastify.log, 'object') - t.equal(typeof fastify.log.fatal, 'function') - t.equal(typeof fastify.log.error, 'function') - t.equal(typeof fastify.log.warn, 'function') - t.equal(typeof fastify.log.info, 'function') - t.equal(typeof fastify.log.debug, 'function') - t.equal(typeof fastify.log.trace, 'function') - t.equal(typeof fastify.log.child, 'function') - }) - - t.test('should be able to use a custom logger', (t) => { - t.plan(7) - - const logger = { - fatal: (msg) => { t.equal(msg, 'fatal') }, - error: (msg) => { t.equal(msg, 'error') }, - warn: (msg) => { t.equal(msg, 'warn') }, - info: (msg) => { t.equal(msg, 'info') }, - debug: (msg) => { t.equal(msg, 'debug') }, - trace: (msg) => { t.equal(msg, 'trace') }, - child: () => logger - } - - const fastify = Fastify({ logger }) - t.teardown(fastify.close.bind(fastify)) - - fastify.log.fatal('fatal') - fastify.log.error('error') - fastify.log.warn('warn') - fastify.log.info('info') - fastify.log.debug('debug') - fastify.log.trace('trace') - const child = fastify.log.child() - t.equal(child, logger) - }) - - t.test('should create a default logger if provided one is invalid', (t) => { - t.plan(8) - - const logger = new Date() - - const fastify = Fastify({ logger }) - t.teardown(fastify.close.bind(fastify)) - - t.equal(typeof fastify.log, 'object') - t.equal(typeof fastify.log.fatal, 'function') - t.equal(typeof fastify.log.error, 'function') - t.equal(typeof fastify.log.warn, 'function') - t.equal(typeof fastify.log.info, 'function') - t.equal(typeof fastify.log.debug, 'function') - t.equal(typeof fastify.log.trace, 'function') - t.equal(typeof fastify.log.child, 'function') - }) - - t.test('should not throw error when serializing custom req', (t) => { - t.plan(1) - - const lines = [] - const dest = new stream.Writable({ - write: function (chunk, enc, cb) { - lines.push(JSON.parse(chunk)) - cb() - } - }) - const fastify = Fastify({ logger: { level: 'info', stream: dest } }) - t.teardown(fastify.close.bind(fastify)) - - fastify.log.info({ req: {} }) - - t.same(lines[0].req, {}) - }) -}) From 16859bb0ca643b9e34be6fa4bff4a4ff9efa8289 Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Tue, 3 Oct 2023 16:58:04 +0200 Subject: [PATCH 0474/1295] fix(async-hooks): mixing async and callback style (#5069) --- lib/route.js | 17 +++- test/hooks-async.test.js | 170 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 176 insertions(+), 11 deletions(-) diff --git a/lib/route.js b/lib/route.js index aa273f3dc52..4b8bab18040 100644 --- a/lib/route.js +++ b/lib/route.js @@ -28,7 +28,8 @@ const { FST_ERR_ROUTE_METHOD_NOT_SUPPORTED, FST_ERR_ROUTE_METHOD_INVALID, FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED, - FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT + FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT, + FST_ERR_HOOK_INVALID_ASYNC_HANDLER } = require('./errors') const { @@ -268,6 +269,20 @@ function buildRouting (options) { if (typeof func !== 'function') { throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(func)) } + + if (hook === 'onSend' || hook === 'preSerialization' || hook === 'onError' || hook === 'preParsing') { + if (func.constructor.name === 'AsyncFunction' && func.length === 4) { + throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER() + } + } else if (hook === 'onRequestAbort') { + if (func.constructor.name === 'AsyncFunction' && func.length !== 1) { + throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER() + } + } else { + if (func.constructor.name === 'AsyncFunction' && func.length === 3) { + throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER() + } + } } } else if (opts[hook] !== undefined && typeof opts[hook] !== 'function') { throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(opts[hook])) diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index 7f24099b46b..49fe3e7b8d9 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -745,8 +745,8 @@ test('Should log a warning if is an async function with `done`', t => { try { fastify.addHook('onRequestAbort', async (req, done) => {}) } catch (e) { - t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) @@ -757,8 +757,8 @@ test('Should log a warning if is an async function with `done`', t => { try { fastify.addHook('onRequest', async (req, reply, done) => {}) } catch (e) { - t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) @@ -769,20 +769,20 @@ test('Should log a warning if is an async function with `done`', t => { try { fastify.addHook('onSend', async (req, reply, payload, done) => {}) } catch (e) { - t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } try { fastify.addHook('preSerialization', async (req, reply, payload, done) => {}) } catch (e) { - t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } try { fastify.addHook('onError', async (req, reply, payload, done) => {}) } catch (e) { - t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) @@ -923,3 +923,153 @@ t.test('nested hooks to do not crash on 404', t => { t.equal(res.statusCode, 404) }) }) + +test('Register an hook (preHandler) as route option should fail if mixing async and callback style', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.get( + '/', + { + preHandler: [ + async (request, reply, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.fail('preHandler mixing async and callback style') + } catch (e) { + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (onSend) as route option should fail if mixing async and callback style', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.get( + '/', + { + onSend: [ + async (request, reply, payload, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.fail('onSend mixing async and callback style') + } catch (e) { + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (preSerialization) as route option should fail if mixing async and callback style', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.get( + '/', + { + preSerialization: [ + async (request, reply, payload, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.fail('preSerialization mixing async and callback style') + } catch (e) { + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (onError) as route option should fail if mixing async and callback style', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.get( + '/', + { + onError: [ + async (request, reply, error, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.fail('onError mixing async and callback style') + } catch (e) { + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (preParsing) as route option should fail if mixing async and callback style', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.get( + '/', + { + preParsing: [ + async (request, reply, payload, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.fail('preParsing mixing async and callback style') + } catch (e) { + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (onRequestAbort) as route option should fail if mixing async and callback style', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.get( + '/', + { + onRequestAbort: [ + async (request, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.fail('onRequestAbort mixing async and callback style') + } catch (e) { + t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) From cefe85d16e378a04b3e38557c6e81e4eda61d363 Mon Sep 17 00:00:00 2001 From: Shankhadeep Dey Date: Wed, 4 Oct 2023 13:27:23 +0530 Subject: [PATCH 0475/1295] fix: enhance 100 and 200 or 204 handling (#5056) Co-authored-by: Carlos Fuentes --- lib/reply.js | 11 +++++++ test/reply-code.test.js | 64 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/lib/reply.js b/lib/reply.js index be691b7e077..45f7e4de0b8 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -591,6 +591,17 @@ function onSendEnd (reply, payload) { return } + if ((statusCode >= 100 && statusCode < 200) || statusCode === 204) { + // Responses without a content body must not send content-type + // or content-length headers. + // See https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6. + reply.removeHeader('content-type') + reply.removeHeader('content-length') + safeWriteHead(reply, statusCode) + sendTrailer(undefined, res, reply) + return + } + if (typeof payload.pipe === 'function') { sendStream(payload, res, reply) return diff --git a/test/reply-code.test.js b/test/reply-code.test.js index 1e709322e4d..00847d5a9cb 100644 --- a/test/reply-code.test.js +++ b/test/reply-code.test.js @@ -57,3 +57,67 @@ test('code should handle null/undefined/float', t => { t.equal(res.statusCode, 404) }) }) + +test('code should handle 204', t => { + t.plan(8) + + const fastify = Fastify() + + fastify.get('/204', function (request, reply) { + reply.status(204) + return null + }) + + fastify.get('/undefined/204', function (request, reply) { + reply.status(204).send({ message: 'hello' }) + }) + + fastify.inject({ + method: 'GET', + url: '/204' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 204) + t.equal(res.payload, '') + t.equal(res.headers['content-length'], undefined) + }) + + fastify.inject({ + method: 'GET', + url: '/undefined/204' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 204) + t.equal(res.payload, '') + t.equal(res.headers['content-length'], undefined) + }) +}) + +test('code should handle onSend hook on 204', t => { + t.plan(5) + + const fastify = Fastify() + fastify.addHook('onSend', async function (request, reply, payload) { + return { + ...payload, + world: 'hello' + } + }) + + fastify.get('/204', function (request, reply) { + reply.status(204).send({ + hello: 'world' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/204' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 204) + t.equal(res.payload, '') + t.equal(res.headers['content-length'], undefined) + t.equal(res.headers['content-type'], undefined) + }) +}) From 9a019624d4ca408ed58b00c5642a05d405b7d67c Mon Sep 17 00:00:00 2001 From: Rylee George Date: Wed, 4 Oct 2023 21:27:33 +1100 Subject: [PATCH 0476/1295] docs: add fastify-cf-turnstile (#5067) * docs: add fastify-cf-turnstile --------- Co-authored-by: Manuel Spigolon --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 92c939e1373..23a45da63d3 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -281,6 +281,8 @@ section. - [`fastify-cloudevents`](https://github.com/smartiniOnGitHub/fastify-cloudevents) Fastify plugin to generate and forward Fastify events in the Cloudevents format. +- [`fastify-cloudflare-turnstile`](https://github.com/112RG/fastify-cloudflare-turnstile) + Fastify plugin for CloudFlare Turnstile. - [`fastify-cloudinary`](https://github.com/Vanilla-IceCream/fastify-cloudinary) The Cloudinary Fastify SDK allows you to quickly and easily integrate your application with Cloudinary. Effortlessly optimize and transform your cloud's From 912f49d5cee5d906ce723b4df2be0069ccd971bf Mon Sep 17 00:00:00 2001 From: Michael Di Prisco Date: Fri, 6 Oct 2023 10:25:18 +0200 Subject: [PATCH 0477/1295] docs: typos (#5079) --- CONTRIBUTING.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94ecb08182b..db231afbd28 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,7 +94,7 @@ the following tasks: - Plugins team: maintains the Fastify's plugins and its ecosystem 3. Open a pull request to [`fastify/fastify:HEAD`](https://github.com/fastify/fastify/pulls) that adds - your name, username, and email to the team you have choosen in the + your name, username, and email to the team you have chosen in the [README.md](./README.md) and [package.json](./package.json) *(if you are part of the core team)* files. The members lists are sorted alphabetically; make sure to add your name in the proper order. diff --git a/README.md b/README.md index 797a9411bde..d70a079eb18 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ Past Sponsors: - [LetzDoIt](https://www.letzdoitapp.com/) This list includes all companies that support one or more of the team members -in the maintainance of this project. +in the maintenance of this project. ## License From fb4b3bd40b5bf897a3ecdad0656eeed64c11af55 Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Mon, 9 Oct 2023 07:07:05 -0300 Subject: [PATCH 0478/1295] feat: Add `Symbol.asyncDispose` to improve DX in short lived servers. (#5082) * feat: dispose * fix: check if asyncDispose exists * feat: ts typings * tests: symbol.dispose types * fix: dispose return type and tests * docs: added basic docs * tests: .test * docs * pass if asyncDispose is not present * tests: better skip * tests: plan of 2 --- docs/Reference/Server.md | 34 +++++++++++++++++++++++++++++++--- fastify.js | 7 +++++++ test/async-dispose.test.js | 21 +++++++++++++++++++++ test/types/using.test-d.ts | 14 ++++++++++++++ types/instance.d.ts | 3 +++ 5 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 test/async-dispose.test.js create mode 100644 test/types/using.test-d.ts diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index b5d910c2357..7e4b4d4b82c 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -50,20 +50,21 @@ describes the properties available in that options object. - [after](#after) - [ready](#ready) - [listen](#listen) + - [`listenTextResolver`](#listentextresolver) - [addresses](#addresses) - [getDefaultRoute](#getdefaultroute) - [setDefaultRoute](#setdefaultroute) - [routing](#routing) - [route](#route) - - [hasRoute](#hasRoute) + - [hasRoute](#hasroute) - [close](#close) - - [decorate*](#decorate) + - [decorate\*](#decorate) - [register](#register) - [addHook](#addhook) - [prefix](#prefix) - [pluginName](#pluginname) - [hasPlugin](#hasplugin) - - [listeningOrigin](#listeningOrigin) + - [listeningOrigin](#listeningorigin) - [log](#log) - [version](#version) - [inject](#inject) @@ -93,6 +94,7 @@ describes the properties available in that options object. - [defaultTextParser](#defaulttextparser) - [errorHandler](#errorhandler) - [childLoggerFactory](#childloggerfactory) + - [Symbol.asyncDispose](#symbolasyncdispose) - [initialConfig](#initialconfig) ### `http` @@ -1866,6 +1868,32 @@ fastify.get('/', { Fastify instance. See the [`childLoggerFactory` config option](#setchildloggerfactory) for more info. +#### Symbol.asyncDispose + + +`fastify[Symbol.asyncDispose]` is a symbol that can be used to define an +asynchronous function that will be called when the Fastify instance is closed. + +Its commonly used alongside the `using` typescript keyword to ensure that +resources are cleaned up when the Fastify instance is closed. + +This combines perfectly inside short lived processes or unit tests, where you must +close all fastify resources after returning from inside the function. + +```ts +it('Uses app and closes it afterwards', async () => { + await using app = fastify(); + // do something with app. +}) +``` + +In the above example, fastify is closed automatically after the test finishes. + +Read more about the +[ECMAScript Explicit Resource Management](https://tc39.es/proposal-explicit-resource-management) +and the [using keyword](https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/) +introduced in typescript 5.2. + #### initialConfig diff --git a/fastify.js b/fastify.js index c74a2f8d8ce..cb140ef1218 100644 --- a/fastify.js +++ b/fastify.js @@ -506,6 +506,13 @@ function fastify (options) { // versions of Node.js. In that event, we don't care, so ignore the error. } + // Older nodejs versions may not have asyncDispose + if ('asyncDispose' in Symbol) { + fastify[Symbol.asyncDispose] = function dispose () { + return fastify.close() + } + } + return fastify function throwIfAlreadyStarted (msg) { diff --git a/test/async-dispose.test.js b/test/async-dispose.test.js new file mode 100644 index 00000000000..576b1648640 --- /dev/null +++ b/test/async-dispose.test.js @@ -0,0 +1,21 @@ +'use strict' + +const t = require('tap') +const Fastify = require('../fastify') + +// asyncDispose doesn't exist in node <= 16 +t.test('async dispose should close fastify', { skip: !('asyncDispose' in Symbol) }, async t => { + t.plan(2) + + const fastify = Fastify() + + await fastify.listen({ port: 0 }) + + t.equal(fastify.server.listening, true) + + // the same as syntax sugar for + // await using app = fastify() + await fastify[Symbol.asyncDispose]() + + t.equal(fastify.server.listening, false) +}) diff --git a/test/types/using.test-d.ts b/test/types/using.test-d.ts new file mode 100644 index 00000000000..6b2997b0130 --- /dev/null +++ b/test/types/using.test-d.ts @@ -0,0 +1,14 @@ +import { expectAssignable, expectType } from 'tsd' +import fastify, { FastifyInstance } from '../../fastify' + +async function hasSymbolDisposeWithUsing () { + await using app = fastify() + expectAssignable(app) + expectAssignable(app.close) +} + +async function hasSymbolDispose () { + const app = fastify() + expectAssignable(app) + expectAssignable(app.close) +} diff --git a/types/instance.d.ts b/types/instance.d.ts index 293d078d947..98caa67170b 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -142,6 +142,9 @@ export interface FastifyInstance< close(): Promise; close(closeListener: () => void): undefined; + /** Alias for {@linkcode FastifyInstance.close()} */ + [Symbol.asyncDispose](): Promise; + // should be able to define something useful with the decorator getter/setter pattern using Generics to enforce the users function returns what they expect it to decorate: DecorationMethod>; decorateRequest: DecorationMethod>; From a34d1c993c5d6f62e4c2fb55ad844244acdc1969 Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Mon, 9 Oct 2023 13:13:39 -0300 Subject: [PATCH 0479/1295] docs: typos (#5083) --- docs/Reference/Server.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 7e4b4d4b82c..bbc50928345 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1874,25 +1874,25 @@ for more info. `fastify[Symbol.asyncDispose]` is a symbol that can be used to define an asynchronous function that will be called when the Fastify instance is closed. -Its commonly used alongside the `using` typescript keyword to ensure that +It's commonly used alongside the `using` TypeScript keyword to ensure that resources are cleaned up when the Fastify instance is closed. This combines perfectly inside short lived processes or unit tests, where you must -close all fastify resources after returning from inside the function. +close all Fastify resources after returning from inside the function. ```ts -it('Uses app and closes it afterwards', async () => { +test('Uses app and closes it afterwards', async () => { await using app = fastify(); // do something with app. }) ``` -In the above example, fastify is closed automatically after the test finishes. +In the above example, Fastify is closed automatically after the test finishes. Read more about the [ECMAScript Explicit Resource Management](https://tc39.es/proposal-explicit-resource-management) and the [using keyword](https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/) -introduced in typescript 5.2. +introduced in TypeScript 5.2. #### initialConfig From ad2da127e78b06084a43d8f697bf83f0f9750e45 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Wed, 11 Oct 2023 02:56:05 -0400 Subject: [PATCH 0480/1295] docs: remove thenable promisesaplus spec references (#5081) --- docs/Reference/Plugins.md | 2 +- docs/Reference/Reply.md | 1 - lib/reply.js | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/Reference/Plugins.md b/docs/Reference/Plugins.md index 9b2952289d5..57c22ab1661 100644 --- a/docs/Reference/Plugins.md +++ b/docs/Reference/Plugins.md @@ -134,7 +134,7 @@ fastify.listen({ port: 3000 }, (err, address) => { *async/await* is supported by `after`, `ready`, and `listen`, as well as -`fastify` being a [Thenable](https://promisesaplus.com/). +`fastify` being a Thenable. ```js await fastify.register(require('my-plugin')) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 0bb0cac6c9c..7d1d1164232 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -892,6 +892,5 @@ For more details, see: - https://github.com/fastify/fastify/issues/1864 for the discussion about this feature -- https://promisesaplus.com/ for the definition of thenables - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then for the signature diff --git a/lib/reply.js b/lib/reply.js index 45f7e4de0b8..34f5cfc3268 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -458,7 +458,6 @@ Reply.prototype.getResponseTime = function () { // Make reply a thenable, so it could be used with async/await. // See // - https://github.com/fastify/fastify/issues/1864 for the discussions -// - https://promisesaplus.com/ for the definition of thenable // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then for the signature Reply.prototype.then = function (fulfilled, rejected) { if (this.sent) { From 78503752679d7a4f929d921cf978310136bf0dff Mon Sep 17 00:00:00 2001 From: Gilles Marchand Date: Wed, 11 Oct 2023 08:56:50 +0200 Subject: [PATCH 0481/1295] docs(ecosystem): add fastify-event-bus (#5085) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 23a45da63d3..92ae3060a76 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -321,6 +321,8 @@ section. - [`fastify-esso`](https://github.com/patrickpissurno/fastify-esso) The easiest authentication plugin for Fastify, with built-in support for Single sign-on (and great documentation). +- [`fastify-event-bus`](https://github.com/Shiva127/fastify-event-bus) Event bus + support for Fastify. Built upon [js-event-bus](https://github.com/bcerati/js-event-bus). - [`fastify-evervault`](https://github.com/Briscoooe/fastify-evervault/) Fastify plugin for instantiating and encapsulating the [Evervault](https://evervault.com/) client. From 96bf5a23af9e7bb9b1ac4bbde5df495ae121b4c1 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Wed, 11 Oct 2023 02:57:59 -0400 Subject: [PATCH 0482/1295] docs: update docs for FastifyPlugin (#5070) --- docs/Reference/TypeScript.md | 11 ++++++----- types/plugin.d.ts | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 35aaa660140..8cccf9a0c80 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -606,7 +606,7 @@ your plugin. ``` 5. Open `index.d.ts` and add the following code: ```typescript - import { FastifyPlugin } from 'fastify' + import { FastifyPluginCallback } from 'fastify' interface PluginOptions { //... @@ -627,7 +627,7 @@ your plugin. // fastify-plugin automatically adds named export, so be sure to add also this type // the variable name is derived from `options.name` property if `module.exports.myPlugin` is missing - export const myPlugin: FastifyPlugin + export const myPlugin: FastifyPluginCallback // fastify-plugin automatically adds `.default` property to the exported plugin. See the note below export default myPlugin @@ -1233,15 +1233,16 @@ a function signature with an underlying generic `Options` which is defaulted to FastifyPlugin parameter when calling this function so there is no need to specify the underlying generic. The options parameter is the intersection of the plugin's options and two additional optional properties: `prefix: string` and -`logLevel`: [LogLevel][LogLevel]. +`logLevel`: [LogLevel][LogLevel]. `FastifyPlugin` is deprecated use +`FastifyPluginCallback` and `FastifyPluginAsync` instead. Below is an example of the options inference in action: ```typescript const server = fastify() -const plugin: FastifyPlugin<{ - option1: string; +const plugin: FastifyPluginCallback<{ +: option1: string; option2: boolean; }> = function (instance, opts, done) { } diff --git a/types/plugin.d.ts b/types/plugin.d.ts index 74c0a2b49f9..f628a152820 100644 --- a/types/plugin.d.ts +++ b/types/plugin.d.ts @@ -38,6 +38,7 @@ export type FastifyPluginAsync< /** * Generic plugin type. - * @deprecated union type doesn't work well with type inference in TS and is therefore deprecated in favor of explicit types. See FastifyRegister. + * @deprecated union type doesn't work well with type inference in TS and is therefore deprecated in favor of explicit types. Use `FastifyPluginCallback` or `FastifyPluginAsync` instead. To activate + * plugins use `FastifyRegister`. https://fastify.dev/docs/latest/Reference/TypeScript/#register */ export type FastifyPlugin> = FastifyPluginCallback | FastifyPluginAsync From 37ecff552ffeae9695027c56e8460a1b74f44939 Mon Sep 17 00:00:00 2001 From: Jack Batzner Date: Wed, 11 Oct 2023 03:05:35 -0500 Subject: [PATCH 0483/1295] Update for .hijack header (#5088) .hijack isn't showing correctly so update it. Co-authored-by: Carlos Fuentes --- docs/Reference/Reply.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 7d1d1164232..af511b19875 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -605,8 +605,9 @@ low-level request and response. Moreover, hooks will not be invoked. *Modifying the `.sent` property directly is deprecated. Please use the aforementioned `.hijack()` method to achieve the same effect.* - ### .hijack() + + Sometimes you might need to halt the execution of the normal request lifecycle and handle sending the response manually. From b59b0a5dcc38f2b81b5ee38e92070efb7bea1c69 Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Wed, 11 Oct 2023 11:27:22 +0200 Subject: [PATCH 0484/1295] fix(warnings): fixed warning when accessing context property from Request and Reply objects (#5084) * fix(warnings): fixed warning when accessing context property from Request and Reply objects * docs(request,reply): added routeOptions to deprecate context * test(reply): improved coverage and use fastify.symbols * docs(Request): added reference to new property to be used instead of context --- docs/Reference/Reply.md | 3 ++- docs/Reference/Request.md | 5 +++-- docs/Reference/TypeScript.md | 17 +++++++++++++++-- fastify.d.ts | 4 ++-- lib/reply.js | 7 +++++++ lib/warnings.js | 4 +++- test/internals/reply.test.js | 37 ++++++++++++++++++++++++++++++++---- test/types/reply.test-d.ts | 6 +++--- test/types/request.test-d.ts | 18 +++++++++--------- types/context.d.ts | 11 +++++++++-- types/reply.d.ts | 4 ++-- types/request.d.ts | 6 +++--- types/route.d.ts | 4 ++-- 13 files changed, 93 insertions(+), 33 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index af511b19875..94674b940fe 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -85,7 +85,8 @@ object that exposes the following functions and properties: from Node core. - `.log` - The logger instance of the incoming request. - `.request` - The incoming request. -- `.context` - Access the [Request's context](./Request.md) property. +- `.context` - Deprecated, access the [Request's context](./Request.md) property. +- `.routeOptions` - Access the [Request's routeOptions](./Request.md) property. ```js fastify.get('/', options, function (request, reply) { diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 4b524e6d2ef..63cd7b4cbab 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -37,8 +37,9 @@ Request is a core Fastify object containing the following fields: - `connection` - Deprecated, use `socket` instead. The underlying connection of the incoming request. - `socket` - the underlying connection of the incoming request -- `context` - A Fastify internal object. You should not use it directly or - modify it. It is useful to access one special key: +- `context` - Deprecated, use `request.routeOptions.config` instead. +A Fastify internal object. You should not use +it directly or modify it. It is useful to access one special key: - `context.config` - The route [`config`](./Routes.md#routes-config) object. - `routeSchema` - Deprecated, use `request.routeOptions.schema` instead. The scheme definition set for the router that is handling the request diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 8cccf9a0c80..3189f5458cb 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -1297,9 +1297,22 @@ Union type of: `'info' | 'error' | 'debug' | 'fatal' | 'warn' | 'trace'` The context type definition is similar to the other highly dynamic pieces of the type system. Route context is available in the route handler method. -##### fastify.FastifyContext +##### fastify.FastifyRequestContext -[src](https://github.com/fastify/fastify/blob/main/types/context.d.ts#L6) +[src](https://github.com/fastify/fastify/blob/main/types/context.d.ts#L11) + +An interface with a single required property `config` that is set by default to +`unknown`. Can be specified either using a generic or an overload. + +This type definition is potentially incomplete. If you are using it and can +provide more details on how to improve the definition, we strongly encourage you +to open an issue in the main +[fastify/fastify](https://github.com/fastify/fastify) repository. Thank you in +advanced! + +##### fastify.FastifyReplyContext + +[src](https://github.com/fastify/fastify/blob/main/types/context.d.ts#L11) An interface with a single required property `config` that is set by default to `unknown`. Can be specified either using a generic or an overload. diff --git a/fastify.d.ts b/fastify.d.ts index 5c0eb90f998..65c36d78cf8 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -10,7 +10,7 @@ import { ConstraintStrategy, HTTPVersion } from 'find-my-way' import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request' import { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser' -import { FastifyContext, FastifyContextConfig } from './types/context' +import { FastifyRequestContext, FastifyContextConfig, FastifyReplyContext } from './types/context' import { FastifyErrorCodes } from './types/errors' import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler } from './types/hooks' import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' @@ -175,7 +175,7 @@ declare namespace fastify { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin, // './types/plugin' FastifyListenOptions, FastifyInstance, PrintRoutesOptions, // './types/instance' FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel, // './types/logger' - FastifyContext, FastifyContextConfig, // './types/context' + FastifyRequestContext, FastifyContextConfig, FastifyReplyContext, // './types/context' RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface, // './types/route' FastifyRegister, FastifyRegisterOptions, RegisterOptions, // './types/register' FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction, // './types/content-type-parser' diff --git a/lib/reply.js b/lib/reply.js index 34f5cfc3268..c0e6bbb5fff 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -4,6 +4,7 @@ const eos = require('node:stream').finished const { kFourOhFourContext, + kPublicRouteContext, kReplyErrorHandlerCalled, kReplyHijacked, kReplyStartTime, @@ -79,6 +80,7 @@ Object.defineProperties(Reply.prototype, { // Is temporary to avoid constant conflicts between `next` and `main` context: { get () { + warning.emit('FSTDEP019') return this.request[kRouteContext] } }, @@ -115,6 +117,11 @@ Object.defineProperties(Reply.prototype, { set (value) { this.code(value) } + }, + [kPublicRouteContext]: { + get () { + return this.request[kPublicRouteContext] + } } }) diff --git a/lib/warnings.js b/lib/warnings.js index 01cee1c4154..ec61b0f7da4 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -21,7 +21,7 @@ warning.create('FastifyDeprecation', 'FSTDEP010', 'Modifying the "reply.sent" pr warning.create('FastifyDeprecation', 'FSTDEP011', 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.') -warning.create('FastifyDeprecation', 'FSTDEP012', 'Request#context property access is deprecated. Please use "Request#routeConfig" or "Request#routeSchema" instead for accessing Route settings. The "Request#context" will be removed in `fastify@5`.') +warning.create('FastifyDeprecation', 'FSTDEP012', 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.') 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`.') @@ -35,6 +35,8 @@ warning.create('FastifyDeprecation', 'FSTDEP017', 'You are accessing the depreca warning.create('FastifyDeprecation', 'FSTDEP018', 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.') +warning.create('FastifyDeprecation', 'FSTDEP019', 'reply.context property access is deprecated. Please use "reply.routeOptions.config" or "reply.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.') + warning.create('FastifyWarning', 'FSTWRN001', 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', { unlimited: true }) module.exports = warning diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 805da05b083..8ceb9ebc842 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -14,7 +14,8 @@ const { kReplySerializer, kReplyIsError, kReplySerializerDefault, - kRouteContext + kRouteContext, + kPublicRouteContext } = require('../../lib/symbols') const fs = require('node:fs') const path = require('node:path') @@ -35,10 +36,10 @@ const doGet = function (url) { } test('Once called, Reply should return an object with methods', t => { - t.plan(14) + t.plan(16) const response = { res: 'res' } - const context = {} - const request = { [kRouteContext]: context } + const context = { config: { onSend: [] }, schema: {} } + const request = { [kRouteContext]: context, [kPublicRouteContext]: { config: context.config, schema: context.schema } } const reply = new Reply(response, request) t.equal(typeof reply, 'object') t.equal(typeof reply[kReplyIsError], 'boolean') @@ -52,6 +53,8 @@ test('Once called, Reply should return an object with methods', t => { t.equal(typeof reply[kReplyHeaders], 'object') t.same(reply.raw, response) t.equal(reply[kRouteContext], context) + t.equal(reply[kPublicRouteContext].config, context.config) + t.equal(reply[kPublicRouteContext].schema, context.schema) t.equal(reply.request, request) // Aim to not bad property keys (including Symbols) t.notOk('undefined' in reply) @@ -1487,6 +1490,32 @@ test('should emit deprecation warning when trying to modify the reply.sent prope }) }) +test('should emit deprecation warning when trying to use the reply.context.config property', t => { + t.plan(4) + const fastify = Fastify() + + const deprecationCode = 'FSTDEP019' + warning.emitted.delete(deprecationCode) + + process.removeAllListeners('warning') + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.code, deprecationCode) + } + + fastify.get('/', (req, reply) => { + req.log(reply.context.config) + }) + + fastify.inject('/', (err, res) => { + t.error(err) + t.pass() + + process.removeListener('warning', onWarning) + }) +}) + test('should throw error when passing falsy value to reply.sent', t => { t.plan(4) const fastify = Fastify() diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index e239a112f33..837d1ddfa20 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,6 +1,6 @@ import { Buffer } from 'buffer' import { expectAssignable, expectError, expectType } from 'tsd' -import fastify, { FastifyContext, FastifyReply, FastifyRequest, FastifySchema, FastifySchemaCompiler, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' +import fastify, { FastifyReplyContext, FastifyReply, FastifyRequest, FastifySchema, FastifySchemaCompiler, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' import { FastifyInstance } from '../../types/instance' import { FastifyLoggerInstance } from '../../types/logger' import { ResolveReplyTypeWithRouteGeneric } from '../../types/reply' @@ -12,8 +12,8 @@ type DefaultFastifyReplyWithCode = FastifyReply(reply.raw) - expectType>(reply.context) - expectType['config']>(reply.context.config) + expectType>(reply.context) + expectType['config']>(reply.context.config) expectType(reply.log) expectType>(reply.request) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 6fe20926f8d..502b877b796 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -2,7 +2,7 @@ import pino from 'pino' import { expectAssignable, expectType } from 'tsd' import fastify, { ContextConfigDefault, - FastifyContext, + FastifyRequestContext, FastifyContextConfig, FastifyLogFn, FastifySchema, @@ -75,10 +75,10 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.raw) expectType(request.body) expectType(request.params) - expectType>(request.context) - expectType['config']>(request.context.config) - expectType['config']>(request.routeConfig) - expectType['config']>(request.routeOptions.config) + expectType>(request.context) + expectType['config']>(request.context.config) + expectType['config']>(request.routeConfig) + expectType['config']>(request.routeOptions.config) expectType(request.routeOptions.config) expectType(request.routeSchema) expectType(request.routeOptions.schema) @@ -112,8 +112,8 @@ const postHandler: Handler = function (request) { expectType(request.params.id) expectType(request.headers['x-foobar']) expectType(request.server) - expectType>(request.context) - expectType['config']>(request.context.config) + expectType>(request.context) + expectType['config']>(request.context.config) } function putHandler (request: CustomRequest, reply: FastifyReply) { @@ -130,8 +130,8 @@ function putHandler (request: CustomRequest, reply: FastifyReply) { expectType(request.params.id) expectType(request.headers['x-foobar']) expectType(request.server) - expectType>(request.context) - expectType['config']>(request.context.config) + expectType>(request.context) + expectType['config']>(request.context.config) } const server = fastify() diff --git a/types/context.d.ts b/types/context.d.ts index 0ebd05c2c20..242b70d3709 100644 --- a/types/context.d.ts +++ b/types/context.d.ts @@ -8,9 +8,16 @@ export interface FastifyContextConfig { /** * Route context object. Properties defined here will be available in the route's handler */ -export interface FastifyContext { +export interface FastifyRequestContext { /** - * @deprecated Use Request#routeConfig or Request#routeSchema instead + * @deprecated Use Request#routeOptions#config or Request#routeOptions#schema instead + */ + config: FastifyContextConfig & FastifyRouteConfig & ContextConfig; +} + +export interface FastifyReplyContext { + /** + * @deprecated Use Reply#routeOptions#config or Reply#routeOptions#schema instead */ config: FastifyContextConfig & FastifyRouteConfig & ContextConfig; } diff --git a/types/reply.d.ts b/types/reply.d.ts index f368ad407ee..3c81d739715 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -1,5 +1,5 @@ import { Buffer } from 'buffer' -import { FastifyContext } from './context' +import { FastifyReplyContext } from './context' import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' import { FastifyRequest } from './request' @@ -39,7 +39,7 @@ export interface FastifyReply< ReplyType extends FastifyReplyType = ResolveFastifyReplyType > { raw: RawReply; - context: FastifyContext; + context: FastifyReplyContext; log: FastifyBaseLogger; request: FastifyRequest; server: FastifyInstance; diff --git a/types/request.d.ts b/types/request.d.ts index 1bb39d07184..75e13475ee0 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,5 +1,5 @@ import { ErrorObject } from '@fastify/ajv-compiler' -import { FastifyContext, FastifyContextConfig } from './context' +import { FastifyRequestContext, FastifyContextConfig } from './context' import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' import { RouteGenericInterface, FastifyRouteConfig } from './route' @@ -60,8 +60,8 @@ export interface FastifyRequest; - routeConfig: FastifyContext['config']; + context: FastifyRequestContext; + routeConfig: FastifyRequestContext['config']; routeSchema: FastifySchema /** in order for this to be used the user should ensure they have set the attachValidation option. */ diff --git a/types/route.d.ts b/types/route.d.ts index 652a09e764b..b1ad4562187 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -1,5 +1,5 @@ import { FastifyError } from '@fastify/error' -import { FastifyContext } from './context' +import { FastifyRequestContext } from './context' import { onErrorHookHandler, onRequestAbortHookHandler, onRequestHookHandler, onResponseHookHandler, onSendHookHandler, onTimeoutHookHandler, preHandlerHookHandler, preParsingHookHandler, preSerializationHookHandler, preValidationHookHandler } from './hooks' import { FastifyInstance } from './instance' import { FastifyBaseLogger, FastifyChildLoggerFactory, LogLevel } from './logger' @@ -41,7 +41,7 @@ export interface RouteShorthandOptions< serializerCompiler?: FastifySerializerCompiler; bodyLimit?: number; logLevel?: LogLevel; - config?: Omit['config'], 'url' | 'method'>; + config?: Omit['config'], 'url' | 'method'>; version?: string; constraints?: { [name: string]: any }, prefixTrailingSlash?: 'slash'|'no-slash'|'both'; From 797f27d7b6fde7c616bc63fe51201b5a6843f8ab Mon Sep 17 00:00:00 2001 From: Ivan Tymoshenko Date: Wed, 11 Oct 2023 12:44:20 +0200 Subject: [PATCH 0485/1295] fix: HEAD route search (#5078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: HEAD route search Co-authored-by: Ran Toledo * Update lib/route.js Co-authored-by: Gürgün Dayıoğlu --------- Co-authored-by: Ran Toledo Co-authored-by: Gürgün Dayıoğlu --- lib/route.js | 4 +- package.json | 2 +- test/constrained-routes.test.js | 130 +++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 6 deletions(-) diff --git a/lib/route.js b/lib/route.js index 4b8bab18040..7c097678a29 100644 --- a/lib/route.js +++ b/lib/route.js @@ -322,8 +322,8 @@ function buildRouting (options) { constraints.version = opts.version } - const headHandler = router.find('HEAD', opts.url, constraints) - const hasHEADHandler = headHandler != null + const headHandler = router.findRoute('HEAD', opts.url, constraints) + const hasHEADHandler = headHandler !== null // remove the head route created by fastify if (hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) { diff --git a/package.json b/package.json index 271c2479eb8..c5cec76405a 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,7 @@ "avvio": "^8.2.1", "fast-content-type-parse": "^1.0.0", "fast-json-stringify": "^5.7.0", - "find-my-way": "^7.6.0", + "find-my-way": "^7.7.0", "light-my-request": "^5.9.1", "pino": "^8.12.0", "process-warning": "^2.2.0", diff --git a/test/constrained-routes.test.js b/test/constrained-routes.test.js index 867b6d9b371..e462f052182 100644 --- a/test/constrained-routes.test.js +++ b/test/constrained-routes.test.js @@ -256,6 +256,91 @@ test('Should allow registering custom constrained routes outside constructor', t }) }) +test('Custom constrained routes registered also for HEAD method generated by fastify', t => { + t.plan(3) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers['x-secret'] + }, + validate () { return true } + } + + const fastify = Fastify({ constraints: { secret: constraint } }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'mySecret' }, + handler: (req, reply) => { + reply.send('from mySecret - my length is 31') + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/', + headers: { + 'X-Secret': 'mySecret' + } + }, (err, res) => { + t.error(err) + t.same(res.headers['content-length'], '31') + t.equal(res.statusCode, 200) + }) +}) + +test('Custom constrained routes registered with addConstraintStrategy apply also for HEAD method generated by fastify', t => { + t.plan(3) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers['x-secret'] + }, + validate () { return true } + } + + const fastify = Fastify() + fastify.addConstraintStrategy(constraint) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'mySecret' }, + handler: (req, reply) => { + reply.send('from mySecret - my length is 31') + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/', + headers: { + 'X-Secret': 'mySecret' + } + }, (err, res) => { + t.error(err) + t.same(res.headers['content-length'], '31') + t.equal(res.statusCode, 200) + }) +}) + test('Add a constraint strategy after fastify instance was started', t => { t.plan(4) @@ -561,7 +646,7 @@ test('Should allow registering a constrained GET route after an unconstrained HE url: '/', handler: (req, reply) => { reply.header('content-type', 'text/plain') - reply.send('custom HEAD response') + reply.send('HEAD response: length is about 33') } }) @@ -570,7 +655,8 @@ test('Should allow registering a constrained GET route after an unconstrained HE url: '/', constraints: { host: 'fastify.io' }, handler: (req, reply) => { - reply.send({ hello: 'from any other domain' }) + reply.header('content-type', 'text/plain') + reply.send('Hello from constrains: length is about 41') } }) @@ -582,7 +668,7 @@ test('Should allow registering a constrained GET route after an unconstrained HE } }, (err, res) => { t.error(err) - t.same(res.payload, 'custom HEAD response') + t.same(res.headers['content-length'], '41') t.equal(res.statusCode, 200) }) }) @@ -774,3 +860,41 @@ test('error in async constraints', async (t) => { t.equal(statusCode, 500) } }) + +test('Allow regex constraints in routes', t => { + t.plan(5) + + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + constraints: { host: /.*\.fastify\.io/ }, + handler: (req, reply) => { + reply.send({ hello: 'from fastify dev domain' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'dev.fastify.io' + } + }, (err, res) => { + t.error(err) + t.same(JSON.parse(res.payload), { hello: 'from fastify dev domain' }) + t.equal(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'google.com' + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 404) + }) +}) From d3d8804ca4022674eb661b11943188eac79ae02c Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 11 Oct 2023 11:54:08 +0100 Subject: [PATCH 0486/1295] Bumped v4.24.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index cb140ef1218..b0ee2e8838c 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.23.2' +const VERSION = '4.24.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index c5cec76405a..8aa487712ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.23.2", + "version": "4.24.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 04cc8c1f8a9b561cf14c7f569461bc649c16c1a1 Mon Sep 17 00:00:00 2001 From: Simone Sanfratello Date: Thu, 12 Oct 2023 14:25:57 +0200 Subject: [PATCH 0487/1295] fix: citgm (#5075) --- package.json | 62 +- test/404s.test.js | 70 +- test/async-await.test.js | 2 +- test/build-certificate.js | 91 +- test/close-pipelining.test.js | 10 +- test/close.test.js | 6 +- test/custom-http-server.test.js | 185 +-- test/custom-parser.0.test.js | 68 +- test/custom-parser.1.test.js | 742 +--------- test/custom-parser.2.test.js | 102 ++ test/custom-parser.3.test.js | 245 ++++ test/custom-parser.4.test.js | 239 ++++ test/custom-parser.5.test.js | 149 ++ test/helper.js | 38 +- test/hooks-async.test.js | 6 +- test/hooks.on-listen.test.js | 13 +- test/hooks.test.js | 19 +- test/http2/closing.test.js | 22 +- test/https/custom-https-server.test.js | 83 +- test/listen.1.test.js | 101 ++ test/listen.2.test.js | 103 ++ test/listen.3.test.js | 87 ++ test/listen.4.test.js | 164 +++ test/listen.deprecated.test.js | 12 +- test/listen.test.js | 427 ------ test/logger/instantiation.test.js | 41 +- test/logger/logger-test-utils.js | 2 +- test/plugin.1.test.js | 249 ++++ test/plugin.2.test.js | 328 +++++ test/plugin.3.test.js | 311 +++++ test/plugin.4.test.js | 416 ++++++ test/plugin.test.js | 1275 ----------------- test/reply-trailers.test.js | 3 +- test/route.1.test.js | 309 +++++ test/route.2.test.js | 99 ++ test/route.3.test.js | 205 +++ test/route.4.test.js | 131 ++ test/route.5.test.js | 230 ++++ test/route.6.test.js | 306 ++++ test/route.7.test.js | 370 +++++ test/route.8.test.js | 142 ++ test/route.test.js | 1762 ------------------------ test/stream.1.test.js | 108 ++ test/stream.2.test.js | 119 ++ test/stream.3.test.js | 192 +++ test/stream.4.test.js | 223 +++ test/stream.5.test.js | 194 +++ test/stream.test.js | 816 ----------- test/trust-proxy.test.js | 6 +- test/upgrade.test.js | 6 +- 50 files changed, 5535 insertions(+), 5354 deletions(-) create mode 100644 test/custom-parser.2.test.js create mode 100644 test/custom-parser.3.test.js create mode 100644 test/custom-parser.4.test.js create mode 100644 test/custom-parser.5.test.js create mode 100644 test/listen.1.test.js create mode 100644 test/listen.2.test.js create mode 100644 test/listen.3.test.js create mode 100644 test/listen.4.test.js delete mode 100644 test/listen.test.js create mode 100644 test/plugin.1.test.js create mode 100644 test/plugin.2.test.js create mode 100644 test/plugin.3.test.js create mode 100644 test/plugin.4.test.js delete mode 100644 test/plugin.test.js create mode 100644 test/route.1.test.js create mode 100644 test/route.2.test.js create mode 100644 test/route.3.test.js create mode 100644 test/route.4.test.js create mode 100644 test/route.5.test.js create mode 100644 test/route.6.test.js create mode 100644 test/route.7.test.js create mode 100644 test/route.8.test.js delete mode 100644 test/route.test.js create mode 100644 test/stream.1.test.js create mode 100644 test/stream.2.test.js create mode 100644 test/stream.3.test.js create mode 100644 test/stream.4.test.js create mode 100644 test/stream.5.test.js delete mode 100644 test/stream.test.js diff --git a/package.json b/package.json index 8aa487712ba..8d49b507b1a 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "unit": "c8 tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap", - "citgm": "tap" + "citgm": "tap --jobs=1" }, "repository": { "type": "git", @@ -144,66 +144,66 @@ "homepage": "https://www.fastify.dev/", "devDependencies": { "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.31.1", - "@sinonjs/fake-timers": "^11.0.0", - "@types/node": "^20.1.0", - "@typescript-eslint/eslint-plugin": "^6.3.0", - "@typescript-eslint/parser": "^6.3.0", + "@sinclair/typebox": "^0.31.17", + "@sinonjs/fake-timers": "^11.1.0", + "@types/node": "^20.8.4", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "branch-comparer": "^1.1.0", - "c8": "^8.0.0", + "c8": "^8.0.1", "cross-env": "^7.0.3", - "eslint": "^8.39.0", - "eslint-config-standard": "^17.0.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^16.0.1", + "eslint": "^8.51.0", + "eslint-config-standard": "^17.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-n": "^16.2.0", "eslint-plugin-promise": "^6.1.1", "fast-json-body": "^1.1.0", - "fastify-plugin": "^4.5.0", - "fluent-json-schema": "^4.1.0", + "fastify-plugin": "^4.5.1", + "fluent-json-schema": "^4.1.2", "form-data": "^4.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", - "joi": "^17.9.2", - "json-schema-to-ts": "^2.9.1", + "joi": "^17.11.0", + "json-schema-to-ts": "^2.9.2", "JSONStream": "^1.3.5", - "markdownlint-cli2": "^0.9.2", + "markdownlint-cli2": "^0.10.0", + "node-forge": "^1.3.1", "proxyquire": "^2.1.3", - "self-cert": "^2.0.0", "send": "^0.18.0", "simple-get": "^4.0.1", "snazzy": "^9.0.0", "split2": "^4.2.0", - "standard": "^17.0.0", - "tap": "^16.3.4", + "standard": "^17.1.0", + "tap": "^16.3.9", "tsd": "^0.29.0", - "typescript": "^5.0.4", - "undici": "^5.22.0", + "typescript": "^5.2.2", + "undici": "^5.26.0", "vary": "^1.1.2", - "yup": "^1.1.1" + "yup": "^1.3.2" }, "dependencies": { "@fastify/ajv-compiler": "^3.5.0", - "@fastify/error": "^3.2.0", + "@fastify/error": "^3.4.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", "avvio": "^8.2.1", - "fast-content-type-parse": "^1.0.0", - "fast-json-stringify": "^5.7.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", "find-my-way": "^7.7.0", - "light-my-request": "^5.9.1", - "pino": "^8.12.0", + "light-my-request": "^5.11.0", + "pino": "^8.16.0", "process-warning": "^2.2.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", - "secure-json-parse": "^2.5.0", - "semver": "^7.5.0", - "toad-cache": "^3.2.0" + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" }, "standard": { "ignore": [ diff --git a/test/404s.test.js b/test/404s.test.js index 4a2cc2d0b3d..b1f1a616e6d 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -8,15 +8,7 @@ const errors = require('http-errors') const split = require('split2') const FormData = require('form-data') const Fastify = require('..') - -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} +const { getServerUrl } = require('./helper') test('default 404', t => { t.plan(5) @@ -37,7 +29,7 @@ test('default 404', t => { t.plan(3) sget({ method: 'PUT', - url: getUrl(fastify), + url: getServerUrl(fastify), body: {}, json: true }, (err, response, body) => { @@ -52,7 +44,7 @@ test('default 404', t => { t.plan(3) sget({ method: 'PROPFIND', - url: getUrl(fastify), + url: getServerUrl(fastify), body: {}, json: true }, (err, response, body) => { @@ -66,7 +58,7 @@ test('default 404', t => { t.plan(3) sget({ method: 'GET', - url: getUrl(fastify) + '/notSupported', + url: getServerUrl(fastify) + '/notSupported', body: {}, json: true }, (err, response, body) => { @@ -83,7 +75,7 @@ test('default 404', t => { sget({ method: 'POST', - url: getUrl(fastify) + '/notSupported', + url: getServerUrl(fastify) + '/notSupported', body: form, json: false }, (err, response, body) => { @@ -128,7 +120,7 @@ test('customized 404', t => { t.plan(3) sget({ method: 'PUT', - url: getUrl(fastify), + url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -142,7 +134,7 @@ test('customized 404', t => { t.plan(3) sget({ method: 'PROPFIND', - url: getUrl(fastify), + url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -156,7 +148,7 @@ test('customized 404', t => { t.plan(3) sget({ method: 'GET', - url: getUrl(fastify) + '/notSupported' + url: getServerUrl(fastify) + '/notSupported' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -168,7 +160,7 @@ test('customized 404', t => { t.plan(3) sget({ method: 'GET', - url: getUrl(fastify) + '/with-error' + url: getServerUrl(fastify) + '/with-error' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -184,7 +176,7 @@ test('customized 404', t => { t.plan(4) sget({ method: 'GET', - url: getUrl(fastify) + '/with-error-custom-header' + url: getServerUrl(fastify) + '/with-error-custom-header' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -218,7 +210,7 @@ test('custom header in notFound handler', t => { t.plan(4) sget({ method: 'GET', - url: getUrl(fastify) + '/notSupported' + url: getServerUrl(fastify) + '/notSupported' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -405,7 +397,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'PUT', - url: getUrl(fastify), + url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -419,7 +411,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'PROPFIND', - url: getUrl(fastify), + url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -433,7 +425,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'GET', - url: getUrl(fastify) + '/notSupported' + url: getServerUrl(fastify) + '/notSupported' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -445,7 +437,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'PUT', - url: getUrl(fastify) + '/test', + url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -459,7 +451,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'PROPFIND', - url: getUrl(fastify) + '/test', + url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -473,7 +465,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'GET', - url: getUrl(fastify) + '/test/notSupported' + url: getServerUrl(fastify) + '/test/notSupported' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -485,7 +477,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'PUT', - url: getUrl(fastify) + '/test2', + url: getServerUrl(fastify) + '/test2', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -499,7 +491,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'PROPFIND', - url: getUrl(fastify) + '/test2', + url: getServerUrl(fastify) + '/test2', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -513,7 +505,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'GET', - url: getUrl(fastify) + '/test2/notSupported' + url: getServerUrl(fastify) + '/test2/notSupported' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -525,7 +517,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'PUT', - url: getUrl(fastify) + '/test3/', + url: getServerUrl(fastify) + '/test3/', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -539,7 +531,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'PROPFIND', - url: getUrl(fastify) + '/test3/', + url: getServerUrl(fastify) + '/test3/', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -553,7 +545,7 @@ test('encapsulated 404', t => { t.plan(3) sget({ method: 'GET', - url: getUrl(fastify) + '/test3/notSupported' + url: getServerUrl(fastify) + '/test3/notSupported' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -717,7 +709,7 @@ test('run hooks on default 404', t => { sget({ method: 'PUT', - url: getUrl(fastify), + url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -878,7 +870,7 @@ test('run hook with encapsulated 404', t => { sget({ method: 'PUT', - url: getUrl(fastify) + '/test', + url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -948,7 +940,7 @@ test('run hook with encapsulated 404 and framework-unsupported method', t => { sget({ method: 'PROPFIND', - url: getUrl(fastify) + '/test', + url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -988,7 +980,7 @@ test('hooks check 404', t => { sget({ method: 'PUT', - url: getUrl(fastify) + '?foo=asd', + url: getServerUrl(fastify) + '?foo=asd', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { @@ -998,7 +990,7 @@ test('hooks check 404', t => { sget({ method: 'GET', - url: getUrl(fastify) + '/notSupported?foo=asd' + url: getServerUrl(fastify) + '/notSupported?foo=asd' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) @@ -1095,7 +1087,7 @@ test('Unknown method', t => { sget({ method: 'UNKNWON_METHOD', - url: getUrl(fastify) + url: getServerUrl(fastify) }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 400) @@ -1129,7 +1121,7 @@ test('recognizes errors from the http-errors module', t => { t.error(err) t.equal(res.statusCode, 404) - sget(getUrl(fastify), (err, response, body) => { + sget(getServerUrl(fastify), (err, response, body) => { t.error(err) const obj = JSON.parse(body.toString()) t.strictSame(obj, { @@ -1276,7 +1268,7 @@ test('404 inside onSend', t => { sget({ method: 'GET', - url: getUrl(fastify) + url: getServerUrl(fastify) }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 404) diff --git a/test/async-await.test.js b/test/async-await.test.js index 75ba89d130f..32e3a9ed65b 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -6,8 +6,8 @@ const sget = require('simple-get').concat const Fastify = require('..') const split = require('split2') const pino = require('pino') +const { sleep } = require('./helper') const statusCodes = require('node:http').STATUS_CODES -const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) const opts = { schema: { diff --git a/test/build-certificate.js b/test/build-certificate.js index 913701f3842..114cd254e84 100644 --- a/test/build-certificate.js +++ b/test/build-certificate.js @@ -1,6 +1,95 @@ 'use strict' -const selfCert = require('self-cert') +const os = require('os') +const forge = require('node-forge') + +// from self-cert module +function selfCert (opts) { + const options = opts || {} + const log = opts.logger || require('abstract-logging') + const now = new Date() + + if (!options.attrs) options.attrs = {} + if (!options.expires) { + options.expires = new Date( + now.getFullYear() + 5, now.getMonth() + 1, now.getDate() + ) + } + + log.debug('generating key pair') + const keys = forge.pki.rsa.generateKeyPair(options.bits || 2048) + log.debug('key pair generated') + + log.debug('generating self-signed certificate') + const cert = forge.pki.createCertificate() + cert.publicKey = keys.publicKey + cert.serialNumber = '01' + cert.validity.notBefore = now + cert.validity.notAfter = options.expires + + const attrs = [ + { name: 'commonName', value: options.attrs.commonName || os.hostname() }, + { name: 'countryName', value: options.attrs.countryName || 'US' }, + { name: 'stateOrProvinceName', value: options.attrs.stateName || 'Georgia' }, + { name: 'localityName', value: options.attrs.locality || 'Atlanta' }, + { name: 'organizationName', value: options.attrs.orgName || 'None' }, + { shortName: 'OU', value: options.attrs.shortName || 'example' } + ] + cert.setSubject(attrs) + cert.setIssuer(attrs) + + cert.setExtensions([ + { name: 'basicConstraints', cA: true }, + { + name: 'keyUsage', + keyCertSign: true, + digitalSignature: true, + nonRepudiation: true, + keyEncipherment: true, + dataEncipherment: true + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true, + codeSigning: true, + emailProtection: true, + timeStamping: true + }, + { + name: 'nsCertType', + client: true, + server: true, + email: true, + objsign: true, + sslCA: true, + emailCA: true, + objCA: true + }, + { name: 'subjectKeyIdentifier' }, + { + name: 'subjectAltName', + altNames: [{ type: 6 /* URI */, value: 'DNS: ' + attrs[0].value }].concat((function () { + const interfaces = os.networkInterfaces() + + // fix citgm: skip invalid ips (aix72-ppc64) + const ips = Object.values(interfaces).flat() + .filter(i => !!forge.util.bytesFromIP(i.address)) + .map(i => ({ type: 7 /* IP */, ip: i.address })) + + return ips + }())) + } + ]) + + cert.sign(keys.privateKey) + log.debug('certificate generated') + return { + privateKey: forge.pki.privateKeyToPem(keys.privateKey), + publicKey: forge.pki.publicKeyToPem(keys.publicKey), + certificate: forge.pki.certificateToPem(cert) + } +} async function buildCertificate () { // "global" is used in here because "t.context" is only supported by "t.beforeEach" and "t.afterEach" diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index 37cb0f53883..0a822998659 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -36,8 +36,8 @@ test('Should return 503 while closing - pipelining', async t => { await instance.close() }) -const isV19plus = semver.gte(process.version, '19.0.0') -test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, async t => { +const isVgte19 = semver.gte(process.version, '19.0.0') +test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v19.x', { skip: isVgte19 }, async t => { const fastify = Fastify({ return503OnClosing: false, forceCloseConnections: false @@ -67,7 +67,7 @@ test('Should not return 503 while closing - pipelining - return503OnClosing: fal await instance.close() }) -test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, async t => { +test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip Node < v19.x', { skip: !isVgte19 }, async t => { // Since Node v19, we will always invoke server.closeIdleConnections() // therefore our socket will be closed const fastify = Fastify({ @@ -75,9 +75,9 @@ test('Should close the socket abruptly - pipelining - return503OnClosing: false, forceCloseConnections: false }) - fastify.get('/', (req, reply) => { - fastify.close() + fastify.get('/', async (req, reply) => { reply.send({ hello: 'world' }) + fastify.close() }) await fastify.listen({ port: 0 }) diff --git a/test/close.test.js b/test/close.test.js index 336fda2b278..3870639d039 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -7,6 +7,7 @@ const Fastify = require('..') const { Client } = require('undici') const semver = require('semver') const split = require('split2') +const { sleep } = require('./helper') test('close callback', t => { t.plan(4) @@ -687,11 +688,6 @@ test('preClose async', async t => { test('preClose execution order', t => { t.plan(4) - async function sleep (ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms) - }) - } const fastify = Fastify() const order = [] fastify.addHook('onClose', onClose) diff --git a/test/custom-http-server.test.js b/test/custom-http-server.test.js index 923773b7e1a..f981b2080c5 100644 --- a/test/custom-http-server.test.js +++ b/test/custom-http-server.test.js @@ -2,126 +2,129 @@ const t = require('tap') const test = t.test -const Fastify = require('..') const http = require('node:http') -const { FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE } = require('../lib/errors') -const sget = require('simple-get').concat const dns = require('node:dns').promises +const sget = require('simple-get').concat +const Fastify = require('..') +const { FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE } = require('../lib/errors') -test('Should support a custom http server', async t => { +async function setup () { const localAddresses = await dns.lookup('localhost', { all: true }) - const minPlan = localAddresses.length - 1 || 1 - - t.plan(minPlan + 3) - const serverFactory = (handler, opts) => { - t.ok(opts.serverFactory, 'it is called once for localhost') + test('Should support a custom http server', { skip: localAddresses.length < 1 }, async t => { + t.plan(4) - const server = http.createServer((req, res) => { - req.custom = true - handler(req, res) - }) + const fastify = Fastify({ + serverFactory: (handler, opts) => { + t.ok(opts.serverFactory, 'it is called once for localhost') - return server - } + const server = http.createServer((req, res) => { + req.custom = true + handler(req, res) + }) - const fastify = Fastify({ serverFactory }) + return server + } + }) - t.teardown(fastify.close.bind(fastify)) + t.teardown(fastify.close.bind(fastify)) - fastify.get('/', (req, reply) => { - t.ok(req.raw.custom) - reply.send({ hello: 'world' }) - }) + fastify.get('/', (req, reply) => { + t.ok(req.raw.custom) + reply.send({ hello: 'world' }) + }) - await fastify.listen({ port: 0 }) + await fastify.listen({ port: 0 }) - await new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - if (err) { - return reject(err) - } - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - resolve() + await new Promise((resolve, reject) => { + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port, + rejectUnauthorized: false + }, (err, response, body) => { + if (err) { + return reject(err) + } + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + resolve() + }) }) }) -}) -test('Should not allow forceCloseConnection=idle if the server does not support closeIdleConnections', t => { - t.plan(1) + test('Should not allow forceCloseConnection=idle if the server does not support closeIdleConnections', t => { + t.plan(1) - t.throws( - () => { - Fastify({ - forceCloseConnections: 'idle', - serverFactory (handler, opts) { - return { - on () { + t.throws( + () => { + Fastify({ + forceCloseConnections: 'idle', + serverFactory (handler, opts) { + return { + on () { + } } } - } - }) - }, - FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE, - "Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method" - ) -}) - -test('Should accept user defined serverFactory and ignore secondary server creation', async t => { - const server = http.createServer(() => {}) - t.teardown(() => new Promise(resolve => server.close(resolve))) - const app = await Fastify({ - serverFactory: () => server + }) + }, + FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE, + "Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method" + ) }) - t.resolves(async () => { - await app.listen({ port: 0 }) + + test('Should accept user defined serverFactory and ignore secondary server creation', async t => { + const server = http.createServer(() => { }) + t.teardown(() => new Promise(resolve => server.close(resolve))) + const app = await Fastify({ + serverFactory: () => server + }) + t.resolves(async () => { + await app.listen({ port: 0 }) + }) }) -}) -test('Should not call close on the server if it has not created it', async t => { - const server = http.createServer() + test('Should not call close on the server if it has not created it', async t => { + const server = http.createServer() - const serverFactory = (handler, opts) => { - server.on('request', handler) - return server - } + const serverFactory = (handler, opts) => { + server.on('request', handler) + return server + } - const fastify = Fastify({ serverFactory }) + const fastify = Fastify({ serverFactory }) - fastify.get('/', (req, reply) => { - reply.send({ hello: 'world' }) - }) + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) - await fastify.ready() + await fastify.ready() - await new Promise((resolve, reject) => { - server.listen(0) - server.on('listening', resolve) - server.on('error', reject) - }) + await new Promise((resolve, reject) => { + server.listen(0) + server.on('listening', resolve) + server.on('error', reject) + }) - const address = server.address() - t.equal(server.listening, true) - await fastify.close() + const address = server.address() + t.equal(server.listening, true) + await fastify.close() - t.equal(server.listening, true) - t.same(server.address(), address) - t.same(fastify.addresses(), [address]) + t.equal(server.listening, true) + t.same(server.address(), address) + t.same(fastify.addresses(), [address]) - await new Promise((resolve, reject) => { - server.close((err) => { - if (err) { - return reject(err) - } - resolve() + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + return reject(err) + } + resolve() + }) }) + t.equal(server.listening, false) + t.same(server.address(), null) }) - t.equal(server.listening, false) - t.same(server.address(), null) -}) +} + +setup() diff --git a/test/custom-parser.0.test.js b/test/custom-parser.0.test.js index 8a683d618dd..340f710f8f0 100644 --- a/test/custom-parser.0.test.js +++ b/test/custom-parser.0.test.js @@ -5,34 +5,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat const Fastify = require('../fastify') - const jsonParser = require('fast-json-body') - -function plainTextParser (request, callback) { - let body = '' - request.setEncoding('utf8') - request.on('error', onError) - request.on('data', onData) - request.on('end', onEnd) - function onError (err) { - callback(err, null) - } - function onData (chunk) { - body += chunk - } - function onEnd () { - callback(null, body) - } -} - -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} +const { getServerUrl, plainTextParser } = require('./helper') process.removeAllListeners('warning') @@ -70,7 +44,7 @@ test('contentTypeParser should add a custom parser', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' @@ -87,7 +61,7 @@ test('contentTypeParser should add a custom parser', t => { sget({ method: 'OPTIONS', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' @@ -128,7 +102,7 @@ test('contentTypeParser should handle multiple custom parsers', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' @@ -141,7 +115,7 @@ test('contentTypeParser should handle multiple custom parsers', t => { sget({ method: 'POST', - url: getUrl(fastify) + '/hello', + url: getServerUrl(fastify) + '/hello', body: '{"hello":"world"}', headers: { 'Content-Type': 'application/ffosj' @@ -180,7 +154,7 @@ test('contentTypeParser should handle an array of custom contentTypes', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' @@ -193,7 +167,7 @@ test('contentTypeParser should handle an array of custom contentTypes', t => { sget({ method: 'POST', - url: getUrl(fastify) + '/hello', + url: getServerUrl(fastify) + '/hello', body: '{"hello":"world"}', headers: { 'Content-Type': 'application/ffosj' @@ -223,7 +197,7 @@ test('contentTypeParser should handle errors', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' @@ -284,7 +258,7 @@ test('contentTypeParser should support encapsulation, second try', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' @@ -317,7 +291,7 @@ test('contentTypeParser shouldn\'t support request with undefined "Content-Type" sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: 'unknown content type!', headers: { // 'Content-Type': undefined @@ -390,7 +364,7 @@ test('catch all content type parser', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: 'hello', headers: { 'Content-Type': 'application/jsoff' @@ -402,7 +376,7 @@ test('catch all content type parser', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: 'hello', headers: { 'Content-Type': 'very-weird-content-type' @@ -444,7 +418,7 @@ test('catch all content type parser should not interfere with other conte type p sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' @@ -456,7 +430,7 @@ test('catch all content type parser should not interfere with other conte type p sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: 'hello', headers: { 'Content-Type': 'very-weird-content-type' @@ -499,7 +473,7 @@ test('\'*\' catch undefined Content-Type requests', t => { sget({ method: 'POST', - url: getUrl(fastify) + '/', + url: getServerUrl(fastify) + '/', body: fileStream }, (err, response, body) => { t.error(err) @@ -552,7 +526,7 @@ test('Can override the default json parser', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/json' @@ -586,7 +560,7 @@ test('Can override the default plain text parser', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: 'hello world', headers: { 'Content-Type': 'text/plain' @@ -624,7 +598,7 @@ test('Can override the default json parser in a plugin', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/json' @@ -709,7 +683,7 @@ test('Should get the body as string', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/json' @@ -736,7 +710,7 @@ test('Should return defined body with no custom parser defined and content type sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: 'hello world', headers: { 'Content-Type': 'text/plain' @@ -763,7 +737,7 @@ test('Should have typeof body object with no custom parser defined, no body defi sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), headers: { 'Content-Type': 'text/plain' } diff --git a/test/custom-parser.1.test.js b/test/custom-parser.1.test.js index c1c7935cfeb..43c8355d74e 100644 --- a/test/custom-parser.1.test.js +++ b/test/custom-parser.1.test.js @@ -4,34 +4,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat const Fastify = require('../fastify') - const jsonParser = require('fast-json-body') - -function plainTextParser (request, callback) { - let body = '' - request.setEncoding('utf8') - request.on('error', onError) - request.on('data', onData) - request.on('end', onEnd) - function onError (err) { - callback(err, null) - } - function onData (chunk) { - body += chunk - } - function onEnd () { - callback(null, body) - } -} - -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} +const { getServerUrl } = require('./helper') process.removeAllListeners('warning') @@ -48,7 +22,7 @@ test('Should have typeof body object with no custom parser defined, null body an sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: null, headers: { 'Content-Type': 'text/plain' @@ -75,7 +49,7 @@ test('Should have typeof body object with no custom parser defined, undefined bo sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: undefined, headers: { 'Content-Type': 'text/plain' @@ -114,7 +88,7 @@ test('Should get the body as string', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: 'hello world', headers: { 'Content-Type': 'text/plain' @@ -153,7 +127,7 @@ test('Should get the body as buffer', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/json' @@ -192,7 +166,7 @@ test('Should get the body as buffer', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: 'hello world', headers: { 'Content-Type': 'text/plain' @@ -229,7 +203,7 @@ test('Should parse empty bodies as a string', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '', headers: { 'Content-Type': 'text/plain' @@ -242,7 +216,7 @@ test('Should parse empty bodies as a string', t => { sget({ method: 'DELETE', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '', headers: { 'Content-Type': 'text/plain', @@ -275,7 +249,7 @@ test('Should parse empty bodies as a buffer', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '', headers: { 'Content-Type': 'text/plain' @@ -309,7 +283,7 @@ test('The charset should not interfere with the content type handling', t => { sget({ method: 'POST', - url: getUrl(fastify), + url: getServerUrl(fastify), body: '{"hello":"world"}', headers: { 'Content-Type': 'application/json; charset=utf-8' @@ -322,699 +296,3 @@ test('The charset should not interfere with the content type handling', t => { }) }) }) - -test('Wrong parseAs parameter', t => { - t.plan(2) - const fastify = Fastify() - - try { - fastify.addContentTypeParser('application/json', { parseAs: 'fireworks' }, () => {}) - t.fail('should throw') - } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') - t.equal(err.message, "The body parser can only parse your data as 'string' or 'buffer', you asked 'fireworks' which is not supported.") - } -}) - -test('Should allow defining the bodyLimit per parser', t => { - t.plan(3) - const fastify = Fastify() - t.teardown(() => fastify.close()) - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser( - 'x/foo', - { parseAs: 'string', bodyLimit: 5 }, - function (req, body, done) { - t.fail('should not be invoked') - done() - } - ) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '1234567890', - headers: { - 'Content-Type': 'x/foo' - } - }, (err, response, body) => { - t.error(err) - t.strictSame(JSON.parse(body.toString()), { - statusCode: 413, - code: 'FST_ERR_CTP_BODY_TOO_LARGE', - error: 'Payload Too Large', - message: 'Request body is too large' - }) - fastify.close() - }) - }) -}) - -test('route bodyLimit should take precedence over a custom parser bodyLimit', t => { - t.plan(3) - const fastify = Fastify() - t.teardown(() => fastify.close()) - - fastify.post('/', { bodyLimit: 5 }, (request, reply) => { - reply.send(request.body) - }) - - fastify.addContentTypeParser( - 'x/foo', - { parseAs: 'string', bodyLimit: 100 }, - function (req, body, done) { - t.fail('should not be invoked') - done() - } - ) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '1234567890', - headers: { 'Content-Type': 'x/foo' } - }, (err, response, body) => { - t.error(err) - t.strictSame(JSON.parse(body.toString()), { - statusCode: 413, - code: 'FST_ERR_CTP_BODY_TOO_LARGE', - error: 'Payload Too Large', - message: 'Request body is too large' - }) - fastify.close() - }) - }) -}) - -test('should be able to use default parser for extra content type', t => { - t.plan(4) - const fastify = Fastify() - t.teardown(() => fastify.close()) - - fastify.post('/', (request, reply) => { - reply.send(request.body) - }) - - fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore')) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'text/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.strictSame(JSON.parse(body.toString()), { hello: 'world' }) - fastify.close() - }) - }) -}) - -test('contentTypeParser should add a custom parser with RegExp value', t => { - t.plan(3) - - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.options('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser(/.*\+json$/, function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - t.teardown(() => fastify.close()) - - t.test('in POST', t => { - t.plan(3) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/vnd.test+json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - }) - - t.test('in OPTIONS', t => { - t.plan(3) - - sget({ - method: 'OPTIONS', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'weird/content-type+json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) - }) - }) -}) - -test('contentTypeParser should add multiple custom parsers with RegExp values', async t => { - t.plan(6) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser(/.*\+json$/, function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.addContentTypeParser(/.*\+xml$/, function (req, payload, done) { - done(null, 'xml') - }) - - fastify.addContentTypeParser(/.*\+myExtension$/i, function (req, payload, done) { - let data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data + 'myExtension') - }) - }) - - await fastify.ready() - - { - const response = await fastify.inject({ - method: 'POST', - url: '/', - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/vnd.hello+json' - } - }) - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), '{"hello":"world"}') - } - - { - const response = await fastify.inject({ - method: 'POST', - url: '/', - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/test+xml' - } - }) - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), 'xml') - } - - await fastify.inject({ - method: 'POST', - path: '/', - payload: 'abcdefg', - headers: { - 'Content-Type': 'application/+myExtension' - } - }).then((response) => { - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), 'abcdefgmyExtension') - }).catch((err) => { - t.error(err) - }) -}) - -test('catch all content type parser should not interfere with content type parser', t => { - t.plan(10) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('*', function (req, payload, done) { - let data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data) - }) - }) - - fastify.addContentTypeParser(/^application\/.*/, function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.addContentTypeParser('text/html', function (req, payload, done) { - let data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data + 'html') - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"myKey":"myValue"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ myKey: 'myValue' })) - }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'body', - headers: { - 'Content-Type': 'very-weird-content-type' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'body') - }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'my text', - headers: { - 'Content-Type': 'text/html' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'my texthtml') - }) - }) -}) - -test('should prefer string content types over RegExp ones', t => { - t.plan(7) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser(/^application\/.*/, function (req, payload, done) { - let data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data) - }) - }) - - fastify.addContentTypeParser('application/json', function (req, payload, done) { - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"k1":"myValue", "k2": "myValue"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ k1: 'myValue', k2: 'myValue' })) - }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'javascript', - headers: { - 'Content-Type': 'application/javascript' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'javascript') - }) - }) -}) - -test('removeContentTypeParser should support arrays of content types to remove', t => { - t.plan(8) - - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - - fastify.addContentTypeParser('application/xml', function (req, payload, done) { - payload.on('data', () => {}) - payload.on('end', () => { - done(null, 'xml') - }) - }) - - fastify.addContentTypeParser(/^image\/.*/, function (req, payload, done) { - payload.on('data', () => {}) - payload.on('end', () => { - done(null, 'image') - }) - }) - - fastify.removeContentTypeParser([/^image\/.*/, 'application/json']) - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '', - headers: { - 'Content-Type': 'application/xml' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'xml') - }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '', - headers: { - 'Content-Type': 'image/png' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{test: "test"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - }) - }) -}) - -test('removeContentTypeParser should support encapsulation', t => { - t.plan(6) - - const fastify = Fastify() - - fastify.addContentTypeParser('application/xml', function (req, payload, done) { - payload.on('data', () => {}) - payload.on('end', () => { - done(null, 'xml') - }) - }) - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.register(function (instance, options, done) { - instance.removeContentTypeParser('application/xml') - - instance.post('/encapsulated', (req, reply) => { - reply.send(req.body) - }) - - done() - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify) + '/encapsulated', - body: '', - headers: { - 'Content-Type': 'application/xml' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '', - headers: { - 'Content-Type': 'application/xml' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'xml') - fastify.close() - }) - }) -}) - -test('removeAllContentTypeParsers should support encapsulation', t => { - t.plan(6) - - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.register(function (instance, options, done) { - instance.removeAllContentTypeParsers() - - instance.post('/encapsulated', (req, reply) => { - reply.send(req.body) - }) - - done() - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify) + '/encapsulated', - body: '{}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - }) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"test":1}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body.toString()).test, 1) - fastify.close() - }) - }) -}) - -test('cannot remove all content type parsers after binding', t => { - t.plan(2) - - const fastify = Fastify() - - t.teardown(fastify.close.bind(fastify)) - - fastify.listen({ port: 0 }, function (err) { - t.error(err) - - t.throws(() => fastify.removeAllContentTypeParsers()) - }) -}) - -test('cannot remove content type parsers after binding', t => { - t.plan(2) - - const fastify = Fastify() - - t.teardown(fastify.close.bind(fastify)) - - fastify.listen({ port: 0 }, function (err) { - t.error(err) - - t.throws(() => fastify.removeContentTypeParser('application/json')) - }) -}) - -test('should be able to override the default json parser after removeAllContentTypeParsers', t => { - t.plan(5) - - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.removeAllContentTypeParsers() - - fastify.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - fastify.close() - }) - }) -}) - -test('should be able to override the default plain text parser after removeAllContentTypeParsers', t => { - t.plan(5) - - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.removeAllContentTypeParsers() - - fastify.addContentTypeParser('text/plain', function (req, payload, done) { - t.ok('called') - plainTextParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') - fastify.close() - }) - }) -}) - -test('should be able to add a custom content type parser after removeAllContentTypeParsers', t => { - t.plan(5) - - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.removeAllContentTypeParsers() - - fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { - t.ok('called') - jsonParser(payload, function (err, body) { - done(err, body) - }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'POST', - url: getUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - fastify.close() - }) - }) -}) diff --git a/test/custom-parser.2.test.js b/test/custom-parser.2.test.js new file mode 100644 index 00000000000..0cc8f92953f --- /dev/null +++ b/test/custom-parser.2.test.js @@ -0,0 +1,102 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../fastify') +const { getServerUrl } = require('./helper') + +process.removeAllListeners('warning') + +test('Wrong parseAs parameter', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addContentTypeParser('application/json', { parseAs: 'fireworks' }, () => {}) + t.fail('should throw') + } catch (err) { + t.equal(err.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') + t.equal(err.message, "The body parser can only parse your data as 'string' or 'buffer', you asked 'fireworks' which is not supported.") + } +}) + +test('Should allow defining the bodyLimit per parser', t => { + t.plan(3) + const fastify = Fastify() + t.teardown(() => fastify.close()) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser( + 'x/foo', + { parseAs: 'string', bodyLimit: 5 }, + function (req, body, done) { + t.fail('should not be invoked') + done() + } + ) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '1234567890', + headers: { + 'Content-Type': 'x/foo' + } + }, (err, response, body) => { + t.error(err) + t.strictSame(JSON.parse(body.toString()), { + statusCode: 413, + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + error: 'Payload Too Large', + message: 'Request body is too large' + }) + fastify.close() + }) + }) +}) + +test('route bodyLimit should take precedence over a custom parser bodyLimit', t => { + t.plan(3) + const fastify = Fastify() + t.teardown(() => fastify.close()) + + fastify.post('/', { bodyLimit: 5 }, (request, reply) => { + reply.send(request.body) + }) + + fastify.addContentTypeParser( + 'x/foo', + { parseAs: 'string', bodyLimit: 100 }, + function (req, body, done) { + t.fail('should not be invoked') + done() + } + ) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '1234567890', + headers: { 'Content-Type': 'x/foo' } + }, (err, response, body) => { + t.error(err) + t.strictSame(JSON.parse(body.toString()), { + statusCode: 413, + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + error: 'Payload Too Large', + message: 'Request body is too large' + }) + fastify.close() + }) + }) +}) diff --git a/test/custom-parser.3.test.js b/test/custom-parser.3.test.js new file mode 100644 index 00000000000..ec8ac2b03dc --- /dev/null +++ b/test/custom-parser.3.test.js @@ -0,0 +1,245 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../fastify') +const jsonParser = require('fast-json-body') +const { getServerUrl } = require('./helper') + +process.removeAllListeners('warning') + +test('should be able to use default parser for extra content type', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(() => fastify.close()) + + fastify.post('/', (request, reply) => { + reply.send(request.body) + }) + + fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore')) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'text/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.strictSame(JSON.parse(body.toString()), { hello: 'world' }) + fastify.close() + }) + }) +}) + +test('contentTypeParser should add a custom parser with RegExp value', t => { + t.plan(3) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.options('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser(/.*\+json$/, function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + t.teardown(() => fastify.close()) + + t.test('in POST', t => { + t.plan(3) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/vnd.test+json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + }) + }) + + t.test('in OPTIONS', t => { + t.plan(3) + + sget({ + method: 'OPTIONS', + url: getServerUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'weird/content-type+json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + }) + }) + }) +}) + +test('contentTypeParser should add multiple custom parsers with RegExp values', async t => { + t.plan(6) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser(/.*\+json$/, function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.addContentTypeParser(/.*\+xml$/, function (req, payload, done) { + done(null, 'xml') + }) + + fastify.addContentTypeParser(/.*\+myExtension$/i, function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data + 'myExtension') + }) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/vnd.hello+json' + } + }) + t.equal(response.statusCode, 200) + t.same(response.payload.toString(), '{"hello":"world"}') + } + + { + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/test+xml' + } + }) + t.equal(response.statusCode, 200) + t.same(response.payload.toString(), 'xml') + } + + await fastify.inject({ + method: 'POST', + path: '/', + payload: 'abcdefg', + headers: { + 'Content-Type': 'application/+myExtension' + } + }).then((response) => { + t.equal(response.statusCode, 200) + t.same(response.payload.toString(), 'abcdefgmyExtension') + }).catch((err) => { + t.error(err) + }) +}) + +test('catch all content type parser should not interfere with content type parser', t => { + t.plan(10) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('*', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.addContentTypeParser(/^application\/.*/, function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.addContentTypeParser('text/html', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data + 'html') + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '{"myKey":"myValue"}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ myKey: 'myValue' })) + }) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: 'body', + headers: { + 'Content-Type': 'very-weird-content-type' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'body') + }) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: 'my text', + headers: { + 'Content-Type': 'text/html' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'my texthtml') + }) + }) +}) diff --git a/test/custom-parser.4.test.js b/test/custom-parser.4.test.js new file mode 100644 index 00000000000..58e0a12a3ae --- /dev/null +++ b/test/custom-parser.4.test.js @@ -0,0 +1,239 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../fastify') +const jsonParser = require('fast-json-body') +const { getServerUrl } = require('./helper') + +process.removeAllListeners('warning') + +test('should prefer string content types over RegExp ones', t => { + t.plan(7) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser(/^application\/.*/, function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '{"k1":"myValue", "k2": "myValue"}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ k1: 'myValue', k2: 'myValue' })) + }) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: 'javascript', + headers: { + 'Content-Type': 'application/javascript' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'javascript') + }) + }) +}) + +test('removeContentTypeParser should support arrays of content types to remove', t => { + t.plan(8) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.addContentTypeParser('application/xml', function (req, payload, done) { + payload.on('data', () => {}) + payload.on('end', () => { + done(null, 'xml') + }) + }) + + fastify.addContentTypeParser(/^image\/.*/, function (req, payload, done) { + payload.on('data', () => {}) + payload.on('end', () => { + done(null, 'image') + }) + }) + + fastify.removeContentTypeParser([/^image\/.*/, 'application/json']) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '', + headers: { + 'Content-Type': 'application/xml' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'xml') + }) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '', + headers: { + 'Content-Type': 'image/png' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + }) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '{test: "test"}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + }) + }) +}) + +test('removeContentTypeParser should support encapsulation', t => { + t.plan(6) + + const fastify = Fastify() + + fastify.addContentTypeParser('application/xml', function (req, payload, done) { + payload.on('data', () => {}) + payload.on('end', () => { + done(null, 'xml') + }) + }) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.register(function (instance, options, done) { + instance.removeContentTypeParser('application/xml') + + instance.post('/encapsulated', (req, reply) => { + reply.send(req.body) + }) + + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify) + '/encapsulated', + body: '', + headers: { + 'Content-Type': 'application/xml' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + }) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '', + headers: { + 'Content-Type': 'application/xml' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'xml') + fastify.close() + }) + }) +}) + +test('removeAllContentTypeParsers should support encapsulation', t => { + t.plan(6) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.register(function (instance, options, done) { + instance.removeAllContentTypeParsers() + + instance.post('/encapsulated', (req, reply) => { + reply.send(req.body) + }) + + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify) + '/encapsulated', + body: '{}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + }) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '{"test":1}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body.toString()).test, 1) + fastify.close() + }) + }) +}) diff --git a/test/custom-parser.5.test.js b/test/custom-parser.5.test.js new file mode 100644 index 00000000000..aad8fa4b401 --- /dev/null +++ b/test/custom-parser.5.test.js @@ -0,0 +1,149 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../fastify') +const jsonParser = require('fast-json-body') +const { getServerUrl, plainTextParser } = require('./helper') + +process.removeAllListeners('warning') + +test('cannot remove all content type parsers after binding', t => { + t.plan(2) + + const fastify = Fastify() + + t.teardown(fastify.close.bind(fastify)) + + fastify.listen({ port: 0 }, function (err) { + t.error(err) + + t.throws(() => fastify.removeAllContentTypeParsers()) + }) +}) + +test('cannot remove content type parsers after binding', t => { + t.plan(2) + + const fastify = Fastify() + + t.teardown(fastify.close.bind(fastify)) + + fastify.listen({ port: 0 }, function (err) { + t.error(err) + + t.throws(() => fastify.removeContentTypeParser('application/json')) + }) +}) + +test('should be able to override the default json parser after removeAllContentTypeParsers', t => { + t.plan(5) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.removeAllContentTypeParsers() + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + t.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + fastify.close() + }) + }) +}) + +test('should be able to override the default plain text parser after removeAllContentTypeParsers', t => { + t.plan(5) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.removeAllContentTypeParsers() + + fastify.addContentTypeParser('text/plain', function (req, payload, done) { + t.ok('called') + plainTextParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.toString(), 'hello world') + fastify.close() + }) + }) +}) + +test('should be able to add a custom content type parser after removeAllContentTypeParsers', t => { + t.plan(5) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.removeAllContentTypeParsers() + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + t.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), JSON.stringify({ hello: 'world' })) + fastify.close() + }) + }) +}) diff --git a/test/helper.js b/test/helper.js index 4763357b8a1..aa18f33e809 100644 --- a/test/helper.js +++ b/test/helper.js @@ -3,8 +3,11 @@ const sget = require('simple-get').concat const dns = require('node:dns').promises const stream = require('node:stream') +const { promisify } = require('node:util') const symbols = require('../lib/symbols') +module.exports.sleep = promisify(setTimeout) + /** * @param method HTTP request method * @param t tap instance @@ -421,16 +424,35 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }) } -module.exports.getLoopbackHost = async () => { - let localhostForURL +function lookupToIp (lookup) { + return lookup.family === 6 ? `[${lookup.address}]` : lookup.address +} +module.exports.getLoopbackHost = async () => { const lookup = await dns.lookup('localhost') - const localhost = lookup.address - if (lookup.family === 6) { - localhostForURL = `[${lookup.address}]` - } else { - localhostForURL = localhost + return [lookup.address, lookupToIp(lookup)] +} + +module.exports.plainTextParser = function (request, callback) { + let body = '' + request.setEncoding('utf8') + request.on('error', onError) + request.on('data', onData) + request.on('end', onEnd) + function onError (err) { + callback(err, null) + } + function onData (chunk) { + body += chunk } + function onEnd () { + callback(null, body) + } +} - return [localhost, localhostForURL] +module.exports.getServerUrl = function (app) { + const { address, port } = app.server.address() + return address === '::1' + ? `http://[${address}]:${port}` + : `http://${address}:${port}` } diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index 49fe3e7b8d9..115cb32a212 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -6,7 +6,7 @@ const test = t.test const sget = require('simple-get').concat const Fastify = require('../fastify') const fs = require('node:fs') -const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) +const { sleep } = require('./helper') process.removeAllListeners('warning') @@ -653,7 +653,7 @@ test('onRequest respond with a stream', t => { fastify.addHook('onRequest', async (req, reply) => { return new Promise((resolve, reject) => { - const stream = fs.createReadStream(process.cwd() + '/test/stream.test.js', 'utf8') + const stream = fs.createReadStream(__filename, 'utf8') // stream.pipe(res) // res.once('finish', resolve) reply.send(stream).then(() => { @@ -704,7 +704,7 @@ test('preHandler respond with a stream', t => { const order = [1, 2] fastify.addHook('preHandler', async (req, reply) => { - const stream = fs.createReadStream(process.cwd() + '/test/stream.test.js', 'utf8') + const stream = fs.createReadStream(__filename, 'utf8') reply.raw.once('finish', () => { t.equal(order.shift(), 2) }) diff --git a/test/hooks.on-listen.test.js b/test/hooks.on-listen.test.js index 38079770661..fd671593036 100644 --- a/test/hooks.on-listen.test.js +++ b/test/hooks.on-listen.test.js @@ -3,14 +3,15 @@ const { test, before } = require('tap') const Fastify = require('../fastify') const fp = require('fastify-plugin') -const dns = require('node:dns').promises const split = require('split2') +const helper = require('./helper') -let localhost +// fix citgm @aix72-ppc64 +const LISTEN_READYNESS = process.env.CITGM ? 250 : 50 +let localhost before(async function () { - const lookup = await dns.lookup('localhost') - localhost = lookup.address + [localhost] = await helper.getLoopbackHost() }) test('onListen should not be processed when .ready() is called', t => { @@ -1068,7 +1069,7 @@ test('onListen hooks do not block /1', t => { port: 0 }, err => { t.error(err) - t.ok(new Date() - startDate < 50) + t.ok(new Date() - startDate < LISTEN_READYNESS) }) }) @@ -1086,5 +1087,5 @@ test('onListen hooks do not block /2', async t => { host: 'localhost', port: 0 }) - t.ok(new Date() - startDate < 50) + t.ok(new Date() - startDate < LISTEN_READYNESS) }) diff --git a/test/hooks.test.js b/test/hooks.test.js index 9b527d4abee..02acbd0c6cc 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -11,22 +11,11 @@ const split = require('split2') const symbols = require('../lib/symbols.js') const payload = { hello: 'world' } const proxyquire = require('proxyquire') -const { promisify } = require('node:util') const { connect } = require('node:net') - -const sleep = promisify(setTimeout) +const { sleep, getServerUrl } = require('./helper') process.removeAllListeners('warning') -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} - test('hooks', t => { t.plan(49) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -760,7 +749,7 @@ test('onRoute hook should able to change the route url', t => { sget({ method: 'GET', - url: getUrl(fastify) + encodeURI('/foo') + url: getServerUrl(fastify) + encodeURI('/foo') }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) @@ -1782,7 +1771,7 @@ test('onRequest respond with a stream', t => { const fastify = Fastify() fastify.addHook('onRequest', (req, reply, done) => { - const stream = fs.createReadStream(process.cwd() + '/test/stream.test.js', 'utf8') + const stream = fs.createReadStream(__filename, 'utf8') // stream.pipe(res) // res.once('finish', done) reply.send(stream) @@ -1833,7 +1822,7 @@ test('preHandler respond with a stream', t => { const order = [1, 2] fastify.addHook('preHandler', (req, reply, done) => { - const stream = fs.createReadStream(process.cwd() + '/test/stream.test.js', 'utf8') + const stream = fs.createReadStream(__filename, 'utf8') reply.send(stream) reply.raw.once('finish', () => { t.equal(order.shift(), 2) diff --git a/test/http2/closing.test.js b/test/http2/closing.test.js index 12184e488b5..6714f69e0ae 100644 --- a/test/http2/closing.test.js +++ b/test/http2/closing.test.js @@ -6,18 +6,10 @@ const http2 = require('node:http2') const { promisify } = require('node:util') const connect = promisify(http2.connect) const { once } = require('node:events') - const { buildCertificate } = require('../build-certificate') -t.before(buildCertificate) +const { getServerUrl } = require('../helper') -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} +t.before(buildCertificate) t.test('http/2 request while fastify closing', t => { let fastify @@ -37,7 +29,7 @@ t.test('http/2 request while fastify closing', t => { t.teardown(() => { fastify.close() }) t.test('return 200', t => { - const url = getUrl(fastify) + const url = getServerUrl(fastify) const session = http2.connect(url, function () { this.request({ ':method': 'GET', @@ -84,7 +76,7 @@ t.test('http/2 request while fastify closing - return503OnClosing: false', t => t.teardown(() => { fastify.close() }) t.test('return 200', t => { - const url = getUrl(fastify) + const url = getServerUrl(fastify) const session = http2.connect(url, function () { this.request({ ':method': 'GET', @@ -120,7 +112,7 @@ t.test('http/2 closes successfully with async await', async t => { await fastify.listen({ port: 0 }) - const url = getUrl(fastify) + const url = getServerUrl(fastify) const session = await connect(url) // An error might or might not happen, as it's OS dependent. session.on('error', () => {}) @@ -139,7 +131,7 @@ t.test('https/2 closes successfully with async await', async t => { await fastify.listen({ port: 0 }) - const url = getUrl(fastify) + const url = getServerUrl(fastify) const session = await connect(url) // An error might or might not happen, as it's OS dependent. session.on('error', () => {}) @@ -162,7 +154,7 @@ t.test('http/2 server side session emits a timeout event', async t => { await fastify.listen({ port: 0 }) - const url = getUrl(fastify) + const url = getServerUrl(fastify) const session = await connect(url) const req = session.request({ ':method': 'GET', diff --git a/test/https/custom-https-server.test.js b/test/https/custom-https-server.test.js index 51a58404a96..d236589a00e 100644 --- a/test/https/custom-https-server.test.js +++ b/test/https/custom-https-server.test.js @@ -4,57 +4,60 @@ const t = require('tap') const test = t.test const Fastify = require('../..') const https = require('node:https') -const sget = require('simple-get').concat const dns = require('node:dns').promises - +const sget = require('simple-get').concat const { buildCertificate } = require('../build-certificate') -t.before(buildCertificate) -test('Should support a custom https server', async t => { - const localAddresses = await dns.lookup('localhost', { all: true }) - const minPlan = localAddresses.length - 1 || 1 +async function setup () { + await buildCertificate() - t.plan(minPlan + 3) - - const serverFactory = (handler, opts) => { - t.ok(opts.serverFactory, 'it is called once for localhost') + const localAddresses = await dns.lookup('localhost', { all: true }) - const options = { - key: global.context.key, - cert: global.context.cert - } + test('Should support a custom https server', { skip: localAddresses.length < 1 }, async t => { + t.plan(4) - const server = https.createServer(options, (req, res) => { - req.custom = true - handler(req, res) - }) + const fastify = Fastify({ + serverFactory: (handler, opts) => { + t.ok(opts.serverFactory, 'it is called once for localhost') - return server - } + const options = { + key: global.context.key, + cert: global.context.cert + } - const fastify = Fastify({ serverFactory }) + const server = https.createServer(options, (req, res) => { + req.custom = true + handler(req, res) + }) - t.teardown(fastify.close.bind(fastify)) + return server + } + }) - fastify.get('/', (req, reply) => { - t.ok(req.raw.custom) - reply.send({ hello: 'world' }) - }) + t.teardown(fastify.close.bind(fastify)) - await fastify.listen({ port: 0 }) + fastify.get('/', (req, reply) => { + t.ok(req.raw.custom) + reply.send({ hello: 'world' }) + }) - await new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - if (err) { - return reject(err) - } - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - resolve() + await fastify.listen({ port: 0 }) + + await new Promise((resolve, reject) => { + sget({ + method: 'GET', + url: 'https://localhost:' + fastify.server.address().port, + rejectUnauthorized: false + }, (err, response, body) => { + if (err) { + return reject(err) + } + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + resolve() + }) }) }) -}) +} + +setup() diff --git a/test/listen.1.test.js b/test/listen.1.test.js new file mode 100644 index 00000000000..370792a76af --- /dev/null +++ b/test/listen.1.test.js @@ -0,0 +1,101 @@ +'use strict' + +const { test, before } = require('tap') +const Fastify = require('..') +const helper = require('./helper') + +let localhost +let localhostForURL + +before(async function () { + [localhost, localhostForURL] = await helper.getLoopbackHost() +}) + +test('listen works without arguments', async t => { + process.on('warning', () => { + t.fail('should not be deprecated') + }) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + await fastify.listen() + const address = fastify.server.address() + t.equal(address.address, localhost) + t.ok(address.port > 0) +}) + +test('Async/await listen with arguments', async t => { + process.on('warning', () => { + t.fail('should not be deprecated') + }) + + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + const addr = await fastify.listen({ port: 0, host: '0.0.0.0' }) + const address = fastify.server.address() + t.equal(addr, `http://${address.address}:${address.port}`) +}) + +test('Promise listen with arguments', t => { + process.on('warning', () => { + t.fail('should not be deprecated') + }) + + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0, host: '0.0.0.0' }).then(addr => { + const address = fastify.server.address() + t.equal(addr, `http://${address.address}:${address.port}`) + }) +}) + +test('listen accepts a callback', t => { + process.on('warning', () => { + t.fail('should not be deprecated') + }) + + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }, (err) => { + t.equal(fastify.server.address().address, localhost) + t.error(err) + }) +}) + +test('listen accepts options and a callback', t => { + process.on('warning', () => { + t.fail('should not be deprecated') + }) + + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ + port: 0, + host: 'localhost', + backlog: 511, + exclusive: false, + readableAll: false, + writableAll: false, + ipv6Only: false + }, (err) => { + t.error(err) + }) +}) + +test('listen after Promise.resolve()', t => { + t.plan(2) + const f = Fastify() + t.teardown(f.close.bind(f)) + Promise.resolve() + .then(() => { + f.listen({ port: 0 }, (err, address) => { + f.server.unref() + t.equal(address, `http://${localhostForURL}:${f.server.address().port}`) + t.error(err) + }) + }) +}) diff --git a/test/listen.2.test.js b/test/listen.2.test.js new file mode 100644 index 00000000000..9861611f7d8 --- /dev/null +++ b/test/listen.2.test.js @@ -0,0 +1,103 @@ +'use strict' + +const { test, before } = require('tap') +const Fastify = require('..') +const helper = require('./helper') + +let localhostForURL + +before(async function () { + [, localhostForURL] = await helper.getLoopbackHost() +}) + +test('register after listen using Promise.resolve()', t => { + t.plan(1) + const f = Fastify() + + const handler = (req, res) => res.send({}) + Promise.resolve() + .then(() => { + f.get('/', handler) + f.register((f2, options, done) => { + f2.get('/plugin', handler) + done() + }) + return f.ready() + }) + .catch(t.error) + .then(() => t.pass('resolved')) +}) + +test('double listen errors', t => { + t.plan(3) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }, (err) => { + t.error(err) + fastify.listen({ port: fastify.server.address().port }, (err, address) => { + t.equal(address, null) + t.ok(err) + }) + }) +}) + +test('double listen errors callback with (err, address)', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }, (err1, address1) => { + t.equal(address1, `http://${localhostForURL}:${fastify.server.address().port}`) + t.error(err1) + fastify.listen({ port: fastify.server.address().port }, (err2, address2) => { + t.equal(address2, null) + t.ok(err2) + }) + }) +}) + +test('nonlocalhost double listen errors callback with (err, address)', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ host: '::1', port: 0 }, (err, address) => { + t.equal(address, `http://${'[::1]'}:${fastify.server.address().port}`) + t.error(err) + fastify.listen({ host: '::1', port: fastify.server.address().port }, (err2, address2) => { + t.equal(address2, null) + t.ok(err2) + }) + }) +}) + +test('listen twice on the same port', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }, (err1, address1) => { + t.equal(address1, `http://${localhostForURL}:${fastify.server.address().port}`) + t.error(err1) + const s2 = Fastify() + t.teardown(s2.close.bind(s2)) + s2.listen({ port: fastify.server.address().port }, (err2, address2) => { + t.equal(address2, null) + t.ok(err2) + }) + }) +}) + +test('listen twice on the same port callback with (err, address)', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }, (err1, address1) => { + const _port = fastify.server.address().port + t.equal(address1, `http://${localhostForURL}:${_port}`) + t.error(err1) + const s2 = Fastify() + t.teardown(s2.close.bind(s2)) + s2.listen({ port: _port }, (err2, address2) => { + t.equal(address2, null) + t.ok(err2) + }) + }) +}) diff --git a/test/listen.3.test.js b/test/listen.3.test.js new file mode 100644 index 00000000000..f68cba76fcd --- /dev/null +++ b/test/listen.3.test.js @@ -0,0 +1,87 @@ +'use strict' + +const os = require('node:os') +const path = require('node:path') +const fs = require('node:fs') +const { test, before } = require('tap') +const Fastify = require('..') +const helper = require('./helper') + +let localhostForURL + +before(async function () { + [, localhostForURL] = await helper.getLoopbackHost() +}) + +// https://nodejs.org/api/net.html#net_ipc_support +if (os.platform() !== 'win32') { + test('listen on socket', t => { + t.plan(3) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').slice(2, 10)}-server.sock`) + try { + fs.unlinkSync(sockFile) + } catch (e) { } + + fastify.listen({ path: sockFile }, (err, address) => { + t.error(err) + t.strictSame(fastify.addresses(), [sockFile]) + t.equal(address, sockFile) + }) + }) +} else { + test('listen on socket', t => { + t.plan(3) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + const sockFile = `\\\\.\\pipe\\${(Math.random().toString(16) + '0000000').slice(2, 10)}-server-sock` + + fastify.listen({ path: sockFile }, (err, address) => { + t.error(err) + t.strictSame(fastify.addresses(), [sockFile]) + t.equal(address, sockFile) + }) + }) +} + +test('listen without callback with (address)', t => { + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }) + .then(address => { + t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) + }) +}) + +test('double listen without callback rejects', t => { + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }) + .then(() => { + fastify.listen({ port: 0 }) + .catch(err => { + t.ok(err) + }) + }) + .catch(err => t.error(err)) +}) + +test('double listen without callback with (address)', t => { + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }) + .then(address => { + t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) + fastify.listen({ port: 0 }) + .catch(err => { + t.ok(err) + }) + }) + .catch(err => t.error(err)) +}) diff --git a/test/listen.4.test.js b/test/listen.4.test.js new file mode 100644 index 00000000000..f239bcef121 --- /dev/null +++ b/test/listen.4.test.js @@ -0,0 +1,164 @@ +'use strict' + +const { test, before } = require('tap') +const dns = require('node:dns').promises +const dnsCb = require('node:dns') +const sget = require('simple-get').concat +const Fastify = require('../fastify') +const helper = require('./helper') + +let localhostForURL + +function getUrl (fastify, lookup) { + const { port } = fastify.server.address() + if (lookup.family === 6) { + return `http://[${lookup.address}]:${port}/` + } else { + return `http://${lookup.address}:${port}/` + } +} + +before(async function () { + [, localhostForURL] = await helper.getLoopbackHost() +}) + +test('listen twice on the same port without callback rejects', t => { + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.listen({ port: 0 }) + .then(() => { + const s2 = Fastify() + t.teardown(s2.close.bind(s2)) + s2.listen({ port: fastify.server.address().port }) + .catch(err => { + t.ok(err) + }) + }) + .catch(err => t.error(err)) +}) + +test('listen twice on the same port without callback rejects with (address)', t => { + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + fastify.listen({ port: 0 }) + .then(address => { + const s2 = Fastify() + t.teardown(s2.close.bind(s2)) + t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) + s2.listen({ port: fastify.server.address().port }) + .catch(err => { + t.ok(err) + }) + }) + .catch(err => t.error(err)) +}) + +test('listen on invalid port without callback rejects', t => { + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + return fastify.listen({ port: -1 }) + .catch(err => { + t.ok(err) + return true + }) +}) + +test('listen logs the port as info', t => { + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + const msgs = [] + fastify.log.info = function (msg) { + msgs.push(msg) + } + + fastify.listen({ port: 0 }) + .then(() => { + t.ok(/http:\/\//.test(msgs[0])) + }) +}) + +test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => { + const localAddresses = await dns.lookup('localhost', { all: true }) + t.plan(2 * localAddresses.length) + + const app = Fastify() + app.get('/', async () => 'hello localhost') + t.teardown(app.close.bind(app)) + await app.listen({ port: 0, host: 'localhost' }) + + for (const lookup of localAddresses) { + await new Promise((resolve, reject) => { + sget({ + method: 'GET', + url: getUrl(app, lookup) + }, (err, response, body) => { + if (err) { return reject(err) } + t.equal(response.statusCode, 200) + t.same(body.toString(), 'hello localhost') + resolve() + }) + }) + } +}) + +test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', t => { + dnsCb.lookup('localhost', { all: true }, (err, lookups) => { + t.plan(2 + (3 * lookups.length)) + t.error(err) + + const app = Fastify() + app.get('/', async () => 'hello localhost') + app.listen({ port: 0, host: 'localhost' }, (err) => { + t.error(err) + t.teardown(app.close.bind(app)) + + for (const lookup of lookups) { + sget({ + method: 'GET', + url: getUrl(app, lookup) + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(body.toString(), 'hello localhost') + }) + } + }) + }) +}) + +test('addresses getter', async t => { + let localAddresses = await dns.lookup('localhost', { all: true }) + + t.plan(4) + const app = Fastify() + app.get('/', async () => 'hello localhost') + + t.same(app.addresses(), [], 'before ready') + await app.ready() + + t.same(app.addresses(), [], 'after ready') + await app.listen({ port: 0, host: 'localhost' }) + + // fix citgm + // dns lookup may have duplicated addresses (rhel8-s390x rhel8-ppc64le debian10-x64) + + localAddresses = [...new Set([...localAddresses.map(a => JSON.stringify({ + address: a.address, + family: typeof a.family === 'number' ? 'IPv' + a.family : a.family + }))])].sort() + + const appAddresses = app.addresses().map(a => JSON.stringify({ + address: a.address, + family: typeof a.family === 'number' ? 'IPv' + a.family : a.family + })).sort() + + t.same(appAddresses, localAddresses, 'after listen') + + await app.close() + t.same(app.addresses(), [], 'after close') +}) diff --git a/test/listen.deprecated.test.js b/test/listen.deprecated.test.js index 8f15dfc47b7..e0c039a3a5f 100644 --- a/test/listen.deprecated.test.js +++ b/test/listen.deprecated.test.js @@ -4,22 +4,16 @@ // removed when the deprecation is complete. const { test, before } = require('tap') -const dns = require('node:dns').promises const Fastify = require('..') +const helper = require('./helper') let localhost let localhostForURL process.removeAllListeners('warning') -before(async function (t) { - const lookup = await dns.lookup('localhost') - localhost = lookup.address - if (lookup.family === 6) { - localhostForURL = `[${lookup.address}]` - } else { - localhostForURL = localhost - } +before(async () => { + [localhost, localhostForURL] = await helper.getLoopbackHost() }) test('listen accepts a port and a callback', t => { diff --git a/test/listen.test.js b/test/listen.test.js deleted file mode 100644 index 32fa1e1d0d9..00000000000 --- a/test/listen.test.js +++ /dev/null @@ -1,427 +0,0 @@ -'use strict' - -const os = require('node:os') -const path = require('node:path') -const fs = require('node:fs') -const { test, before } = require('tap') -const dns = require('node:dns').promises -const dnsCb = require('node:dns') -const sget = require('simple-get').concat -const Fastify = require('..') - -let localhost -let localhostForURL - -before(async function () { - const lookup = await dns.lookup('localhost') - localhost = lookup.address - if (lookup.family === 6) { - localhostForURL = `[${lookup.address}]` - } else { - localhostForURL = localhost - } -}) - -test('listen works without arguments', async t => { - process.on('warning', () => { - t.fail('should not be deprecated') - }) - - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - await fastify.listen() - const address = fastify.server.address() - t.equal(address.address, localhost) - t.ok(address.port > 0) -}) - -test('Async/await listen with arguments', async t => { - process.on('warning', () => { - t.fail('should not be deprecated') - }) - - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - const addr = await fastify.listen({ port: 0, host: '0.0.0.0' }) - const address = fastify.server.address() - t.equal(addr, `http://${address.address}:${address.port}`) -}) - -test('Promise listen with arguments', t => { - process.on('warning', () => { - t.fail('should not be deprecated') - }) - - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0, host: '0.0.0.0' }).then(addr => { - const address = fastify.server.address() - t.equal(addr, `http://${address.address}:${address.port}`) - }) -}) - -test('listen accepts a callback', t => { - process.on('warning', () => { - t.fail('should not be deprecated') - }) - - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }, (err) => { - t.equal(fastify.server.address().address, localhost) - t.error(err) - }) -}) - -test('listen accepts options and a callback', t => { - process.on('warning', () => { - t.fail('should not be deprecated') - }) - - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ - port: 0, - host: 'localhost', - backlog: 511, - exclusive: false, - readableAll: false, - writableAll: false, - ipv6Only: false - }, (err) => { - t.error(err) - }) -}) - -test('listen after Promise.resolve()', t => { - t.plan(2) - const f = Fastify() - t.teardown(f.close.bind(f)) - Promise.resolve() - .then(() => { - f.listen({ port: 0 }, (err, address) => { - f.server.unref() - t.equal(address, `http://${localhostForURL}:${f.server.address().port}`) - t.error(err) - }) - }) -}) - -test('register after listen using Promise.resolve()', t => { - t.plan(1) - const f = Fastify() - - const handler = (req, res) => res.send({}) - Promise.resolve() - .then(() => { - f.get('/', handler) - f.register((f2, options, done) => { - f2.get('/plugin', handler) - done() - }) - return f.ready() - }) - .catch(t.error) - .then(() => t.pass('resolved')) -}) - -test('double listen errors', t => { - t.plan(3) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }, (err) => { - t.error(err) - fastify.listen({ port: fastify.server.address().port }, (err, address) => { - t.equal(address, null) - t.ok(err) - }) - }) -}) - -test('double listen errors callback with (err, address)', t => { - t.plan(4) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }, (err1, address1) => { - t.equal(address1, `http://${localhostForURL}:${fastify.server.address().port}`) - t.error(err1) - fastify.listen({ port: fastify.server.address().port }, (err2, address2) => { - t.equal(address2, null) - t.ok(err2) - }) - }) -}) - -test('nonlocalhost double listen errors callback with (err, address)', t => { - t.plan(4) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ host: '::1', port: 0 }, (err, address) => { - t.equal(address, `http://${'[::1]'}:${fastify.server.address().port}`) - t.error(err) - fastify.listen({ host: '::1', port: fastify.server.address().port }, (err2, address2) => { - t.equal(address2, null) - t.ok(err2) - }) - }) -}) - -test('listen twice on the same port', t => { - t.plan(4) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }, (err1, address1) => { - t.equal(address1, `http://${localhostForURL}:${fastify.server.address().port}`) - t.error(err1) - const s2 = Fastify() - t.teardown(s2.close.bind(s2)) - s2.listen({ port: fastify.server.address().port }, (err2, address2) => { - t.equal(address2, null) - t.ok(err2) - }) - }) -}) - -test('listen twice on the same port callback with (err, address)', t => { - t.plan(4) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }, (err1, address1) => { - const _port = fastify.server.address().port - t.equal(address1, `http://${localhostForURL}:${_port}`) - t.error(err1) - const s2 = Fastify() - t.teardown(s2.close.bind(s2)) - s2.listen({ port: _port }, (err2, address2) => { - t.equal(address2, null) - t.ok(err2) - }) - }) -}) - -// https://nodejs.org/api/net.html#net_ipc_support -if (os.platform() !== 'win32') { - test('listen on socket', t => { - t.plan(3) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - - const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').slice(2, 10)}-server.sock`) - try { - fs.unlinkSync(sockFile) - } catch (e) { } - - fastify.listen({ path: sockFile }, (err, address) => { - t.error(err) - t.strictSame(fastify.addresses(), [sockFile]) - t.equal(address, sockFile) - }) - }) -} else { - test('listen on socket', t => { - t.plan(3) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - - const sockFile = `\\\\.\\pipe\\${(Math.random().toString(16) + '0000000').slice(2, 10)}-server-sock` - - fastify.listen({ path: sockFile }, (err, address) => { - t.error(err) - t.strictSame(fastify.addresses(), [sockFile]) - t.equal(address, sockFile) - }) - }) -} - -test('listen without callback with (address)', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }) - .then(address => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - }) -}) - -test('double listen without callback rejects', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }) - .then(() => { - fastify.listen({ port: 0 }) - .catch(err => { - t.ok(err) - }) - }) - .catch(err => t.error(err)) -}) - -test('double listen without callback with (address)', t => { - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }) - .then(address => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - fastify.listen({ port: 0 }) - .catch(err => { - t.ok(err) - }) - }) - .catch(err => t.error(err)) -}) - -test('listen twice on the same port without callback rejects', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - - fastify.listen({ port: 0 }) - .then(() => { - const s2 = Fastify() - t.teardown(s2.close.bind(s2)) - s2.listen({ port: fastify.server.address().port }) - .catch(err => { - t.ok(err) - }) - }) - .catch(err => t.error(err)) -}) - -test('listen twice on the same port without callback rejects with (address)', t => { - t.plan(2) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }) - .then(address => { - const s2 = Fastify() - t.teardown(s2.close.bind(s2)) - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - s2.listen({ port: fastify.server.address().port }) - .catch(err => { - t.ok(err) - }) - }) - .catch(err => t.error(err)) -}) - -test('listen on invalid port without callback rejects', t => { - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - return fastify.listen({ port: -1 }) - .catch(err => { - t.ok(err) - return true - }) -}) - -test('listen logs the port as info', t => { - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - - const msgs = [] - fastify.log.info = function (msg) { - msgs.push(msg) - } - - fastify.listen({ port: 0 }) - .then(() => { - t.ok(/http:\/\//.test(msgs[0])) - }) -}) - -test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => { - const lookups = await dns.lookup('localhost', { all: true }) - t.plan(2 * lookups.length) - - const app = Fastify() - app.get('/', async () => 'hello localhost') - t.teardown(app.close.bind(app)) - await app.listen({ port: 0, host: 'localhost' }) - - for (const lookup of lookups) { - await new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: getUrl(app, lookup) - }, (err, response, body) => { - if (err) { return reject(err) } - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello localhost') - resolve() - }) - }) - } -}) - -test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', t => { - dnsCb.lookup('localhost', { all: true }, (err, lookups) => { - t.plan(2 + (3 * lookups.length)) - t.error(err) - - const app = Fastify() - app.get('/', async () => 'hello localhost') - app.listen({ port: 0, host: 'localhost' }, (err) => { - t.error(err) - t.teardown(app.close.bind(app)) - - for (const lookup of lookups) { - sget({ - method: 'GET', - url: getUrl(app, lookup) - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello localhost') - }) - } - }) - }) -}) - -test('addresses getter', async t => { - t.plan(4) - const app = Fastify() - app.get('/', async () => 'hello localhost') - - t.same(app.addresses(), [], 'before ready') - await app.ready() - - t.same(app.addresses(), [], 'after ready') - await app.listen({ port: 0, host: 'localhost' }) - const { port } = app.server.address() - const localAddresses = await dns.lookup('localhost', { all: true }) - for (const address of localAddresses) { - address.port = port - if (typeof address.family === 'number') { - address.family = 'IPv' + address.family - } - } - const appAddresses = app.addresses() - for (const address of appAddresses) { - if (typeof address.family === 'number') { - address.family = 'IPv' + address.family - } - } - localAddresses.sort((a, b) => a.address.localeCompare(b.address)) - appAddresses.sort((a, b) => a.address.localeCompare(b.address)) - t.same(appAddresses, localAddresses, 'after listen') - - await app.close() - t.same(app.addresses(), [], 'after close') -}) - -function getUrl (fastify, lookup) { - const { port } = fastify.server.address() - if (lookup.family === 6) { - return `http://[${lookup.address}]:${port}/` - } else { - return `http://${lookup.address}:${port}/` - } -} diff --git a/test/logger/instantiation.test.js b/test/logger/instantiation.test.js index 37cceed26bc..9d274672344 100644 --- a/test/logger/instantiation.test.js +++ b/test/logger/instantiation.test.js @@ -149,21 +149,29 @@ t.test('logger instantiation', (t) => { { reqId: /req-/, req: { method: 'GET', url: '/' }, msg: 'incoming request' }, { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } ] - t.plan(lines.length + 3) + const { file, cleanup } = createTempFile(t) + if (process.env.CITGM) { fs.writeFileSync(file, '') } const fastify = Fastify({ logger: { file } }) - t.teardown(() => { - // cleanup the file after sonic-boom closed - // otherwise we may face racing condition - fastify.log[streamSym].once('close', cleanup) - // we must flush the stream ourself - // otherwise buffer may whole sonic-boom - fastify.log[streamSym].flushSync() - // end after flushing to actually close file - fastify.log[streamSym].end() + + t.teardown(async () => { + await helper.sleep(250) + // may fail on win + try { + // cleanup the file after sonic-boom closed + // otherwise we may face racing condition + fastify.log[streamSym].once('close', cleanup) + // we must flush the stream ourself + // otherwise buffer may whole sonic-boom + fastify.log[streamSym].flushSync() + // end after flushing to actually close file + fastify.log[streamSym].end() + } catch (err) { + console.warn(err) + } }) t.teardown(fastify.close.bind(fastify)) @@ -174,19 +182,20 @@ t.test('logger instantiation', (t) => { await fastify.ready() await fastify.listen({ port: 0, host: localhost }) - await request(`http://${localhostForURL}:` + fastify.server.address().port) - // we already own the full log - const stream = fs.createReadStream(file).pipe(split(JSON.parse)) - t.teardown(stream.resume.bind(stream)) + await helper.sleep(250) + + const log = fs.readFileSync(file, 'utf8').split('\n') + // strip last line + log.pop() let id - for await (const [line] of on(stream, 'data')) { + for (let line of log) { + line = JSON.parse(line) if (id === undefined && line.reqId) id = line.reqId if (id !== undefined && line.reqId) t.equal(line.reqId, id) t.match(line, lines.shift()) - if (lines.length === 0) break } }) diff --git a/test/logger/logger-test-utils.js b/test/logger/logger-test-utils.js index a62df088964..cce6669163b 100644 --- a/test/logger/logger-test-utils.js +++ b/test/logger/logger-test-utils.js @@ -16,7 +16,7 @@ function createDeferredPromise () { let count = 0 function createTempFile () { - const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${process.hrtime().toString()}-${count++}`) + const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${count++}`) function cleanup () { try { fs.unlinkSync(file) diff --git a/test/plugin.1.test.js b/test/plugin.1.test.js new file mode 100644 index 00000000000..ffd39c4763c --- /dev/null +++ b/test/plugin.1.test.js @@ -0,0 +1,249 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const t = require('tap') +const test = t.test +const Fastify = require('../fastify') +const sget = require('simple-get').concat +const fp = require('fastify-plugin') + +test('require a plugin', t => { + t.plan(1) + const fastify = Fastify() + fastify.register(require('./plugin.helper')) + fastify.ready(() => { + t.ok(fastify.test) + }) +}) + +test('plugin metadata - ignore prefix', t => { + t.plan(2) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + fastify.register(plugin, { prefix: 'foo' }) + + fastify.inject({ + method: 'GET', + url: '/' + }, function (err, res) { + t.error(err) + t.equal(res.payload, 'hello') + }) + + function plugin (instance, opts, done) { + instance.get('/', function (request, reply) { + reply.send('hello') + }) + done() + } +}) + +test('plugin metadata - naming plugins', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.register(require('./plugin.name.display')) + fastify.register(function (fastify, opts, done) { + // one line + t.equal(fastify.pluginName, 'function (fastify, opts, done) { -- // one line') + done() + }) + fastify.register(function fooBar (fastify, opts, done) { + t.equal(fastify.pluginName, 'fooBar') + done() + }) + + await fastify.ready() +}) + +test('fastify.register with fastify-plugin should not encapsulate his code', t => { + t.plan(10) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('test', () => {}) + t.ok(i.test) + n() + })) + + t.notOk(instance.test) + + // the decoration is added at the end + instance.after(() => { + t.ok(instance.test) + }) + + instance.get('/', (req, reply) => { + t.ok(instance.test) + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.ready(() => { + t.notOk(fastify.test) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) + +test('fastify.register with fastify-plugin should provide access to external fastify instance if opts argument is a function', t => { + t.plan(22) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('global', () => {}) + t.ok(i.global) + n() + })) + + instance.register((i, o, n) => n(), p => { + t.notOk(p === instance || p === fastify) + t.ok(instance.isPrototypeOf(p)) + t.ok(fastify.isPrototypeOf(p)) + t.ok(p.global) + }) + + instance.register((i, o, n) => { + i.decorate('local', () => {}) + n() + }) + + instance.register((i, o, n) => n(), p => t.notOk(p.local)) + + instance.register((i, o, n) => { + t.ok(i.local) + n() + }, p => p.decorate('local', () => {})) + + instance.register((i, o, n) => n(), p => t.notOk(p.local)) + + instance.register(fp((i, o, n) => { + t.ok(i.global_2) + n() + }), p => p.decorate('global_2', () => 'hello')) + + instance.register((i, o, n) => { + i.decorate('global_2', () => 'world') + n() + }, p => p.get('/', (req, reply) => { + t.ok(p.global_2) + reply.send({ hello: p.global_2() }) + })) + + t.notOk(instance.global) + t.notOk(instance.global_2) + t.notOk(instance.local) + + // the decoration is added at the end + instance.after(() => { + t.ok(instance.global) + t.equal(instance.global_2(), 'hello') + t.notOk(instance.local) + }) + + done() + }) + + fastify.ready(() => { + t.notOk(fastify.global) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) + +test('fastify.register with fastify-plugin registers fastify level plugins', t => { + t.plan(15) + const fastify = Fastify() + + function fastifyPlugin (instance, opts, done) { + instance.decorate('test', 'first') + t.ok(instance.test) + done() + } + + function innerPlugin (instance, opts, done) { + instance.decorate('test2', 'second') + done() + } + + fastify.register(fp(fastifyPlugin)) + + fastify.register((instance, opts, done) => { + t.ok(instance.test) + instance.register(fp(innerPlugin)) + + instance.get('/test2', (req, reply) => { + t.ok(instance.test2) + reply.send({ test2: instance.test2 }) + }) + + done() + }) + + fastify.ready(() => { + t.ok(fastify.test) + t.notOk(fastify.test2) + }) + + fastify.get('/', (req, reply) => { + t.ok(fastify.test) + reply.send({ test: fastify.test }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { test: 'first' }) + }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/test2' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { test2: 'second' }) + }) + }) +}) diff --git a/test/plugin.2.test.js b/test/plugin.2.test.js new file mode 100644 index 00000000000..1fad6c29b92 --- /dev/null +++ b/test/plugin.2.test.js @@ -0,0 +1,328 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const t = require('tap') +const test = t.test +const Fastify = require('../fastify') +const sget = require('simple-get').concat +const fp = require('fastify-plugin') + +test('check dependencies - should not throw', t => { + t.plan(12) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('test', () => {}) + t.ok(i.test) + n() + })) + + instance.register(fp((i, o, n) => { + try { + i.decorate('otherTest', () => {}, ['test']) + t.ok(i.test) + t.ok(i.otherTest) + n() + } catch (e) { + t.fail() + } + })) + + instance.get('/', (req, reply) => { + t.ok(instance.test) + t.ok(instance.otherTest) + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.ready(() => { + t.notOk(fastify.test) + t.notOk(fastify.otherTest) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) + +test('check dependencies - should throw', t => { + t.plan(12) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + try { + i.decorate('otherTest', () => {}, ['test']) + t.fail() + } catch (e) { + t.equal(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.equal(e.message, 'The decorator is missing dependency \'test\'.') + } + n() + })) + + instance.register(fp((i, o, n) => { + i.decorate('test', () => {}) + t.ok(i.test) + t.notOk(i.otherTest) + n() + })) + + instance.get('/', (req, reply) => { + t.ok(instance.test) + t.notOk(instance.otherTest) + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.ready(() => { + t.notOk(fastify.test) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) + +test('set the plugin name based on the plugin displayName symbol', t => { + t.plan(6) + const fastify = Fastify() + + fastify.register(fp((fastify, opts, done) => { + t.equal(fastify.pluginName, 'fastify -> plugin-A') + fastify.register(fp((fastify, opts, done) => { + t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') + done() + }, { name: 'plugin-AB' })) + fastify.register(fp((fastify, opts, done) => { + t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + done() + }, { name: 'plugin-AC' })) + done() + }, { name: 'plugin-A' })) + + fastify.register(fp((fastify, opts, done) => { + t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC -> plugin-B') + done() + }, { name: 'plugin-B' })) + + t.equal(fastify.pluginName, 'fastify') + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.close() + }) +}) + +test('plugin name will change when using no encapsulation', t => { + t.plan(6) + const fastify = Fastify() + + fastify.register(fp((fastify, opts, done) => { + // store it in a different variable will hold the correct name + const pluginName = fastify.pluginName + fastify.register(fp((fastify, opts, done) => { + t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') + done() + }, { name: 'plugin-AB' })) + fastify.register(fp((fastify, opts, done) => { + t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + done() + }, { name: 'plugin-AC' })) + setImmediate(() => { + // normally we would expect the name plugin-A + // but we operate on the same instance in each plugin + t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + t.equal(pluginName, 'fastify -> plugin-A') + }) + done() + }, { name: 'plugin-A' })) + + t.equal(fastify.pluginName, 'fastify') + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.close() + }) +}) + +test('plugin name is undefined when accessing in no plugin context', t => { + t.plan(2) + const fastify = Fastify() + + t.equal(fastify.pluginName, 'fastify') + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.close() + }) +}) + +test('set the plugin name based on the plugin function name', t => { + t.plan(5) + const fastify = Fastify() + + fastify.register(function myPluginA (fastify, opts, done) { + t.equal(fastify.pluginName, 'myPluginA') + fastify.register(function myPluginAB (fastify, opts, done) { + t.equal(fastify.pluginName, 'myPluginAB') + done() + }) + setImmediate(() => { + // exact name due to encapsulation + t.equal(fastify.pluginName, 'myPluginA') + }) + done() + }) + + fastify.register(function myPluginB (fastify, opts, done) { + t.equal(fastify.pluginName, 'myPluginB') + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.close() + }) +}) + +test('approximate a plugin name when no meta data is available', t => { + t.plan(7) + const fastify = Fastify() + + fastify.register((fastify, opts, done) => { + // A + t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.equal(fastify.pluginName.includes('// A'), true) + fastify.register((fastify, opts, done) => { + // B + t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.equal(fastify.pluginName.includes('// B'), true) + done() + }) + setImmediate(() => { + t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.equal(fastify.pluginName.includes('// A'), true) + }) + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.close() + }) +}) + +test('approximate a plugin name also when fastify-plugin has no meta data', t => { + t.plan(4) + const fastify = Fastify() + // plugin name is got from current file name + const pluginName = /plugin\.2\.test/ + const pluginNameWithFunction = /plugin\.2\.test-auto-\d+ -> B/ + + fastify.register(fp((fastify, opts, done) => { + t.match(fastify.pluginName, pluginName) + fastify.register(fp(function B (fastify, opts, done) { + // function has name + t.match(fastify.pluginName, pluginNameWithFunction) + done() + })) + setImmediate(() => { + t.match(fastify.pluginName, pluginNameWithFunction) + }) + done() + })) + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.close() + }) +}) + +test('plugin encapsulation', t => { + t.plan(10) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('test', 'first') + n() + })) + + instance.get('/first', (req, reply) => { + reply.send({ plugin: instance.test }) + }) + + done() + }) + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('test', 'second') + n() + })) + + instance.get('/second', (req, reply) => { + reply.send({ plugin: instance.test }) + }) + + done() + }) + + fastify.ready(() => { + t.notOk(fastify.test) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/first' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { plugin: 'first' }) + }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/second' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + t.same(JSON.parse(body), { plugin: 'second' }) + }) + }) +}) diff --git a/test/plugin.3.test.js b/test/plugin.3.test.js new file mode 100644 index 00000000000..71cf8a7b088 --- /dev/null +++ b/test/plugin.3.test.js @@ -0,0 +1,311 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const t = require('tap') +const test = t.test +const Fastify = require('../fastify') +const sget = require('simple-get').concat +const fp = require('fastify-plugin') + +test('if a plugin raises an error and there is not a callback to handle it, the server must not start', t => { + t.plan(2) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + done(new Error('err')) + }) + + fastify.listen({ port: 0 }, err => { + t.ok(err instanceof Error) + t.equal(err.message, 'err') + }) +}) + +test('add hooks after route declaration', t => { + t.plan(3) + const fastify = Fastify() + + function plugin (instance, opts, done) { + instance.decorateRequest('check', null) + instance.addHook('onRequest', (req, reply, done) => { + req.check = {} + done() + }) + setImmediate(done) + } + fastify.register(fp(plugin)) + + fastify.register((instance, options, done) => { + instance.addHook('preHandler', function b (req, res, done) { + req.check.hook2 = true + done() + }) + + instance.get('/', (req, reply) => { + reply.send(req.check) + }) + + instance.addHook('preHandler', function c (req, res, done) { + req.check.hook3 = true + done() + }) + + done() + }) + + fastify.addHook('preHandler', function a (req, res, done) { + req.check.hook1 = true + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.same(JSON.parse(body), { hook1: true, hook2: true, hook3: true }) + fastify.close() + }) + }) +}) + +test('nested plugins', t => { + t.plan(5) + + const fastify = Fastify() + + t.teardown(fastify.close.bind(fastify)) + + fastify.register(function (fastify, opts, done) { + fastify.register((fastify, opts, done) => { + fastify.get('/', function (req, reply) { + reply.send('I am child 1') + }) + done() + }, { prefix: '/child1' }) + + fastify.register((fastify, opts, done) => { + fastify.get('/', function (req, reply) { + reply.send('I am child 2') + }) + done() + }, { prefix: '/child2' }) + + done() + }, { prefix: '/parent' }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/parent/child1' + }, (err, response, body) => { + t.error(err) + t.same(body.toString(), 'I am child 1') + }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/parent/child2' + }, (err, response, body) => { + t.error(err) + t.same(body.toString(), 'I am child 2') + }) + }) +}) + +test('nested plugins awaited', t => { + t.plan(5) + + const fastify = Fastify() + + t.teardown(fastify.close.bind(fastify)) + + fastify.register(async function wrap (fastify, opts) { + await fastify.register(async function child1 (fastify, opts) { + fastify.get('/', function (req, reply) { + reply.send('I am child 1') + }) + }, { prefix: '/child1' }) + + await fastify.register(async function child2 (fastify, opts) { + fastify.get('/', function (req, reply) { + reply.send('I am child 2') + }) + }, { prefix: '/child2' }) + }, { prefix: '/parent' }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/parent/child1' + }, (err, response, body) => { + t.error(err) + t.same(body.toString(), 'I am child 1') + }) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/parent/child2' + }, (err, response, body) => { + t.error(err) + t.same(body.toString(), 'I am child 2') + }) + }) +}) + +test('plugin metadata - decorators', t => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', true) + fastify.decorateReply('plugin1', true) + fastify.decorateRequest('plugin1', true) + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + decorators: { + fastify: ['plugin1'], + reply: ['plugin1'], + request: ['plugin1'] + } + } + + fastify.register(plugin) + + fastify.ready(() => { + t.ok(fastify.plugin) + }) + + function plugin (instance, opts, done) { + instance.decorate('plugin', true) + done() + } +}) + +test('plugin metadata - decorators - should throw', t => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', true) + fastify.decorateReply('plugin1', true) + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + decorators: { + fastify: ['plugin1'], + reply: ['plugin1'], + request: ['plugin1'] + } + } + + fastify.register(plugin) + fastify.ready((err) => { + t.equal(err.message, "The decorator 'plugin1' is not present in Request") + }) + + function plugin (instance, opts, done) { + instance.decorate('plugin', true) + done() + } +}) + +test('plugin metadata - decorators - should throw with plugin name', t => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', true) + fastify.decorateReply('plugin1', true) + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'the-plugin', + decorators: { + fastify: ['plugin1'], + reply: ['plugin1'], + request: ['plugin1'] + } + } + + fastify.register(plugin) + fastify.ready((err) => { + t.equal(err.message, "The decorator 'plugin1' required by 'the-plugin' is not present in Request") + }) + + function plugin (instance, opts, done) { + instance.decorate('plugin', true) + done() + } +}) + +test('plugin metadata - dependencies', t => { + t.plan(1) + const fastify = Fastify() + + dependency[Symbol.for('skip-override')] = true + dependency[Symbol.for('plugin-meta')] = { + name: 'plugin' + } + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + dependencies: ['plugin'] + } + + fastify.register(dependency) + fastify.register(plugin) + + fastify.ready(() => { + t.pass('everything right') + }) + + function dependency (instance, opts, done) { + done() + } + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - dependencies (nested)', t => { + t.plan(1) + const fastify = Fastify() + + dependency[Symbol.for('skip-override')] = true + dependency[Symbol.for('plugin-meta')] = { + name: 'plugin' + } + + nested[Symbol.for('skip-override')] = true + nested[Symbol.for('plugin-meta')] = { + dependencies: ['plugin'] + } + + fastify.register(dependency) + fastify.register(plugin) + + fastify.ready(() => { + t.pass('everything right') + }) + + function dependency (instance, opts, done) { + done() + } + + function plugin (instance, opts, done) { + instance.register(nested) + done() + } + + function nested (instance, opts, done) { + done() + } +}) diff --git a/test/plugin.4.test.js b/test/plugin.4.test.js new file mode 100644 index 00000000000..a162f939cd2 --- /dev/null +++ b/test/plugin.4.test.js @@ -0,0 +1,416 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const t = require('tap') +const test = t.test +const Fastify = require('../fastify') +const fp = require('fastify-plugin') +const fakeTimer = require('@sinonjs/fake-timers') + +test('pluginTimeout', t => { + t.plan(5) + const fastify = Fastify({ + pluginTimeout: 10 + }) + fastify.register(function (app, opts, done) { + // to no call done on purpose + }) + fastify.ready((err) => { + t.ok(err) + t.equal(err.message, + "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // to no call done on purpose'. You may have forgotten to call 'done' function or to resolve a Promise") + t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.ok(err.cause) + t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + }) +}) + +test('pluginTimeout - named function', t => { + t.plan(5) + const fastify = Fastify({ + pluginTimeout: 10 + }) + fastify.register(function nameFunction (app, opts, done) { + // to no call done on purpose + }) + fastify.ready((err) => { + t.ok(err) + t.equal(err.message, + "fastify-plugin: Plugin did not start in time: 'nameFunction'. You may have forgotten to call 'done' function or to resolve a Promise") + t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.ok(err.cause) + t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + }) +}) + +test('pluginTimeout default', t => { + t.plan(5) + const clock = fakeTimer.install({ shouldClearNativeTimers: true }) + + const fastify = Fastify() + fastify.register(function (app, opts, done) { + // default time elapsed without calling done + clock.tick(10000) + }) + + fastify.ready((err) => { + t.ok(err) + t.equal(err.message, + "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // default time elapsed without calling done'. You may have forgotten to call 'done' function or to resolve a Promise") + t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.ok(err.cause) + t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + }) + + t.teardown(clock.uninstall) +}) + +test('plugin metadata - version', t => { + t.plan(1) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '2.0.0' + } + + fastify.register(plugin) + + fastify.ready(() => { + t.pass('everything right') + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - version range', t => { + t.plan(1) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '>=2.0.0' + } + + fastify.register(plugin) + + fastify.ready(() => { + t.pass('everything right') + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - version not matching requirement', t => { + t.plan(2) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '99.0.0' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.ok(err) + t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - version not matching requirement 2', t => { + t.plan(2) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '<=3.0.0' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.ok(err) + t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - version not matching requirement 3', t => { + t.plan(2) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '>=99.0.0' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.ok(err) + t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - release candidate', t => { + t.plan(2) + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { + value: '99.0.0-rc.1' + }) + + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '99.x' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.error(err) + t.pass('everything right') + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('fastify-rc loads prior version plugins', t => { + t.plan(2) + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { + value: '99.0.0-rc.1' + }) + + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '^98.1.0' + } + plugin2[Symbol.for('plugin-meta')] = { + name: 'plugin2', + fastify: '98.x' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.error(err) + t.pass('everything right') + }) + + function plugin (instance, opts, done) { + done() + } + + function plugin2 (instance, opts, done) { + done() + } +}) + +test('hasPlugin method exists as a function', t => { + t.plan(1) + + const fastify = Fastify() + t.equal(typeof fastify.hasPlugin, 'function') +}) + +test('hasPlugin returns true if the specified plugin has been registered', async t => { + t.plan(4) + + const fastify = Fastify() + + function pluginA (fastify, opts, done) { + t.ok(fastify.hasPlugin('plugin-A')) + done() + } + pluginA[Symbol.for('fastify.display-name')] = 'plugin-A' + fastify.register(pluginA) + + fastify.register(function pluginB (fastify, opts, done) { + t.ok(fastify.hasPlugin('pluginB')) + done() + }) + + fastify.register(function (fastify, opts, done) { + // one line + t.ok(fastify.hasPlugin('function (fastify, opts, done) { -- // one line')) + done() + }) + + await fastify.ready() + + t.ok(fastify.hasPlugin('fastify')) +}) + +test('hasPlugin returns false if the specified plugin has not been registered', t => { + t.plan(1) + + const fastify = Fastify() + t.notOk(fastify.hasPlugin('pluginFoo')) +}) + +test('hasPlugin returns false when using encapsulation', async t => { + t.plan(25) + + const fastify = Fastify() + + fastify.register(function pluginA (fastify, opts, done) { + t.ok(fastify.hasPlugin('pluginA')) + t.notOk(fastify.hasPlugin('pluginAA')) + t.notOk(fastify.hasPlugin('pluginAAA')) + t.notOk(fastify.hasPlugin('pluginAB')) + t.notOk(fastify.hasPlugin('pluginB')) + + fastify.register(function pluginAA (fastify, opts, done) { + t.notOk(fastify.hasPlugin('pluginA')) + t.ok(fastify.hasPlugin('pluginAA')) + t.notOk(fastify.hasPlugin('pluginAAA')) + t.notOk(fastify.hasPlugin('pluginAB')) + t.notOk(fastify.hasPlugin('pluginB')) + + fastify.register(function pluginAAA (fastify, opts, done) { + t.notOk(fastify.hasPlugin('pluginA')) + t.notOk(fastify.hasPlugin('pluginAA')) + t.ok(fastify.hasPlugin('pluginAAA')) + t.notOk(fastify.hasPlugin('pluginAB')) + t.notOk(fastify.hasPlugin('pluginB')) + + done() + }) + + done() + }) + + fastify.register(function pluginAB (fastify, opts, done) { + t.notOk(fastify.hasPlugin('pluginA')) + t.notOk(fastify.hasPlugin('pluginAA')) + t.notOk(fastify.hasPlugin('pluginAAA')) + t.ok(fastify.hasPlugin('pluginAB')) + t.notOk(fastify.hasPlugin('pluginB')) + + done() + }) + + done() + }) + + fastify.register(function pluginB (fastify, opts, done) { + t.notOk(fastify.hasPlugin('pluginA')) + t.notOk(fastify.hasPlugin('pluginAA')) + t.notOk(fastify.hasPlugin('pluginAAA')) + t.notOk(fastify.hasPlugin('pluginAB')) + t.ok(fastify.hasPlugin('pluginB')) + + done() + }) + + await fastify.ready() +}) + +test('hasPlugin returns true when using no encapsulation', async t => { + t.plan(26) + + const fastify = Fastify() + + fastify.register(fp((fastify, opts, done) => { + t.equal(fastify.pluginName, 'fastify -> plugin-AA') + t.ok(fastify.hasPlugin('plugin-AA')) + t.notOk(fastify.hasPlugin('plugin-A')) + t.notOk(fastify.hasPlugin('plugin-AAA')) + t.notOk(fastify.hasPlugin('plugin-AB')) + t.notOk(fastify.hasPlugin('plugin-B')) + + fastify.register(fp((fastify, opts, done) => { + t.ok(fastify.hasPlugin('plugin-AA')) + t.ok(fastify.hasPlugin('plugin-A')) + t.notOk(fastify.hasPlugin('plugin-AAA')) + t.notOk(fastify.hasPlugin('plugin-AB')) + t.notOk(fastify.hasPlugin('plugin-B')) + + fastify.register(fp((fastify, opts, done) => { + t.ok(fastify.hasPlugin('plugin-AA')) + t.ok(fastify.hasPlugin('plugin-A')) + t.ok(fastify.hasPlugin('plugin-AAA')) + t.notOk(fastify.hasPlugin('plugin-AB')) + t.notOk(fastify.hasPlugin('plugin-B')) + + done() + }, { name: 'plugin-AAA' })) + + done() + }, { name: 'plugin-A' })) + + fastify.register(fp((fastify, opts, done) => { + t.ok(fastify.hasPlugin('plugin-AA')) + t.ok(fastify.hasPlugin('plugin-A')) + t.ok(fastify.hasPlugin('plugin-AAA')) + t.ok(fastify.hasPlugin('plugin-AB')) + t.notOk(fastify.hasPlugin('plugin-B')) + + done() + }, { name: 'plugin-AB' })) + + done() + }, { name: 'plugin-AA' })) + + fastify.register(fp((fastify, opts, done) => { + t.ok(fastify.hasPlugin('plugin-AA')) + t.ok(fastify.hasPlugin('plugin-A')) + t.ok(fastify.hasPlugin('plugin-AAA')) + t.ok(fastify.hasPlugin('plugin-AB')) + t.ok(fastify.hasPlugin('plugin-B')) + + done() + }, { name: 'plugin-B' })) + + await fastify.ready() +}) + +test('hasPlugin returns true when using encapsulation', async t => { + t.plan(2) + + const fastify = Fastify() + + const pluginCallback = function (server, options, done) { + done() + } + const pluginName = 'awesome-plugin' + const plugin = fp(pluginCallback, { name: pluginName }) + + fastify.register(plugin) + + fastify.register(async (server) => { + t.ok(server.hasPlugin(pluginName)) + }) + + fastify.register(async function foo (server) { + server.register(async function bar (server) { + t.ok(server.hasPlugin(pluginName)) + }) + }) + + await fastify.ready() +}) diff --git a/test/plugin.test.js b/test/plugin.test.js deleted file mode 100644 index 51e45e18cb9..00000000000 --- a/test/plugin.test.js +++ /dev/null @@ -1,1275 +0,0 @@ -'use strict' - -/* eslint no-prototype-builtins: 0 */ - -const t = require('tap') -const test = t.test -const Fastify = require('..') -const sget = require('simple-get').concat -const fp = require('fastify-plugin') -const fakeTimer = require('@sinonjs/fake-timers') - -test('require a plugin', t => { - t.plan(1) - const fastify = Fastify() - fastify.register(require('./plugin.helper')) - fastify.ready(() => { - t.ok(fastify.test) - }) -}) - -test('plugin metadata - ignore prefix', t => { - t.plan(2) - const fastify = Fastify() - - plugin[Symbol.for('skip-override')] = true - fastify.register(plugin, { prefix: 'foo' }) - - fastify.inject({ - method: 'GET', - url: '/' - }, function (err, res) { - t.error(err) - t.equal(res.payload, 'hello') - }) - - function plugin (instance, opts, done) { - instance.get('/', function (request, reply) { - reply.send('hello') - }) - done() - } -}) - -test('plugin metadata - naming plugins', async t => { - t.plan(2) - const fastify = Fastify() - - fastify.register(require('./plugin.name.display')) - fastify.register(function (fastify, opts, done) { - // one line - t.equal(fastify.pluginName, 'function (fastify, opts, done) { -- // one line') - done() - }) - fastify.register(function fooBar (fastify, opts, done) { - t.equal(fastify.pluginName, 'fooBar') - done() - }) - - await fastify.ready() -}) - -test('fastify.register with fastify-plugin should not encapsulate his code', t => { - t.plan(10) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - instance.register(fp((i, o, n) => { - i.decorate('test', () => {}) - t.ok(i.test) - n() - })) - - t.notOk(instance.test) - - // the decoration is added at the end - instance.after(() => { - t.ok(instance.test) - }) - - instance.get('/', (req, reply) => { - t.ok(instance.test) - reply.send({ hello: 'world' }) - }) - - done() - }) - - fastify.ready(() => { - t.notOk(fastify.test) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) -}) - -test('fastify.register with fastify-plugin should provide access to external fastify instance if opts argument is a function', t => { - t.plan(22) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - instance.register(fp((i, o, n) => { - i.decorate('global', () => {}) - t.ok(i.global) - n() - })) - - instance.register((i, o, n) => n(), p => { - t.notOk(p === instance || p === fastify) - t.ok(instance.isPrototypeOf(p)) - t.ok(fastify.isPrototypeOf(p)) - t.ok(p.global) - }) - - instance.register((i, o, n) => { - i.decorate('local', () => {}) - n() - }) - - instance.register((i, o, n) => n(), p => t.notOk(p.local)) - - instance.register((i, o, n) => { - t.ok(i.local) - n() - }, p => p.decorate('local', () => {})) - - instance.register((i, o, n) => n(), p => t.notOk(p.local)) - - instance.register(fp((i, o, n) => { - t.ok(i.global_2) - n() - }), p => p.decorate('global_2', () => 'hello')) - - instance.register((i, o, n) => { - i.decorate('global_2', () => 'world') - n() - }, p => p.get('/', (req, reply) => { - t.ok(p.global_2) - reply.send({ hello: p.global_2() }) - })) - - t.notOk(instance.global) - t.notOk(instance.global_2) - t.notOk(instance.local) - - // the decoration is added at the end - instance.after(() => { - t.ok(instance.global) - t.equal(instance.global_2(), 'hello') - t.notOk(instance.local) - }) - - done() - }) - - fastify.ready(() => { - t.notOk(fastify.global) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) -}) - -test('fastify.register with fastify-plugin registers fastify level plugins', t => { - t.plan(15) - const fastify = Fastify() - - function fastifyPlugin (instance, opts, done) { - instance.decorate('test', 'first') - t.ok(instance.test) - done() - } - - function innerPlugin (instance, opts, done) { - instance.decorate('test2', 'second') - done() - } - - fastify.register(fp(fastifyPlugin)) - - fastify.register((instance, opts, done) => { - t.ok(instance.test) - instance.register(fp(innerPlugin)) - - instance.get('/test2', (req, reply) => { - t.ok(instance.test2) - reply.send({ test2: instance.test2 }) - }) - - done() - }) - - fastify.ready(() => { - t.ok(fastify.test) - t.notOk(fastify.test2) - }) - - fastify.get('/', (req, reply) => { - t.ok(fastify.test) - reply.send({ test: fastify.test }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { test: 'first' }) - }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/test2' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { test2: 'second' }) - }) - }) -}) - -test('check dependencies - should not throw', t => { - t.plan(12) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - instance.register(fp((i, o, n) => { - i.decorate('test', () => {}) - t.ok(i.test) - n() - })) - - instance.register(fp((i, o, n) => { - try { - i.decorate('otherTest', () => {}, ['test']) - t.ok(i.test) - t.ok(i.otherTest) - n() - } catch (e) { - t.fail() - } - })) - - instance.get('/', (req, reply) => { - t.ok(instance.test) - t.ok(instance.otherTest) - reply.send({ hello: 'world' }) - }) - - done() - }) - - fastify.ready(() => { - t.notOk(fastify.test) - t.notOk(fastify.otherTest) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) -}) - -test('check dependencies - should throw', t => { - t.plan(12) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - instance.register(fp((i, o, n) => { - try { - i.decorate('otherTest', () => {}, ['test']) - t.fail() - } catch (e) { - t.equal(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') - t.equal(e.message, 'The decorator is missing dependency \'test\'.') - } - n() - })) - - instance.register(fp((i, o, n) => { - i.decorate('test', () => {}) - t.ok(i.test) - t.notOk(i.otherTest) - n() - })) - - instance.get('/', (req, reply) => { - t.ok(instance.test) - t.notOk(instance.otherTest) - reply.send({ hello: 'world' }) - }) - - done() - }) - - fastify.ready(() => { - t.notOk(fastify.test) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) -}) - -test('set the plugin name based on the plugin displayName symbol', t => { - t.plan(6) - const fastify = Fastify() - - fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A') - fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') - done() - }, { name: 'plugin-AB' })) - fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') - done() - }, { name: 'plugin-AC' })) - done() - }, { name: 'plugin-A' })) - - fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC -> plugin-B') - done() - }, { name: 'plugin-B' })) - - t.equal(fastify.pluginName, 'fastify') - - fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() - }) -}) - -test('plugin name will change when using no encapsulation', t => { - t.plan(6) - const fastify = Fastify() - - fastify.register(fp((fastify, opts, done) => { - // store it in a different variable will hold the correct name - const pluginName = fastify.pluginName - fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') - done() - }, { name: 'plugin-AB' })) - fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') - done() - }, { name: 'plugin-AC' })) - setImmediate(() => { - // normally we would expect the name plugin-A - // but we operate on the same instance in each plugin - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') - t.equal(pluginName, 'fastify -> plugin-A') - }) - done() - }, { name: 'plugin-A' })) - - t.equal(fastify.pluginName, 'fastify') - - fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() - }) -}) - -test('plugin name is undefined when accessing in no plugin context', t => { - t.plan(2) - const fastify = Fastify() - - t.equal(fastify.pluginName, 'fastify') - - fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() - }) -}) - -test('set the plugin name based on the plugin function name', t => { - t.plan(5) - const fastify = Fastify() - - fastify.register(function myPluginA (fastify, opts, done) { - t.equal(fastify.pluginName, 'myPluginA') - fastify.register(function myPluginAB (fastify, opts, done) { - t.equal(fastify.pluginName, 'myPluginAB') - done() - }) - setImmediate(() => { - // exact name due to encapsulation - t.equal(fastify.pluginName, 'myPluginA') - }) - done() - }) - - fastify.register(function myPluginB (fastify, opts, done) { - t.equal(fastify.pluginName, 'myPluginB') - done() - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() - }) -}) - -test('approximate a plugin name when no meta data is available', t => { - t.plan(7) - const fastify = Fastify() - - fastify.register((fastify, opts, done) => { - // A - t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) - t.equal(fastify.pluginName.includes('// A'), true) - fastify.register((fastify, opts, done) => { - // B - t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) - t.equal(fastify.pluginName.includes('// B'), true) - done() - }) - setImmediate(() => { - t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) - t.equal(fastify.pluginName.includes('// A'), true) - }) - done() - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() - }) -}) - -test('approximate a plugin name also when fastify-plugin has no meta data', t => { - t.plan(4) - const fastify = Fastify() - - fastify.register(fp((fastify, opts, done) => { - t.match(fastify.pluginName, /plugin\.test/) - fastify.register(fp(function B (fastify, opts, done) { - // function has name - t.match(fastify.pluginName, /plugin\.test-auto-\d+ -> B/) - done() - })) - setImmediate(() => { - t.match(fastify.pluginName, /plugin\.test-auto-\d+ -> B/) - }) - done() - })) - - fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() - }) -}) - -test('plugin encapsulation', t => { - t.plan(10) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - instance.register(fp((i, o, n) => { - i.decorate('test', 'first') - n() - })) - - instance.get('/first', (req, reply) => { - reply.send({ plugin: instance.test }) - }) - - done() - }) - - fastify.register((instance, opts, done) => { - instance.register(fp((i, o, n) => { - i.decorate('test', 'second') - n() - })) - - instance.get('/second', (req, reply) => { - reply.send({ plugin: instance.test }) - }) - - done() - }) - - fastify.ready(() => { - t.notOk(fastify.test) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { plugin: 'first' }) - }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { plugin: 'second' }) - }) - }) -}) - -test('if a plugin raises an error and there is not a callback to handle it, the server must not start', t => { - t.plan(2) - const fastify = Fastify() - - fastify.register((instance, opts, done) => { - done(new Error('err')) - }) - - fastify.listen({ port: 0 }, err => { - t.ok(err instanceof Error) - t.equal(err.message, 'err') - }) -}) - -test('add hooks after route declaration', t => { - t.plan(3) - const fastify = Fastify() - - function plugin (instance, opts, done) { - instance.decorateRequest('check', null) - instance.addHook('onRequest', (req, reply, done) => { - req.check = {} - done() - }) - setImmediate(done) - } - fastify.register(fp(plugin)) - - fastify.register((instance, options, done) => { - instance.addHook('preHandler', function b (req, res, done) { - req.check.hook2 = true - done() - }) - - instance.get('/', (req, reply) => { - reply.send(req.check) - }) - - instance.addHook('preHandler', function c (req, res, done) { - req.check.hook3 = true - done() - }) - - done() - }) - - fastify.addHook('preHandler', function a (req, res, done) { - req.check.hook1 = true - done() - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.same(JSON.parse(body), { hook1: true, hook2: true, hook3: true }) - fastify.close() - }) - }) -}) - -test('nested plugins', t => { - t.plan(5) - - const fastify = Fastify() - - t.teardown(fastify.close.bind(fastify)) - - fastify.register(function (fastify, opts, done) { - fastify.register((fastify, opts, done) => { - fastify.get('/', function (req, reply) { - reply.send('I am child 1') - }) - done() - }, { prefix: '/child1' }) - - fastify.register((fastify, opts, done) => { - fastify.get('/', function (req, reply) { - reply.send('I am child 2') - }) - done() - }, { prefix: '/child2' }) - - done() - }, { prefix: '/parent' }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/parent/child1' - }, (err, response, body) => { - t.error(err) - t.same(body.toString(), 'I am child 1') - }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/parent/child2' - }, (err, response, body) => { - t.error(err) - t.same(body.toString(), 'I am child 2') - }) - }) -}) - -test('nested plugins awaited', t => { - t.plan(5) - - const fastify = Fastify() - - t.teardown(fastify.close.bind(fastify)) - - fastify.register(async function wrap (fastify, opts) { - await fastify.register(async function child1 (fastify, opts) { - fastify.get('/', function (req, reply) { - reply.send('I am child 1') - }) - }, { prefix: '/child1' }) - - await fastify.register(async function child2 (fastify, opts) { - fastify.get('/', function (req, reply) { - reply.send('I am child 2') - }) - }, { prefix: '/child2' }) - }, { prefix: '/parent' }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/parent/child1' - }, (err, response, body) => { - t.error(err) - t.same(body.toString(), 'I am child 1') - }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/parent/child2' - }, (err, response, body) => { - t.error(err) - t.same(body.toString(), 'I am child 2') - }) - }) -}) - -test('plugin metadata - decorators', t => { - t.plan(1) - const fastify = Fastify() - - fastify.decorate('plugin1', true) - fastify.decorateReply('plugin1', true) - fastify.decorateRequest('plugin1', true) - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - decorators: { - fastify: ['plugin1'], - reply: ['plugin1'], - request: ['plugin1'] - } - } - - fastify.register(plugin) - - fastify.ready(() => { - t.ok(fastify.plugin) - }) - - function plugin (instance, opts, done) { - instance.decorate('plugin', true) - done() - } -}) - -test('plugin metadata - decorators - should throw', t => { - t.plan(1) - const fastify = Fastify() - - fastify.decorate('plugin1', true) - fastify.decorateReply('plugin1', true) - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - decorators: { - fastify: ['plugin1'], - reply: ['plugin1'], - request: ['plugin1'] - } - } - - fastify.register(plugin) - fastify.ready((err) => { - t.equal(err.message, "The decorator 'plugin1' is not present in Request") - }) - - function plugin (instance, opts, done) { - instance.decorate('plugin', true) - done() - } -}) - -test('plugin metadata - decorators - should throw with plugin name', t => { - t.plan(1) - const fastify = Fastify() - - fastify.decorate('plugin1', true) - fastify.decorateReply('plugin1', true) - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - name: 'the-plugin', - decorators: { - fastify: ['plugin1'], - reply: ['plugin1'], - request: ['plugin1'] - } - } - - fastify.register(plugin) - fastify.ready((err) => { - t.equal(err.message, "The decorator 'plugin1' required by 'the-plugin' is not present in Request") - }) - - function plugin (instance, opts, done) { - instance.decorate('plugin', true) - done() - } -}) - -test('plugin metadata - dependencies', t => { - t.plan(1) - const fastify = Fastify() - - dependency[Symbol.for('skip-override')] = true - dependency[Symbol.for('plugin-meta')] = { - name: 'plugin' - } - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - dependencies: ['plugin'] - } - - fastify.register(dependency) - fastify.register(plugin) - - fastify.ready(() => { - t.pass('everything right') - }) - - function dependency (instance, opts, done) { - done() - } - - function plugin (instance, opts, done) { - done() - } -}) - -test('plugin metadata - dependencies (nested)', t => { - t.plan(1) - const fastify = Fastify() - - dependency[Symbol.for('skip-override')] = true - dependency[Symbol.for('plugin-meta')] = { - name: 'plugin' - } - - nested[Symbol.for('skip-override')] = true - nested[Symbol.for('plugin-meta')] = { - dependencies: ['plugin'] - } - - fastify.register(dependency) - fastify.register(plugin) - - fastify.ready(() => { - t.pass('everything right') - }) - - function dependency (instance, opts, done) { - done() - } - - function plugin (instance, opts, done) { - instance.register(nested) - done() - } - - function nested (instance, opts, done) { - done() - } -}) - -test('pluginTimeout', t => { - t.plan(5) - const fastify = Fastify({ - pluginTimeout: 10 - }) - fastify.register(function (app, opts, done) { - // to no call done on purpose - }) - fastify.ready((err) => { - t.ok(err) - t.equal(err.message, - "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // to no call done on purpose'. You may have forgotten to call 'done' function or to resolve a Promise") - t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') - t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') - }) -}) - -test('pluginTimeout - named function', t => { - t.plan(5) - const fastify = Fastify({ - pluginTimeout: 10 - }) - fastify.register(function nameFunction (app, opts, done) { - // to no call done on purpose - }) - fastify.ready((err) => { - t.ok(err) - t.equal(err.message, - "fastify-plugin: Plugin did not start in time: 'nameFunction'. You may have forgotten to call 'done' function or to resolve a Promise") - t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') - t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') - }) -}) - -test('pluginTimeout default', t => { - t.plan(5) - const clock = fakeTimer.install({ shouldClearNativeTimers: true }) - - const fastify = Fastify() - fastify.register(function (app, opts, done) { - // default time elapsed without calling done - clock.tick(10000) - }) - - fastify.ready((err) => { - t.ok(err) - t.equal(err.message, - "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // default time elapsed without calling done'. You may have forgotten to call 'done' function or to resolve a Promise") - t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') - t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') - }) - - t.teardown(clock.uninstall) -}) - -test('plugin metadata - version', t => { - t.plan(1) - const fastify = Fastify() - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '2.0.0' - } - - fastify.register(plugin) - - fastify.ready(() => { - t.pass('everything right') - }) - - function plugin (instance, opts, done) { - done() - } -}) - -test('plugin metadata - version range', t => { - t.plan(1) - const fastify = Fastify() - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '>=2.0.0' - } - - fastify.register(plugin) - - fastify.ready(() => { - t.pass('everything right') - }) - - function plugin (instance, opts, done) { - done() - } -}) - -test('plugin metadata - version not matching requirement', t => { - t.plan(2) - const fastify = Fastify() - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '99.0.0' - } - - fastify.register(plugin) - - fastify.ready((err) => { - t.ok(err) - t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') - }) - - function plugin (instance, opts, done) { - done() - } -}) - -test('plugin metadata - version not matching requirement 2', t => { - t.plan(2) - const fastify = Fastify() - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '<=3.0.0' - } - - fastify.register(plugin) - - fastify.ready((err) => { - t.ok(err) - t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') - }) - - function plugin (instance, opts, done) { - done() - } -}) - -test('plugin metadata - version not matching requirement 3', t => { - t.plan(2) - const fastify = Fastify() - - plugin[Symbol.for('skip-override')] = true - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '>=99.0.0' - } - - fastify.register(plugin) - - fastify.ready((err) => { - t.ok(err) - t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') - }) - - function plugin (instance, opts, done) { - done() - } -}) - -test('plugin metadata - release candidate', t => { - t.plan(2) - const fastify = Fastify() - Object.defineProperty(fastify, 'version', { - value: '99.0.0-rc.1' - }) - - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '99.x' - } - - fastify.register(plugin) - - fastify.ready((err) => { - t.error(err) - t.pass('everything right') - }) - - function plugin (instance, opts, done) { - done() - } -}) - -test('fastify-rc loads prior version plugins', t => { - t.plan(2) - const fastify = Fastify() - Object.defineProperty(fastify, 'version', { - value: '99.0.0-rc.1' - }) - - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '^98.1.0' - } - plugin2[Symbol.for('plugin-meta')] = { - name: 'plugin2', - fastify: '98.x' - } - - fastify.register(plugin) - - fastify.ready((err) => { - t.error(err) - t.pass('everything right') - }) - - function plugin (instance, opts, done) { - done() - } - - function plugin2 (instance, opts, done) { - done() - } -}) - -test('hasPlugin method exists as a function', t => { - t.plan(1) - - const fastify = Fastify() - t.equal(typeof fastify.hasPlugin, 'function') -}) - -test('hasPlugin returns true if the specified plugin has been registered', async t => { - t.plan(4) - - const fastify = Fastify() - - function pluginA (fastify, opts, done) { - t.ok(fastify.hasPlugin('plugin-A')) - done() - } - pluginA[Symbol.for('fastify.display-name')] = 'plugin-A' - fastify.register(pluginA) - - fastify.register(function pluginB (fastify, opts, done) { - t.ok(fastify.hasPlugin('pluginB')) - done() - }) - - fastify.register(function (fastify, opts, done) { - // one line - t.ok(fastify.hasPlugin('function (fastify, opts, done) { -- // one line')) - done() - }) - - await fastify.ready() - - t.ok(fastify.hasPlugin('fastify')) -}) - -test('hasPlugin returns false if the specified plugin has not been registered', t => { - t.plan(1) - - const fastify = Fastify() - t.notOk(fastify.hasPlugin('pluginFoo')) -}) - -test('hasPlugin returns false when using encapsulation', async t => { - t.plan(25) - - const fastify = Fastify() - - fastify.register(function pluginA (fastify, opts, done) { - t.ok(fastify.hasPlugin('pluginA')) - t.notOk(fastify.hasPlugin('pluginAA')) - t.notOk(fastify.hasPlugin('pluginAAA')) - t.notOk(fastify.hasPlugin('pluginAB')) - t.notOk(fastify.hasPlugin('pluginB')) - - fastify.register(function pluginAA (fastify, opts, done) { - t.notOk(fastify.hasPlugin('pluginA')) - t.ok(fastify.hasPlugin('pluginAA')) - t.notOk(fastify.hasPlugin('pluginAAA')) - t.notOk(fastify.hasPlugin('pluginAB')) - t.notOk(fastify.hasPlugin('pluginB')) - - fastify.register(function pluginAAA (fastify, opts, done) { - t.notOk(fastify.hasPlugin('pluginA')) - t.notOk(fastify.hasPlugin('pluginAA')) - t.ok(fastify.hasPlugin('pluginAAA')) - t.notOk(fastify.hasPlugin('pluginAB')) - t.notOk(fastify.hasPlugin('pluginB')) - - done() - }) - - done() - }) - - fastify.register(function pluginAB (fastify, opts, done) { - t.notOk(fastify.hasPlugin('pluginA')) - t.notOk(fastify.hasPlugin('pluginAA')) - t.notOk(fastify.hasPlugin('pluginAAA')) - t.ok(fastify.hasPlugin('pluginAB')) - t.notOk(fastify.hasPlugin('pluginB')) - - done() - }) - - done() - }) - - fastify.register(function pluginB (fastify, opts, done) { - t.notOk(fastify.hasPlugin('pluginA')) - t.notOk(fastify.hasPlugin('pluginAA')) - t.notOk(fastify.hasPlugin('pluginAAA')) - t.notOk(fastify.hasPlugin('pluginAB')) - t.ok(fastify.hasPlugin('pluginB')) - - done() - }) - - await fastify.ready() -}) - -test('hasPlugin returns true when using no encapsulation', async t => { - t.plan(26) - - const fastify = Fastify() - - fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-AA') - t.ok(fastify.hasPlugin('plugin-AA')) - t.notOk(fastify.hasPlugin('plugin-A')) - t.notOk(fastify.hasPlugin('plugin-AAA')) - t.notOk(fastify.hasPlugin('plugin-AB')) - t.notOk(fastify.hasPlugin('plugin-B')) - - fastify.register(fp((fastify, opts, done) => { - t.ok(fastify.hasPlugin('plugin-AA')) - t.ok(fastify.hasPlugin('plugin-A')) - t.notOk(fastify.hasPlugin('plugin-AAA')) - t.notOk(fastify.hasPlugin('plugin-AB')) - t.notOk(fastify.hasPlugin('plugin-B')) - - fastify.register(fp((fastify, opts, done) => { - t.ok(fastify.hasPlugin('plugin-AA')) - t.ok(fastify.hasPlugin('plugin-A')) - t.ok(fastify.hasPlugin('plugin-AAA')) - t.notOk(fastify.hasPlugin('plugin-AB')) - t.notOk(fastify.hasPlugin('plugin-B')) - - done() - }, { name: 'plugin-AAA' })) - - done() - }, { name: 'plugin-A' })) - - fastify.register(fp((fastify, opts, done) => { - t.ok(fastify.hasPlugin('plugin-AA')) - t.ok(fastify.hasPlugin('plugin-A')) - t.ok(fastify.hasPlugin('plugin-AAA')) - t.ok(fastify.hasPlugin('plugin-AB')) - t.notOk(fastify.hasPlugin('plugin-B')) - - done() - }, { name: 'plugin-AB' })) - - done() - }, { name: 'plugin-AA' })) - - fastify.register(fp((fastify, opts, done) => { - t.ok(fastify.hasPlugin('plugin-AA')) - t.ok(fastify.hasPlugin('plugin-A')) - t.ok(fastify.hasPlugin('plugin-AAA')) - t.ok(fastify.hasPlugin('plugin-AB')) - t.ok(fastify.hasPlugin('plugin-B')) - - done() - }, { name: 'plugin-B' })) - - await fastify.ready() -}) - -test('hasPlugin returns true when using encapsulation', async t => { - t.plan(2) - - const fastify = Fastify() - - const pluginCallback = function (server, options, done) { - done() - } - const pluginName = 'awesome-plugin' - const plugin = fp(pluginCallback, { name: pluginName }) - - fastify.register(plugin) - - fastify.register(async (server) => { - t.ok(server.hasPlugin(pluginName)) - }) - - fastify.register(async function foo (server) { - server.register(async function bar (server) { - t.ok(server.hasPlugin(pluginName)) - }) - }) - - await fastify.ready() -}) diff --git a/test/reply-trailers.test.js b/test/reply-trailers.test.js index 40b47e21b0b..f49275a58b1 100644 --- a/test/reply-trailers.test.js +++ b/test/reply-trailers.test.js @@ -5,8 +5,7 @@ const test = t.test const Fastify = require('..') const { Readable } = require('node:stream') const { createHash } = require('node:crypto') -const { promisify } = require('node:util') -const sleep = promisify(setTimeout) +const { sleep } = require('./helper') test('send trailers when payload is empty string', t => { t.plan(5) diff --git a/test/route.1.test.js b/test/route.1.test.js new file mode 100644 index 00000000000..5ca84a9cc6a --- /dev/null +++ b/test/route.1.test.js @@ -0,0 +1,309 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('..') +const { + FST_ERR_INSTANCE_ALREADY_LISTENING, + FST_ERR_ROUTE_METHOD_INVALID +} = require('../lib/errors') +const { getServerUrl } = require('./helper') + +test('route', t => { + t.plan(10) + const test = t.test + + test('route - get', t => { + t.plan(4) + + const fastify = Fastify() + t.doesNotThrow(() => + fastify.route({ + method: 'GET', + url: '/', + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } + }, + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + ) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) + }) + + test('missing schema - route', t => { + t.plan(4) + + const fastify = Fastify() + t.doesNotThrow(() => + fastify.route({ + method: 'GET', + url: '/missing', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + ) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/missing' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) + }) + + test('invalid handler attribute - route', t => { + t.plan(1) + + const fastify = Fastify() + t.throws(() => fastify.get('/', { handler: 'not a function' }, () => { })) + }) + + test('Add Multiple methods per route all uppercase', t => { + t.plan(7) + + const fastify = Fastify() + t.doesNotThrow(() => + fastify.route({ + method: ['GET', 'DELETE'], + url: '/multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + })) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + + sget({ + method: 'DELETE', + url: getServerUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) + }) + + test('Add Multiple methods per route all lowercase', t => { + t.plan(7) + + const fastify = Fastify() + t.doesNotThrow(() => + fastify.route({ + method: ['get', 'delete'], + url: '/multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + })) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + + sget({ + method: 'DELETE', + url: getServerUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) + }) + + test('Add Multiple methods per route mixed uppercase and lowercase', t => { + t.plan(7) + + const fastify = Fastify() + t.doesNotThrow(() => + fastify.route({ + method: ['GET', 'delete'], + url: '/multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + })) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + + sget({ + method: 'DELETE', + url: getServerUrl(fastify) + '/multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) + }) + + test('Add invalid Multiple methods per route', t => { + t.plan(1) + + const fastify = Fastify() + t.throws(() => + fastify.route({ + method: ['GET', 1], + url: '/invalid-method', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_ROUTE_METHOD_INVALID()) + }) + + test('Add method', t => { + t.plan(1) + + const fastify = Fastify() + t.throws(() => + fastify.route({ + method: 1, + url: '/invalid-method', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_ROUTE_METHOD_INVALID()) + }) + + test('Add additional multiple methods to existing route', t => { + t.plan(7) + + const fastify = Fastify() + t.doesNotThrow(() => { + fastify.get('/add-multiple', function (req, reply) { + reply.send({ hello: 'Bob!' }) + }) + fastify.route({ + method: ['PUT', 'DELETE'], + url: '/add-multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + sget({ + method: 'PUT', + url: getServerUrl(fastify) + '/add-multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + + sget({ + method: 'DELETE', + url: getServerUrl(fastify) + '/add-multiple' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) + }) + + test('cannot add another route after binding', t => { + t.plan(1) + + const fastify = Fastify() + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + t.teardown(() => { fastify.close() }) + + t.throws(() => fastify.route({ + method: 'GET', + url: '/another-get-route', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_INSTANCE_ALREADY_LISTENING('Cannot add route!')) + }) + }) +}) + +test('invalid schema - route', t => { + t.plan(3) + + const fastify = Fastify() + fastify.route({ + handler: () => { }, + method: 'GET', + url: '/invalid', + schema: { + querystring: { + id: 'string' + } + } + }) + fastify.after(err => { + t.notOk(err, 'the error is throw on preReady') + }) + fastify.ready(err => { + t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.match(err.message, /Failed building the validation schema for GET: \/invalid/) + }) +}) diff --git a/test/route.2.test.js b/test/route.2.test.js new file mode 100644 index 00000000000..1175784c2f7 --- /dev/null +++ b/test/route.2.test.js @@ -0,0 +1,99 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('../fastify') + +test('same route definition object on multiple prefixes', async t => { + t.plan(2) + + const routeObject = { + handler: () => { }, + method: 'GET', + url: '/simple' + } + + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.register(async function (f) { + f.addHook('onRoute', (routeOptions) => { + t.equal(routeOptions.url, '/v1/simple') + }) + f.route(routeObject) + }, { prefix: '/v1' }) + fastify.register(async function (f) { + f.addHook('onRoute', (routeOptions) => { + t.equal(routeOptions.url, '/v2/simple') + }) + f.route(routeObject) + }, { prefix: '/v2' }) + + await fastify.ready() +}) + +test('path can be specified in place of uri', t => { + t.plan(3) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/path', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + + const reqOpts = { + method: 'GET', + url: '/path' + } + + fastify.inject(reqOpts, (err, res) => { + t.error(err) + t.equal(res.statusCode, 200) + t.same(JSON.parse(res.payload), { hello: 'world' }) + }) +}) + +test('invalid bodyLimit option - route', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.route({ + bodyLimit: false, + method: 'PUT', + handler: () => null + }) + t.fail('bodyLimit must be an integer') + } catch (err) { + t.equal(err.message, "'bodyLimit' option must be an integer > 0. Got 'false'") + } + + try { + fastify.post('/url', { bodyLimit: 10000.1 }, () => null) + t.fail('bodyLimit must be an integer') + } catch (err) { + t.equal(err.message, "'bodyLimit' option must be an integer > 0. Got '10000.1'") + } +}) + +test('handler function in options of shorthand route should works correctly', t => { + t.plan(3) + + const fastify = Fastify() + fastify.get('/foo', { + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 200) + t.same(JSON.parse(res.payload), { hello: 'world' }) + }) +}) diff --git a/test/route.3.test.js b/test/route.3.test.js new file mode 100644 index 00000000000..7126d9e8f32 --- /dev/null +++ b/test/route.3.test.js @@ -0,0 +1,205 @@ +'use strict' + +const t = require('tap') +const test = t.test +const joi = require('joi') +const Fastify = require('..') + +test('does not mutate joi schemas', t => { + t.plan(4) + + const fastify = Fastify() + function validatorCompiler ({ schema, method, url, httpPart }) { + // Needed to extract the params part, + // without the JSON-schema encapsulation + // that is automatically added by the short + // form of params. + schema = joi.object(schema.properties) + + return validateHttpData + + function validateHttpData (data) { + return schema.validate(data) + } + } + + fastify.setValidatorCompiler(validatorCompiler) + + fastify.route({ + path: '/foo/:an_id', + method: 'GET', + schema: { + params: { an_id: joi.number() } + }, + handler (req, res) { + t.same(req.params, { an_id: 42 }) + res.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/foo/42' + }, (err, result) => { + t.error(err) + t.equal(result.statusCode, 200) + t.same(JSON.parse(result.payload), { hello: 'world' }) + }) +}) + +test('multiple routes with one schema', t => { + t.plan(2) + + const fastify = Fastify() + + const schema = { + query: { + id: { type: 'number' } + } + } + + fastify.route({ + schema, + method: 'GET', + path: '/first/:id', + handler (req, res) { + res.send({ hello: 'world' }) + } + }) + + fastify.route({ + schema, + method: 'GET', + path: '/second/:id', + handler (req, res) { + res.send({ hello: 'world' }) + } + }) + + fastify.ready(error => { + t.error(error) + t.same(schema, schema) + }) +}) + +test('route error handler overrides default error handler', t => { + t.plan(4) + + const fastify = Fastify() + + const customRouteErrorHandler = (error, request, reply) => { + t.equal(error.message, 'Wrong Pot Error') + + reply.code(418).send({ + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + res.send(new Error('Wrong Pot Error')) + }, + errorHandler: customRouteErrorHandler + }) + + fastify.inject({ + method: 'GET', + url: '/coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 418) + t.same(JSON.parse(res.payload), { + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + }) +}) + +test('route error handler does not affect other routes', t => { + t.plan(3) + + const fastify = Fastify() + + const customRouteErrorHandler = (error, request, reply) => { + t.equal(error.message, 'Wrong Pot Error') + + reply.code(418).send({ + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + res.send(new Error('Wrong Pot Error')) + }, + errorHandler: customRouteErrorHandler + }) + + fastify.route({ + method: 'GET', + path: '/tea', + handler: (req, res) => { + res.send(new Error('No tea today')) + } + }) + + fastify.inject({ + method: 'GET', + url: '/tea' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 500) + t.same(JSON.parse(res.payload), { + message: 'No tea today', + statusCode: 500, + error: 'Internal Server Error' + }) + }) +}) + +test('async error handler for a route', t => { + t.plan(4) + + const fastify = Fastify() + + const customRouteErrorHandler = async (error, request, reply) => { + t.equal(error.message, 'Delayed Pot Error') + reply.code(418) + return { + message: 'Make a brew sometime later', + statusCode: 418, + error: 'Delayed Pot Error' + } + } + + fastify.route({ + method: 'GET', + path: '/late-coffee', + handler: (req, res) => { + res.send(new Error('Delayed Pot Error')) + }, + errorHandler: customRouteErrorHandler + }) + + fastify.inject({ + method: 'GET', + url: '/late-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 418) + t.same(JSON.parse(res.payload), { + message: 'Make a brew sometime later', + statusCode: 418, + error: 'Delayed Pot Error' + }) + }) +}) diff --git a/test/route.4.test.js b/test/route.4.test.js new file mode 100644 index 00000000000..882cca09981 --- /dev/null +++ b/test/route.4.test.js @@ -0,0 +1,131 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') + +test('route error handler overrides global custom error handler', t => { + t.plan(4) + + const fastify = Fastify() + + const customGlobalErrorHandler = (error, request, reply) => { + t.error(error) + reply.code(429).send({ message: 'Too much coffee' }) + } + + const customRouteErrorHandler = (error, request, reply) => { + t.equal(error.message, 'Wrong Pot Error') + reply.code(418).send({ + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + } + + fastify.setErrorHandler(customGlobalErrorHandler) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, res) => { + res.send(new Error('Wrong Pot Error')) + }, + errorHandler: customRouteErrorHandler + }) + + fastify.inject({ + method: 'GET', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 418) + t.same(JSON.parse(res.payload), { + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + }) +}) + +test('throws when route with empty url', async t => { + t.plan(1) + + const fastify = Fastify() + try { + fastify.route({ + method: 'GET', + url: '', + handler: (req, res) => { + res.send('hi!') + } + }) + } catch (err) { + t.equal(err.message, 'The path could not be empty') + } +}) + +test('throws when route with empty url in shorthand declaration', async t => { + t.plan(1) + + const fastify = Fastify() + try { + fastify.get( + '', + async function handler () { return {} } + ) + } catch (err) { + t.equal(err.message, 'The path could not be empty') + } +}) + +test('throws when route-level error handler is not a function', t => { + t.plan(1) + + const fastify = Fastify() + + try { + fastify.route({ + method: 'GET', + url: '/tea', + handler: (req, res) => { + res.send('hi!') + }, + errorHandler: 'teapot' + }) + } catch (err) { + t.equal(err.message, 'Error Handler for GET:/tea route, if defined, must be a function') + } +}) + +test('route child logger factory overrides default child logger factory', t => { + t.plan(3) + + const fastify = Fastify() + + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.equal(message, 'custom') + } + return child + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + + fastify.inject({ + method: 'GET', + url: '/coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) +}) diff --git a/test/route.5.test.js b/test/route.5.test.js new file mode 100644 index 00000000000..a2132081647 --- /dev/null +++ b/test/route.5.test.js @@ -0,0 +1,230 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') + +test('route child logger factory does not affect other routes', t => { + t.plan(6) + + const fastify = Fastify() + + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.equal(message, 'custom') + } + return child + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + + fastify.route({ + method: 'GET', + path: '/tea', + handler: (req, res) => { + t.notMatch(req.log.customLog instanceof Function) + res.send() + } + }) + + fastify.inject({ + method: 'GET', + url: '/coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) + fastify.inject({ + method: 'GET', + url: '/tea' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) +}) +test('route child logger factory overrides global custom error handler', t => { + t.plan(6) + + const fastify = Fastify() + + const customGlobalChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.globalLog = function (message) { + t.equal(message, 'global') + } + return child + } + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.equal(message, 'custom') + } + return child + } + + fastify.setChildLoggerFactory(customGlobalChildLogger) + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, res) => { + req.log.globalLog('global') + res.send() + } + }) + + fastify.inject({ + method: 'GET', + url: '/coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) + fastify.inject({ + method: 'GET', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) +}) + +test('Creates a HEAD route for each GET one (default)', t => { + t.plan(8) + + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + fastify.route({ + method: 'GET', + path: '/some-light', + handler: (req, reply) => { + reply.send('Get some light!') + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/json; charset=utf-8') + t.same(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/some-light' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') + t.equal(res.body, '') + }) +}) + +test('Do not create a HEAD route for each GET one (exposeHeadRoutes: false)', t => { + t.plan(4) + + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + fastify.route({ + method: 'GET', + path: '/some-light', + handler: (req, reply) => { + reply.send('Get some light!') + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 404) + }) + + fastify.inject({ + method: 'HEAD', + url: '/some-light' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 404) + }) +}) + +test('Creates a HEAD route for each GET one', t => { + t.plan(8) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + fastify.route({ + method: 'GET', + path: '/some-light', + handler: (req, reply) => { + reply.send('Get some light!') + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/json; charset=utf-8') + t.same(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/some-light' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') + t.equal(res.body, '') + }) +}) diff --git a/test/route.6.test.js b/test/route.6.test.js new file mode 100644 index 00000000000..60fe5d74959 --- /dev/null +++ b/test/route.6.test.js @@ -0,0 +1,306 @@ +'use strict' + +const stream = require('node:stream') +const t = require('tap') +const test = t.test +const Fastify = require('..') + +test('Creates a HEAD route for a GET one with prefixTrailingSlash', async (t) => { + t.plan(1) + + const fastify = Fastify() + + const arr = [] + fastify.register((instance, opts, next) => { + instance.addHook('onRoute', (routeOptions) => { + arr.push(`${routeOptions.method} ${routeOptions.url}`) + }) + + instance.route({ + method: 'GET', + path: '/', + exposeHeadRoute: true, + prefixTrailingSlash: 'both', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + next() + }, { prefix: '/v1' }) + + await fastify.ready() + + t.ok(true) +}) + +test('Will not create a HEAD route that is not GET', t => { + t.plan(11) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + fastify.route({ + method: 'GET', + path: '/some-light', + handler: (req, reply) => { + reply.send() + } + }) + + fastify.route({ + method: 'POST', + path: '/something', + handler: (req, reply) => { + reply.send({ look: 'It is something!' }) + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/json; charset=utf-8') + t.same(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/some-light' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], undefined) + t.equal(res.headers['content-length'], '0') + t.equal(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/something' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 404) + }) +}) + +test('HEAD route should handle properly each response type', t => { + t.plan(25) + + const fastify = Fastify({ exposeHeadRoutes: true }) + const resString = 'Found me!' + const resJSON = { here: 'is Johnny' } + const resBuffer = Buffer.from('I am a buffer!') + const resStream = stream.Readable.from('I am a stream!') + + fastify.route({ + method: 'GET', + path: '/json', + handler: (req, reply) => { + reply.send(resJSON) + } + }) + + fastify.route({ + method: 'GET', + path: '/string', + handler: (req, reply) => { + reply.send(resString) + } + }) + + fastify.route({ + method: 'GET', + path: '/buffer', + handler: (req, reply) => { + reply.send(resBuffer) + } + }) + + fastify.route({ + method: 'GET', + path: '/buffer-with-content-type', + handler: (req, reply) => { + reply.headers({ 'content-type': 'image/jpeg' }) + reply.send(resBuffer) + } + }) + + fastify.route({ + method: 'GET', + path: '/stream', + handler: (req, reply) => { + return resStream + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/json' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/json; charset=utf-8') + t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJSON))}`) + t.same(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/string' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') + t.equal(res.headers['content-length'], `${Buffer.byteLength(resString)}`) + t.equal(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/buffer' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/octet-stream') + t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) + t.equal(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/buffer-with-content-type' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'image/jpeg') + t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) + t.equal(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/stream' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], undefined) + t.equal(res.headers['content-length'], undefined) + t.equal(res.body, '') + }) +}) + +test('HEAD route should respect custom onSend handlers', t => { + t.plan(6) + + let counter = 0 + const resBuffer = Buffer.from('I am a coffee!') + const fastify = Fastify({ exposeHeadRoutes: true }) + const customOnSend = (res, reply, payload, done) => { + counter = counter + 1 + done(null, payload) + } + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send(resBuffer) + }, + onSend: [customOnSend, customOnSend] + }) + + fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/octet-stream') + t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) + t.equal(res.body, '') + t.equal(counter, 2) + }) +}) + +test('route onSend can be function or array of functions', t => { + t.plan(12) + const counters = { single: 0, multiple: 0 } + + const resBuffer = Buffer.from('I am a coffee!') + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: () => resBuffer, + onSend: (res, reply, payload, done) => { + counters.single += 1 + done(null, payload) + } + }) + + const customOnSend = (res, reply, payload, done) => { + counters.multiple += 1 + done(null, payload) + } + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: () => resBuffer, + onSend: [customOnSend, customOnSend] + }) + + fastify.inject({ method: 'HEAD', url: '/coffee' }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/octet-stream') + t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) + t.equal(res.body, '') + t.equal(counters.single, 1) + }) + + fastify.inject({ method: 'HEAD', url: '/more-coffee' }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/octet-stream') + t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) + t.equal(res.body, '') + t.equal(counters.multiple, 2) + }) +}) + +test('no warning for exposeHeadRoute', async t => { + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/more-coffee', + exposeHeadRoute: true, + async handler () { + return 'hello world' + } + }) + + const listener = (w) => { + t.fail('no warning') + } + + process.on('warning', listener) + + await fastify.listen({ port: 0 }) + + process.removeListener('warning', listener) + + await fastify.close() +}) diff --git a/test/route.7.test.js b/test/route.7.test.js new file mode 100644 index 00000000000..aeacecf2a68 --- /dev/null +++ b/test/route.7.test.js @@ -0,0 +1,370 @@ +'use strict' + +const stream = require('node:stream') +const split = require('split2') +const t = require('tap') +const test = t.test +const Fastify = require('..') +const proxyquire = require('proxyquire') + +test("HEAD route should handle stream.on('error')", t => { + t.plan(6) + + const resStream = stream.Readable.from('Hello with error!') + const logStream = split(JSON.parse) + const expectedError = new Error('Hello!') + const fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + exposeHeadRoute: true, + handler: (req, reply) => { + process.nextTick(() => resStream.emit('error', expectedError)) + return resStream + } + }) + + logStream.once('data', line => { + const { message, stack } = expectedError + t.same(line.err, { type: 'Error', message, stack }) + t.equal(line.msg, 'Error on Stream found for HEAD route') + t.equal(line.level, 50) + }) + + fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], undefined) + }) +}) + +test('HEAD route should be exposed by default', t => { + t.plan(7) + + const resStream = stream.Readable.from('Hello with error!') + const resJson = { hello: 'world' } + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/without-flag', + handler: (req, reply) => { + return resStream + } + }) + + fastify.route({ + exposeHeadRoute: true, + method: 'GET', + path: '/with-flag', + handler: (req, reply) => { + return resJson + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/without-flag' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + }) + + fastify.inject({ + method: 'HEAD', + url: '/with-flag' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/json; charset=utf-8') + t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJson))}`) + t.equal(res.body, '') + }) +}) + +test('HEAD route should be exposed if route exposeHeadRoute is set', t => { + t.plan(7) + + const resBuffer = Buffer.from('I am a coffee!') + const resJson = { hello: 'world' } + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.route({ + exposeHeadRoute: true, + method: 'GET', + path: '/one', + handler: (req, reply) => { + return resBuffer + } + }) + + fastify.route({ + method: 'GET', + path: '/two', + handler: (req, reply) => { + return resJson + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/one' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/octet-stream') + t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) + t.equal(res.body, '') + }) + + fastify.inject({ + method: 'HEAD', + url: '/two' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 404) + }) +}) + +test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (global)', t => { + t.plan(6) + + const resBuffer = Buffer.from('I am a coffee!') + const fastify = Fastify({ + exposeHeadRoutes: true + }) + + fastify.route({ + method: 'HEAD', + path: '/one', + handler: (req, reply) => { + reply.header('content-type', 'application/pdf') + reply.header('content-length', `${resBuffer.byteLength}`) + reply.header('x-custom-header', 'some-custom-header') + reply.send() + } + }) + + fastify.route({ + method: 'GET', + path: '/one', + handler: (req, reply) => { + return resBuffer + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/one' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/pdf') + t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) + t.equal(res.headers['x-custom-header'], 'some-custom-header') + t.equal(res.body, '') + }) +}) + +test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', t => { + t.plan(7) + + function onWarning (code) { + t.equal(code, 'FSTDEP007') + } + const warning = { + emit: onWarning + } + + const route = proxyquire('../lib/route', { './warnings': warning }) + const fastify = proxyquire('..', { './lib/route.js': route })() + + const resBuffer = Buffer.from('I am a coffee!') + + fastify.route({ + method: 'HEAD', + path: '/one', + handler: (req, reply) => { + reply.header('content-type', 'application/pdf') + reply.header('content-length', `${resBuffer.byteLength}`) + reply.header('x-custom-header', 'some-custom-header') + reply.send() + } + }) + + fastify.route({ + method: 'GET', + exposeHeadRoute: true, + path: '/one', + handler: (req, reply) => { + return resBuffer + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/one' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'application/pdf') + t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) + t.equal(res.headers['x-custom-header'], 'some-custom-header') + t.equal(res.body, '') + }) +}) + +test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'no-slash\'', t => { + t.plan(2) + + const fastify = Fastify() + + fastify.register(function routes (f, opts, next) { + f.route({ + method: 'GET', + url: '/', + exposeHeadRoute: true, + prefixTrailingSlash: 'no-slash', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + next() + }, { prefix: '/prefix' }) + + fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 404) + }) +}) + +test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'both\'', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.register(function routes (f, opts, next) { + f.route({ + method: 'GET', + url: '/', + exposeHeadRoute: true, + prefixTrailingSlash: 'both', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + next() + }, { prefix: '/prefix' }) + + const doublePrefixReply = await fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }) + const trailingSlashReply = await fastify.inject({ url: '/prefix/', method: 'HEAD' }) + const noneTrailingReply = await fastify.inject({ url: '/prefix', method: 'HEAD' }) + + t.equal(doublePrefixReply.statusCode, 404) + t.equal(trailingSlashReply.statusCode, 200) + t.equal(noneTrailingReply.statusCode, 200) +}) + +test('GET route with body schema should throw', t => { + t.plan(1) + + const fastify = Fastify() + + t.throws(() => { + fastify.route({ + method: 'GET', + path: '/get', + schema: { + body: {} + }, + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }, new Error('Body validation schema for GET:/get route is not supported!')) +}) + +test('HEAD route with body schema should throw', t => { + t.plan(1) + + const fastify = Fastify() + + t.throws(() => { + fastify.route({ + method: 'HEAD', + path: '/shouldThrow', + schema: { + body: {} + }, + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }, new Error('Body validation schema for HEAD:/shouldThrow route is not supported!')) +}) + +test('[HEAD, GET] route with body schema should throw', t => { + t.plan(1) + + const fastify = Fastify() + + t.throws(() => { + fastify.route({ + method: ['HEAD', 'GET'], + path: '/shouldThrowHead', + schema: { + body: {} + }, + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }, new Error('Body validation schema for HEAD:/shouldThrowHead route is not supported!')) +}) + +test('GET route with body schema should throw - shorthand', t => { + t.plan(1) + + const fastify = Fastify() + + t.throws(() => { + fastify.get('/shouldThrow', { + schema: { + body: {} + } + }, + function (req, reply) { + reply.send({ hello: 'world' }) + } + ) + }, new Error('Body validation schema for GET:/shouldThrow route is not supported!')) +}) + +test('HEAD route with body schema should throw - shorthand', t => { + t.plan(1) + + const fastify = Fastify() + + t.throws(() => { + fastify.head('/shouldThrow2', { + schema: { + body: {} + } + }, + function (req, reply) { + reply.send({ hello: 'world' }) + } + ) + }, new Error('Body validation schema for HEAD:/shouldThrow2 route is not supported!')) +}) diff --git a/test/route.8.test.js b/test/route.8.test.js new file mode 100644 index 00000000000..4ae6fd1419a --- /dev/null +++ b/test/route.8.test.js @@ -0,0 +1,142 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../fastify') +const { FST_ERR_INVALID_URL } = require('../lib/errors') +const { getServerUrl } = require('./helper') + +test('Request and Reply share the route config', async t => { + t.plan(3) + + const fastify = Fastify() + + const config = { + this: 'is a string', + thisIs: function aFunction () {} + } + + fastify.route({ + method: 'GET', + url: '/', + config, + handler: (req, reply) => { + t.same(req.context, reply.context) + t.same(req.context.config, reply.context.config) + t.match(req.context.config, config, 'there are url and method additional properties') + + reply.send({ hello: 'world' }) + } + }) + + await fastify.inject('/') +}) + +test('Will not try to re-createprefixed HEAD route if it already exists and exposeHeadRoutes is true', async (t) => { + t.plan(1) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.register((scope, opts, next) => { + scope.route({ + method: 'HEAD', + path: '/route', + handler: (req, reply) => { + reply.header('content-type', 'text/plain') + reply.send('custom HEAD response') + } + }) + scope.route({ + method: 'GET', + path: '/route', + handler: (req, reply) => { + reply.send({ ok: true }) + } + }) + + next() + }, { prefix: '/prefix' }) + + await fastify.ready() + + t.ok(true) +}) + +test('route with non-english characters', t => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/föö', (request, reply) => { + reply.send('here /föö') + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: getServerUrl(fastify) + encodeURI('/föö') + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.toString(), 'here /föö') + }) + }) +}) + +test('invalid url attribute - non string URL', t => { + t.plan(1) + const fastify = Fastify() + + try { + fastify.get(/^\/(donations|skills|blogs)/, () => { }) + } catch (error) { + t.equal(error.code, FST_ERR_INVALID_URL().code) + } +}) + +test('exposeHeadRoute should not reuse the same route option', async t => { + t.plan(2) + + const fastify = Fastify() + + // we update the onRequest hook in onRoute hook + // if we reuse the same route option + // that means we will append another function inside the array + fastify.addHook('onRoute', function (routeOption) { + if (Array.isArray(routeOption.onRequest)) { + routeOption.onRequest.push(() => {}) + } else { + routeOption.onRequest = [() => {}] + } + }) + + fastify.addHook('onRoute', function (routeOption) { + t.equal(routeOption.onRequest.length, 1) + }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + async handler () { + return 'hello world' + } + }) +}) + +test('using fastify.all when a catchall is defined does not degrade performance', { timeout: 30000 }, async t => { + t.plan(1) + + const fastify = Fastify() + + fastify.get('/*', async (_, reply) => reply.json({ ok: true })) + + for (let i = 0; i < 100; i++) { + fastify.all(`/${i}`, async (_, reply) => reply.json({ ok: true })) + } + + t.pass() +}) diff --git a/test/route.test.js b/test/route.test.js deleted file mode 100644 index ca4b37f1e25..00000000000 --- a/test/route.test.js +++ /dev/null @@ -1,1762 +0,0 @@ -'use strict' - -const stream = require('node:stream') -const split = require('split2') -const t = require('tap') -const test = t.test -const sget = require('simple-get').concat -const joi = require('joi') -const Fastify = require('..') -const proxyquire = require('proxyquire') -const { - FST_ERR_INVALID_URL, - FST_ERR_INSTANCE_ALREADY_LISTENING, - FST_ERR_ROUTE_METHOD_INVALID -} = require('../lib/errors') - -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} - -test('route', t => { - t.plan(10) - const test = t.test - - test('route - get', t => { - t.plan(4) - - const fastify = Fastify() - t.doesNotThrow(() => - fastify.route({ - method: 'GET', - url: '/', - schema: { - response: { - '2xx': { - type: 'object', - properties: { - hello: { - type: 'string' - } - } - } - } - }, - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }) - ) - - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) - sget({ - method: 'GET', - url: getUrl(fastify) + '/' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) - }) - - test('missing schema - route', t => { - t.plan(4) - - const fastify = Fastify() - t.doesNotThrow(() => - fastify.route({ - method: 'GET', - url: '/missing', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }) - ) - - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) - sget({ - method: 'GET', - url: getUrl(fastify) + '/missing' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) - }) - - test('invalid handler attribute - route', t => { - t.plan(1) - - const fastify = Fastify() - t.throws(() => fastify.get('/', { handler: 'not a function' }, () => { })) - }) - - test('Add Multiple methods per route all uppercase', t => { - t.plan(7) - - const fastify = Fastify() - t.doesNotThrow(() => - fastify.route({ - method: ['GET', 'DELETE'], - url: '/multiple', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - })) - - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) - sget({ - method: 'GET', - url: getUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - - sget({ - method: 'DELETE', - url: getUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) - }) - - test('Add Multiple methods per route all lowercase', t => { - t.plan(7) - - const fastify = Fastify() - t.doesNotThrow(() => - fastify.route({ - method: ['get', 'delete'], - url: '/multiple', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - })) - - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) - sget({ - method: 'GET', - url: getUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - - sget({ - method: 'DELETE', - url: getUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) - }) - - test('Add Multiple methods per route mixed uppercase and lowercase', t => { - t.plan(7) - - const fastify = Fastify() - t.doesNotThrow(() => - fastify.route({ - method: ['GET', 'delete'], - url: '/multiple', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - })) - - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) - sget({ - method: 'GET', - url: getUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - - sget({ - method: 'DELETE', - url: getUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) - }) - - test('Add invalid Multiple methods per route', t => { - t.plan(1) - - const fastify = Fastify() - t.throws(() => - fastify.route({ - method: ['GET', 1], - url: '/invalid-method', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }), new FST_ERR_ROUTE_METHOD_INVALID()) - }) - - test('Add method', t => { - t.plan(1) - - const fastify = Fastify() - t.throws(() => - fastify.route({ - method: 1, - url: '/invalid-method', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }), new FST_ERR_ROUTE_METHOD_INVALID()) - }) - - test('Add additional multiple methods to existing route', t => { - t.plan(7) - - const fastify = Fastify() - t.doesNotThrow(() => { - fastify.get('/add-multiple', function (req, reply) { - reply.send({ hello: 'Bob!' }) - }) - fastify.route({ - method: ['PUT', 'DELETE'], - url: '/add-multiple', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }) - }) - - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) - sget({ - method: 'PUT', - url: getUrl(fastify) + '/add-multiple' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - - sget({ - method: 'DELETE', - url: getUrl(fastify) + '/add-multiple' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) - }) - - test('cannot add another route after binding', t => { - t.plan(1) - - const fastify = Fastify() - - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) - - t.throws(() => fastify.route({ - method: 'GET', - url: '/another-get-route', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }), new FST_ERR_INSTANCE_ALREADY_LISTENING('Cannot add route!')) - }) - }) -}) - -test('invalid schema - route', t => { - t.plan(3) - - const fastify = Fastify() - fastify.route({ - handler: () => { }, - method: 'GET', - url: '/invalid', - schema: { - querystring: { - id: 'string' - } - } - }) - fastify.after(err => { - t.notOk(err, 'the error is throw on preReady') - }) - fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.match(err.message, /Failed building the validation schema for GET: \/invalid/) - }) -}) - -test('same route definition object on multiple prefixes', async t => { - t.plan(2) - - const routeObject = { - handler: () => { }, - method: 'GET', - url: '/simple' - } - - const fastify = Fastify({ exposeHeadRoutes: false }) - - fastify.register(async function (f) { - f.addHook('onRoute', (routeOptions) => { - t.equal(routeOptions.url, '/v1/simple') - }) - f.route(routeObject) - }, { prefix: '/v1' }) - fastify.register(async function (f) { - f.addHook('onRoute', (routeOptions) => { - t.equal(routeOptions.url, '/v2/simple') - }) - f.route(routeObject) - }, { prefix: '/v2' }) - - await fastify.ready() -}) - -test('path can be specified in place of uri', t => { - t.plan(3) - const fastify = Fastify() - - fastify.route({ - method: 'GET', - path: '/path', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }) - - const reqOpts = { - method: 'GET', - url: '/path' - } - - fastify.inject(reqOpts, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) - }) -}) - -test('invalid bodyLimit option - route', t => { - t.plan(2) - const fastify = Fastify() - - try { - fastify.route({ - bodyLimit: false, - method: 'PUT', - handler: () => null - }) - t.fail('bodyLimit must be an integer') - } catch (err) { - t.equal(err.message, "'bodyLimit' option must be an integer > 0. Got 'false'") - } - - try { - fastify.post('/url', { bodyLimit: 10000.1 }, () => null) - t.fail('bodyLimit must be an integer') - } catch (err) { - t.equal(err.message, "'bodyLimit' option must be an integer > 0. Got '10000.1'") - } -}) - -test('handler function in options of shorthand route should works correctly', t => { - t.plan(3) - - const fastify = Fastify() - fastify.get('/foo', { - handler: (req, reply) => { - reply.send({ hello: 'world' }) - } - }) - - fastify.inject({ - method: 'GET', - url: '/foo' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) - }) -}) - -test('does not mutate joi schemas', t => { - t.plan(4) - - const fastify = Fastify() - function validatorCompiler ({ schema, method, url, httpPart }) { - // Needed to extract the params part, - // without the JSON-schema encapsulation - // that is automatically added by the short - // form of params. - schema = joi.object(schema.properties) - - return validateHttpData - - function validateHttpData (data) { - return schema.validate(data) - } - } - - fastify.setValidatorCompiler(validatorCompiler) - - fastify.route({ - path: '/foo/:an_id', - method: 'GET', - schema: { - params: { an_id: joi.number() } - }, - handler (req, res) { - t.same(req.params, { an_id: 42 }) - res.send({ hello: 'world' }) - } - }) - - fastify.inject({ - method: 'GET', - url: '/foo/42' - }, (err, result) => { - t.error(err) - t.equal(result.statusCode, 200) - t.same(JSON.parse(result.payload), { hello: 'world' }) - }) -}) - -test('multiple routes with one schema', t => { - t.plan(2) - - const fastify = Fastify() - - const schema = { - query: { - id: { type: 'number' } - } - } - - fastify.route({ - schema, - method: 'GET', - path: '/first/:id', - handler (req, res) { - res.send({ hello: 'world' }) - } - }) - - fastify.route({ - schema, - method: 'GET', - path: '/second/:id', - handler (req, res) { - res.send({ hello: 'world' }) - } - }) - - fastify.ready(error => { - t.error(error) - t.same(schema, schema) - }) -}) - -test('route error handler overrides default error handler', t => { - t.plan(4) - - const fastify = Fastify() - - const customRouteErrorHandler = (error, request, reply) => { - t.equal(error.message, 'Wrong Pot Error') - - reply.code(418).send({ - message: 'Make a brew', - statusCode: 418, - error: 'Wrong Pot Error' - }) - } - - fastify.route({ - method: 'GET', - path: '/coffee', - handler: (req, res) => { - res.send(new Error('Wrong Pot Error')) - }, - errorHandler: customRouteErrorHandler - }) - - fastify.inject({ - method: 'GET', - url: '/coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same(JSON.parse(res.payload), { - message: 'Make a brew', - statusCode: 418, - error: 'Wrong Pot Error' - }) - }) -}) - -test('route error handler does not affect other routes', t => { - t.plan(3) - - const fastify = Fastify() - - const customRouteErrorHandler = (error, request, reply) => { - t.equal(error.message, 'Wrong Pot Error') - - reply.code(418).send({ - message: 'Make a brew', - statusCode: 418, - error: 'Wrong Pot Error' - }) - } - - fastify.route({ - method: 'GET', - path: '/coffee', - handler: (req, res) => { - res.send(new Error('Wrong Pot Error')) - }, - errorHandler: customRouteErrorHandler - }) - - fastify.route({ - method: 'GET', - path: '/tea', - handler: (req, res) => { - res.send(new Error('No tea today')) - } - }) - - fastify.inject({ - method: 'GET', - url: '/tea' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 500) - t.same(JSON.parse(res.payload), { - message: 'No tea today', - statusCode: 500, - error: 'Internal Server Error' - }) - }) -}) - -test('async error handler for a route', t => { - t.plan(4) - - const fastify = Fastify() - - const customRouteErrorHandler = async (error, request, reply) => { - t.equal(error.message, 'Delayed Pot Error') - reply.code(418) - return { - message: 'Make a brew sometime later', - statusCode: 418, - error: 'Delayed Pot Error' - } - } - - fastify.route({ - method: 'GET', - path: '/late-coffee', - handler: (req, res) => { - res.send(new Error('Delayed Pot Error')) - }, - errorHandler: customRouteErrorHandler - }) - - fastify.inject({ - method: 'GET', - url: '/late-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same(JSON.parse(res.payload), { - message: 'Make a brew sometime later', - statusCode: 418, - error: 'Delayed Pot Error' - }) - }) -}) - -test('route error handler overrides global custom error handler', t => { - t.plan(4) - - const fastify = Fastify() - - const customGlobalErrorHandler = (error, request, reply) => { - t.error(error) - reply.code(429).send({ message: 'Too much coffee' }) - } - - const customRouteErrorHandler = (error, request, reply) => { - t.equal(error.message, 'Wrong Pot Error') - reply.code(418).send({ - message: 'Make a brew', - statusCode: 418, - error: 'Wrong Pot Error' - }) - } - - fastify.setErrorHandler(customGlobalErrorHandler) - - fastify.route({ - method: 'GET', - path: '/more-coffee', - handler: (req, res) => { - res.send(new Error('Wrong Pot Error')) - }, - errorHandler: customRouteErrorHandler - }) - - fastify.inject({ - method: 'GET', - url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same(JSON.parse(res.payload), { - message: 'Make a brew', - statusCode: 418, - error: 'Wrong Pot Error' - }) - }) -}) - -test('throws when route with empty url', async t => { - t.plan(1) - - const fastify = Fastify() - try { - fastify.route({ - method: 'GET', - url: '', - handler: (req, res) => { - res.send('hi!') - } - }) - } catch (err) { - t.equal(err.message, 'The path could not be empty') - } -}) - -test('throws when route with empty url in shorthand declaration', async t => { - t.plan(1) - - const fastify = Fastify() - try { - fastify.get( - '', - async function handler () { return {} } - ) - } catch (err) { - t.equal(err.message, 'The path could not be empty') - } -}) - -test('throws when route-level error handler is not a function', t => { - t.plan(1) - - const fastify = Fastify() - - try { - fastify.route({ - method: 'GET', - url: '/tea', - handler: (req, res) => { - res.send('hi!') - }, - errorHandler: 'teapot' - }) - } catch (err) { - t.equal(err.message, 'Error Handler for GET:/tea route, if defined, must be a function') - } -}) - -test('route child logger factory overrides default child logger factory', t => { - t.plan(3) - - const fastify = Fastify() - - const customRouteChildLogger = (logger, bindings, opts, req) => { - const child = logger.child(bindings, opts) - child.customLog = function (message) { - t.equal(message, 'custom') - } - return child - } - - fastify.route({ - method: 'GET', - path: '/coffee', - handler: (req, res) => { - req.log.customLog('custom') - res.send() - }, - childLoggerFactory: customRouteChildLogger - }) - - fastify.inject({ - method: 'GET', - url: '/coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - }) -}) - -test('route child logger factory does not affect other routes', t => { - t.plan(6) - - const fastify = Fastify() - - const customRouteChildLogger = (logger, bindings, opts, req) => { - const child = logger.child(bindings, opts) - child.customLog = function (message) { - t.equal(message, 'custom') - } - return child - } - - fastify.route({ - method: 'GET', - path: '/coffee', - handler: (req, res) => { - req.log.customLog('custom') - res.send() - }, - childLoggerFactory: customRouteChildLogger - }) - - fastify.route({ - method: 'GET', - path: '/tea', - handler: (req, res) => { - t.notMatch(req.log.customLog instanceof Function) - res.send() - } - }) - - fastify.inject({ - method: 'GET', - url: '/coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - }) - fastify.inject({ - method: 'GET', - url: '/tea' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - }) -}) -test('route child logger factory overrides global custom error handler', t => { - t.plan(6) - - const fastify = Fastify() - - const customGlobalChildLogger = (logger, bindings, opts, req) => { - const child = logger.child(bindings, opts) - child.globalLog = function (message) { - t.equal(message, 'global') - } - return child - } - const customRouteChildLogger = (logger, bindings, opts, req) => { - const child = logger.child(bindings, opts) - child.customLog = function (message) { - t.equal(message, 'custom') - } - return child - } - - fastify.setChildLoggerFactory(customGlobalChildLogger) - - fastify.route({ - method: 'GET', - path: '/coffee', - handler: (req, res) => { - req.log.customLog('custom') - res.send() - }, - childLoggerFactory: customRouteChildLogger - }) - fastify.route({ - method: 'GET', - path: '/more-coffee', - handler: (req, res) => { - req.log.globalLog('global') - res.send() - } - }) - - fastify.inject({ - method: 'GET', - url: '/coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - }) - fastify.inject({ - method: 'GET', - url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - }) -}) - -test('Creates a HEAD route for each GET one (default)', t => { - t.plan(8) - - const fastify = Fastify() - - fastify.route({ - method: 'GET', - path: '/more-coffee', - handler: (req, reply) => { - reply.send({ here: 'is coffee' }) - } - }) - - fastify.route({ - method: 'GET', - path: '/some-light', - handler: (req, reply) => { - reply.send('Get some light!') - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/some-light' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') - t.equal(res.body, '') - }) -}) - -test('Do not create a HEAD route for each GET one (exposeHeadRoutes: false)', t => { - t.plan(4) - - const fastify = Fastify({ exposeHeadRoutes: false }) - - fastify.route({ - method: 'GET', - path: '/more-coffee', - handler: (req, reply) => { - reply.send({ here: 'is coffee' }) - } - }) - - fastify.route({ - method: 'GET', - path: '/some-light', - handler: (req, reply) => { - reply.send('Get some light!') - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) - }) - - fastify.inject({ - method: 'HEAD', - url: '/some-light' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) - }) -}) - -test('Creates a HEAD route for each GET one', t => { - t.plan(8) - - const fastify = Fastify({ exposeHeadRoutes: true }) - - fastify.route({ - method: 'GET', - path: '/more-coffee', - handler: (req, reply) => { - reply.send({ here: 'is coffee' }) - } - }) - - fastify.route({ - method: 'GET', - path: '/some-light', - handler: (req, reply) => { - reply.send('Get some light!') - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/some-light' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') - t.equal(res.body, '') - }) -}) - -test('Creates a HEAD route for a GET one with prefixTrailingSlash', async (t) => { - t.plan(1) - - const fastify = Fastify() - - const arr = [] - fastify.register((instance, opts, next) => { - instance.addHook('onRoute', (routeOptions) => { - arr.push(`${routeOptions.method} ${routeOptions.url}`) - }) - - instance.route({ - method: 'GET', - path: '/', - exposeHeadRoute: true, - prefixTrailingSlash: 'both', - handler: (req, reply) => { - reply.send({ here: 'is coffee' }) - } - }) - - next() - }, { prefix: '/v1' }) - - await fastify.ready() - - t.ok(true) -}) - -test('Will not create a HEAD route that is not GET', t => { - t.plan(11) - - const fastify = Fastify({ exposeHeadRoutes: true }) - - fastify.route({ - method: 'GET', - path: '/more-coffee', - handler: (req, reply) => { - reply.send({ here: 'is coffee' }) - } - }) - - fastify.route({ - method: 'GET', - path: '/some-light', - handler: (req, reply) => { - reply.send() - } - }) - - fastify.route({ - method: 'POST', - path: '/something', - handler: (req, reply) => { - reply.send({ look: 'It is something!' }) - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/some-light' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '0') - t.equal(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/something' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) - }) -}) - -test('HEAD route should handle properly each response type', t => { - t.plan(25) - - const fastify = Fastify({ exposeHeadRoutes: true }) - const resString = 'Found me!' - const resJSON = { here: 'is Johnny' } - const resBuffer = Buffer.from('I am a buffer!') - const resStream = stream.Readable.from('I am a stream!') - - fastify.route({ - method: 'GET', - path: '/json', - handler: (req, reply) => { - reply.send(resJSON) - } - }) - - fastify.route({ - method: 'GET', - path: '/string', - handler: (req, reply) => { - reply.send(resString) - } - }) - - fastify.route({ - method: 'GET', - path: '/buffer', - handler: (req, reply) => { - reply.send(resBuffer) - } - }) - - fastify.route({ - method: 'GET', - path: '/buffer-with-content-type', - handler: (req, reply) => { - reply.headers({ 'content-type': 'image/jpeg' }) - reply.send(resBuffer) - } - }) - - fastify.route({ - method: 'GET', - path: '/stream', - handler: (req, reply) => { - return resStream - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/json' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJSON))}`) - t.same(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/string' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') - t.equal(res.headers['content-length'], `${Buffer.byteLength(resString)}`) - t.equal(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/buffer' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/buffer-with-content-type' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'image/jpeg') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/stream' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], undefined) - t.equal(res.body, '') - }) -}) - -test('HEAD route should respect custom onSend handlers', t => { - t.plan(6) - - let counter = 0 - const resBuffer = Buffer.from('I am a coffee!') - const fastify = Fastify({ exposeHeadRoutes: true }) - const customOnSend = (res, reply, payload, done) => { - counter = counter + 1 - done(null, payload) - } - - fastify.route({ - method: 'GET', - path: '/more-coffee', - handler: (req, reply) => { - reply.send(resBuffer) - }, - onSend: [customOnSend, customOnSend] - }) - - fastify.inject({ - method: 'HEAD', - url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - t.equal(counter, 2) - }) -}) - -test('route onSend can be function or array of functions', t => { - t.plan(12) - const counters = { single: 0, multiple: 0 } - - const resBuffer = Buffer.from('I am a coffee!') - const fastify = Fastify({ exposeHeadRoutes: true }) - - fastify.route({ - method: 'GET', - path: '/coffee', - handler: () => resBuffer, - onSend: (res, reply, payload, done) => { - counters.single += 1 - done(null, payload) - } - }) - - const customOnSend = (res, reply, payload, done) => { - counters.multiple += 1 - done(null, payload) - } - - fastify.route({ - method: 'GET', - path: '/more-coffee', - handler: () => resBuffer, - onSend: [customOnSend, customOnSend] - }) - - fastify.inject({ method: 'HEAD', url: '/coffee' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - t.equal(counters.single, 1) - }) - - fastify.inject({ method: 'HEAD', url: '/more-coffee' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - t.equal(counters.multiple, 2) - }) -}) - -test('no warning for exposeHeadRoute', async t => { - const fastify = Fastify() - - fastify.route({ - method: 'GET', - path: '/more-coffee', - exposeHeadRoute: true, - async handler () { - return 'hello world' - } - }) - - const listener = (w) => { - t.fail('no warning') - } - - process.on('warning', listener) - - await fastify.listen({ port: 0 }) - - process.removeListener('warning', listener) - - await fastify.close() -}) - -test("HEAD route should handle stream.on('error')", t => { - t.plan(6) - - const resStream = stream.Readable.from('Hello with error!') - const logStream = split(JSON.parse) - const expectedError = new Error('Hello!') - const fastify = Fastify({ - logger: { - stream: logStream, - level: 'error' - } - }) - - fastify.route({ - method: 'GET', - path: '/more-coffee', - exposeHeadRoute: true, - handler: (req, reply) => { - process.nextTick(() => resStream.emit('error', expectedError)) - return resStream - } - }) - - logStream.once('data', line => { - const { message, stack } = expectedError - t.same(line.err, { type: 'Error', message, stack }) - t.equal(line.msg, 'Error on Stream found for HEAD route') - t.equal(line.level, 50) - }) - - fastify.inject({ - method: 'HEAD', - url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], undefined) - }) -}) - -test('HEAD route should be exposed by default', t => { - t.plan(7) - - const resStream = stream.Readable.from('Hello with error!') - const resJson = { hello: 'world' } - const fastify = Fastify() - - fastify.route({ - method: 'GET', - path: '/without-flag', - handler: (req, reply) => { - return resStream - } - }) - - fastify.route({ - exposeHeadRoute: true, - method: 'GET', - path: '/with-flag', - handler: (req, reply) => { - return resJson - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/without-flag' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - }) - - fastify.inject({ - method: 'HEAD', - url: '/with-flag' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJson))}`) - t.equal(res.body, '') - }) -}) - -test('HEAD route should be exposed if route exposeHeadRoute is set', t => { - t.plan(7) - - const resBuffer = Buffer.from('I am a coffee!') - const resJson = { hello: 'world' } - const fastify = Fastify({ exposeHeadRoutes: false }) - - fastify.route({ - exposeHeadRoute: true, - method: 'GET', - path: '/one', - handler: (req, reply) => { - return resBuffer - } - }) - - fastify.route({ - method: 'GET', - path: '/two', - handler: (req, reply) => { - return resJson - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/one' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - }) - - fastify.inject({ - method: 'HEAD', - url: '/two' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) - }) -}) - -test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (global)', t => { - t.plan(6) - - const resBuffer = Buffer.from('I am a coffee!') - const fastify = Fastify({ - exposeHeadRoutes: true - }) - - fastify.route({ - method: 'HEAD', - path: '/one', - handler: (req, reply) => { - reply.header('content-type', 'application/pdf') - reply.header('content-length', `${resBuffer.byteLength}`) - reply.header('x-custom-header', 'some-custom-header') - reply.send() - } - }) - - fastify.route({ - method: 'GET', - path: '/one', - handler: (req, reply) => { - return resBuffer - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/one' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/pdf') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.headers['x-custom-header'], 'some-custom-header') - t.equal(res.body, '') - }) -}) - -test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', t => { - t.plan(7) - - function onWarning (code) { - t.equal(code, 'FSTDEP007') - } - const warning = { - emit: onWarning - } - - const route = proxyquire('../lib/route', { './warnings': warning }) - const fastify = proxyquire('..', { './lib/route.js': route })() - - const resBuffer = Buffer.from('I am a coffee!') - - fastify.route({ - method: 'HEAD', - path: '/one', - handler: (req, reply) => { - reply.header('content-type', 'application/pdf') - reply.header('content-length', `${resBuffer.byteLength}`) - reply.header('x-custom-header', 'some-custom-header') - reply.send() - } - }) - - fastify.route({ - method: 'GET', - exposeHeadRoute: true, - path: '/one', - handler: (req, reply) => { - return resBuffer - } - }) - - fastify.inject({ - method: 'HEAD', - url: '/one' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/pdf') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.headers['x-custom-header'], 'some-custom-header') - t.equal(res.body, '') - }) -}) - -test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'no-slash\'', t => { - t.plan(2) - - const fastify = Fastify() - - fastify.register(function routes (f, opts, next) { - f.route({ - method: 'GET', - url: '/', - exposeHeadRoute: true, - prefixTrailingSlash: 'no-slash', - handler: (req, reply) => { - reply.send({ hello: 'world' }) - } - }) - - next() - }, { prefix: '/prefix' }) - - fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) -}) - -test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'both\'', async t => { - t.plan(3) - - const fastify = Fastify() - - fastify.register(function routes (f, opts, next) { - f.route({ - method: 'GET', - url: '/', - exposeHeadRoute: true, - prefixTrailingSlash: 'both', - handler: (req, reply) => { - reply.send({ hello: 'world' }) - } - }) - - next() - }, { prefix: '/prefix' }) - - const doublePrefixReply = await fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }) - const trailingSlashReply = await fastify.inject({ url: '/prefix/', method: 'HEAD' }) - const noneTrailingReply = await fastify.inject({ url: '/prefix', method: 'HEAD' }) - - t.equal(doublePrefixReply.statusCode, 404) - t.equal(trailingSlashReply.statusCode, 200) - t.equal(noneTrailingReply.statusCode, 200) -}) - -test('Request and Reply share the route config', async t => { - t.plan(3) - - const fastify = Fastify() - - const config = { - this: 'is a string', - thisIs: function aFunction () {} - } - - fastify.route({ - method: 'GET', - url: '/', - config, - handler: (req, reply) => { - t.same(req.context, reply.context) - t.same(req.context.config, reply.context.config) - t.match(req.context.config, config, 'there are url and method additional properties') - - reply.send({ hello: 'world' }) - } - }) - - await fastify.inject('/') -}) - -test('Will not try to re-createprefixed HEAD route if it already exists and exposeHeadRoutes is true', async (t) => { - t.plan(1) - - const fastify = Fastify({ exposeHeadRoutes: true }) - - fastify.register((scope, opts, next) => { - scope.route({ - method: 'HEAD', - path: '/route', - handler: (req, reply) => { - reply.header('content-type', 'text/plain') - reply.send('custom HEAD response') - } - }) - scope.route({ - method: 'GET', - path: '/route', - handler: (req, reply) => { - reply.send({ ok: true }) - } - }) - - next() - }, { prefix: '/prefix' }) - - await fastify.ready() - - t.ok(true) -}) - -test('GET route with body schema should throw', t => { - t.plan(1) - - const fastify = Fastify() - - t.throws(() => { - fastify.route({ - method: 'GET', - path: '/get', - schema: { - body: {} - }, - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }) - }, new Error('Body validation schema for GET:/get route is not supported!')) -}) - -test('HEAD route with body schema should throw', t => { - t.plan(1) - - const fastify = Fastify() - - t.throws(() => { - fastify.route({ - method: 'HEAD', - path: '/shouldThrow', - schema: { - body: {} - }, - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }) - }, new Error('Body validation schema for HEAD:/shouldThrow route is not supported!')) -}) - -test('[HEAD, GET] route with body schema should throw', t => { - t.plan(1) - - const fastify = Fastify() - - t.throws(() => { - fastify.route({ - method: ['HEAD', 'GET'], - path: '/shouldThrowHead', - schema: { - body: {} - }, - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }) - }, new Error('Body validation schema for HEAD:/shouldThrowHead route is not supported!')) -}) - -test('GET route with body schema should throw - shorthand', t => { - t.plan(1) - - const fastify = Fastify() - - t.throws(() => { - fastify.get('/shouldThrow', { - schema: { - body: {} - } - }, - function (req, reply) { - reply.send({ hello: 'world' }) - } - ) - }, new Error('Body validation schema for GET:/shouldThrow route is not supported!')) -}) - -test('HEAD route with body schema should throw - shorthand', t => { - t.plan(1) - - const fastify = Fastify() - - t.throws(() => { - fastify.head('/shouldThrow2', { - schema: { - body: {} - } - }, - function (req, reply) { - reply.send({ hello: 'world' }) - } - ) - }, new Error('Body validation schema for HEAD:/shouldThrow2 route is not supported!')) -}) - -test('route with non-english characters', t => { - t.plan(4) - - const fastify = Fastify() - - fastify.get('/föö', (request, reply) => { - reply.send('here /föö') - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget({ - method: 'GET', - url: getUrl(fastify) + encodeURI('/föö') - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'here /föö') - }) - }) -}) - -test('invalid url attribute - non string URL', t => { - t.plan(1) - const fastify = Fastify() - - try { - fastify.get(/^\/(donations|skills|blogs)/, () => { }) - } catch (error) { - t.equal(error.code, FST_ERR_INVALID_URL().code) - } -}) - -test('exposeHeadRoute should not reuse the same route option', async t => { - t.plan(2) - - const fastify = Fastify() - - // we update the onRequest hook in onRoute hook - // if we reuse the same route option - // that means we will append another function inside the array - fastify.addHook('onRoute', function (routeOption) { - if (Array.isArray(routeOption.onRequest)) { - routeOption.onRequest.push(() => {}) - } else { - routeOption.onRequest = [() => {}] - } - }) - - fastify.addHook('onRoute', function (routeOption) { - t.equal(routeOption.onRequest.length, 1) - }) - - fastify.route({ - method: 'GET', - path: '/more-coffee', - async handler () { - return 'hello world' - } - }) -}) - -test('using fastify.all when a catchall is defined does not degrade performance', { timeout: 30000 }, async t => { - t.plan(1) - - const fastify = Fastify() - - fastify.get('/*', async (_, reply) => reply.json({ ok: true })) - - for (let i = 0; i < 100; i++) { - fastify.all(`/${i}`, async (_, reply) => reply.json({ ok: true })) - } - - t.pass() -}) diff --git a/test/stream.1.test.js b/test/stream.1.test.js new file mode 100644 index 00000000000..95fedab6f5a --- /dev/null +++ b/test/stream.1.test.js @@ -0,0 +1,108 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fs = require('node:fs') +const Fastify = require('../fastify') + +test('should respond with a stream', t => { + t.plan(6) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + const stream = fs.createReadStream(__filename, 'utf8') + reply.code(200).send(stream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget(`http://localhost:${fastify.server.address().port}`, function (err, response, data) { + t.error(err) + t.equal(response.headers['content-type'], undefined) + t.equal(response.statusCode, 200) + + fs.readFile(__filename, (err, expected) => { + t.error(err) + t.equal(expected.toString(), data.toString()) + }) + }) + }) +}) + +test('should respond with a stream (error)', t => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/error', function (req, reply) { + const stream = fs.createReadStream('not-existing-file', 'utf8') + reply.code(200).send(stream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) { + t.error(err) + t.equal(response.statusCode, 500) + }) + }) +}) + +test('should trigger the onSend hook', t => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.send(fs.createReadStream(__filename, 'utf8')) + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.ok(payload._readableState) + reply.header('Content-Type', 'application/javascript') + done() + }) + + fastify.inject({ + url: '/' + }, (err, res) => { + t.error(err) + t.equal(res.headers['content-type'], 'application/javascript') + t.equal(res.payload, fs.readFileSync(__filename, 'utf8')) + fastify.close() + }) +}) + +test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', t => { + t.plan(5) + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.send(fs.createReadStream('not-existing-file', 'utf8')) + }) + + let counter = 0 + fastify.addHook('onSend', (req, reply, payload, done) => { + if (counter === 0) { + t.ok(payload._readableState) + } else if (counter === 1) { + const error = JSON.parse(payload) + t.equal(error.statusCode, 500) + } + counter++ + done() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget(`http://localhost:${fastify.server.address().port}`, function (err, response) { + t.error(err) + t.equal(response.statusCode, 500) + }) + }) +}) diff --git a/test/stream.2.test.js b/test/stream.2.test.js new file mode 100644 index 00000000000..99e73888cf5 --- /dev/null +++ b/test/stream.2.test.js @@ -0,0 +1,119 @@ +'use strict' + +const t = require('tap') +const test = t.test +const proxyquire = require('proxyquire') +const fs = require('node:fs') +const resolve = require('node:path').resolve +const zlib = require('node:zlib') +const pipeline = require('node:stream').pipeline +const Fastify = require('..') + +test('onSend hook stream', t => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + const gzStream = zlib.createGzip() + + reply.header('Content-Encoding', 'gzip') + pipeline( + fs.createReadStream(resolve(__filename), 'utf8'), + gzStream, + t.error + ) + done(null, gzStream) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.error(err) + t.equal(res.headers['content-encoding'], 'gzip') + const file = fs.readFileSync(resolve(__filename), 'utf8') + const payload = zlib.gunzipSync(res.rawPayload) + t.equal(payload.toString('utf-8'), file) + fastify.close() + }) +}) + +test('onSend hook stream should work even if payload is not a proper stream', t => { + t.plan(1) + + const reply = proxyquire('../lib/reply', { + 'node:stream': { + finished: (...args) => { + if (args.length === 2) { args[1](new Error('test-error')) } + } + } + }) + const Fastify = proxyquire('..', { + './lib/reply.js': reply + }) + const spyLogger = { + fatal: () => { }, + error: () => { }, + warn: (message) => { + t.equal(message, 'stream payload does not end properly') + fastify.close() + }, + info: () => { }, + debug: () => { }, + trace: () => { }, + child: () => { return spyLogger } + } + + const fastify = Fastify({ logger: spyLogger }) + fastify.get('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + fastify.addHook('onSend', (req, reply, payload, done) => { + const fakeStream = { pipe: () => { } } + done(null, fakeStream) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }) +}) + +test('onSend hook stream should work on payload with "close" ending function', t => { + t.plan(1) + + const reply = proxyquire('../lib/reply', { + 'node:stream': { + finished: (...args) => { + if (args.length === 2) { args[1](new Error('test-error')) } + } + } + }) + const Fastify = proxyquire('..', { + './lib/reply.js': reply + }) + + const fastify = Fastify({ logger: false }) + fastify.get('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + fastify.addHook('onSend', (req, reply, payload, done) => { + const fakeStream = { + pipe: () => { }, + close: (cb) => { + cb() + t.pass() + } + } + done(null, fakeStream) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }) +}) diff --git a/test/stream.3.test.js b/test/stream.3.test.js new file mode 100644 index 00000000000..44cb666263d --- /dev/null +++ b/test/stream.3.test.js @@ -0,0 +1,192 @@ +'use strict' + +const t = require('tap') +const test = t.test +const split = require('split2') +const Fastify = require('..') + +test('Destroying streams prematurely', t => { + t.plan(6) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'info' + } + }) + } catch (e) { + t.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + // Test that "premature close" errors are logged with level warn + logStream.on('data', line => { + if (line.res) { + t.equal(line.msg, 'stream closed prematurely') + t.equal(line.level, 30) + } + }) + + fastify.get('/', function (request, reply) { + t.pass('Received request') + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.equal(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.pass('Response closed') + }) + }) + }) +}) + +test('Destroying streams prematurely should call close method', t => { + t.plan(7) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'info' + } + }) + } catch (e) { + t.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + // Test that "premature close" errors are logged with level warn + logStream.on('data', line => { + if (line.res) { + t.equal(line.msg, 'stream closed prematurely') + t.equal(line.level, 30) + } + }) + + fastify.get('/', function (request, reply) { + t.pass('Received request') + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + reallyLongStream.destroy = undefined + reallyLongStream.close = () => t.ok('called') + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.equal(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.pass('Response closed') + }) + }) + }) +}) + +test('Destroying streams prematurely should call close method when destroy is not a function', t => { + t.plan(7) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'info' + } + }) + } catch (e) { + t.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + // Test that "premature close" errors are logged with level warn + logStream.on('data', line => { + if (line.res) { + t.equal(line.msg, 'stream closed prematurely') + t.equal(line.level, 30) + } + }) + + fastify.get('/', function (request, reply) { + t.pass('Received request') + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + reallyLongStream.destroy = true + reallyLongStream.close = () => t.ok('called') + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.equal(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.pass('Response closed') + }) + }) + }) +}) diff --git a/test/stream.4.test.js b/test/stream.4.test.js new file mode 100644 index 00000000000..53afff27d30 --- /dev/null +++ b/test/stream.4.test.js @@ -0,0 +1,223 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fs = require('node:fs') +const errors = require('http-errors') +const JSONStream = require('JSONStream') +const send = require('send') +const Readable = require('node:stream').Readable +const split = require('split2') +const semver = require('semver') +const Fastify = require('..') +const { kDisableRequestLogging } = require('../lib/symbols.js') +const { getServerUrl } = require('./helper') + +test('Destroying streams prematurely should call abort method', t => { + t.plan(7) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'info' + } + }) + } catch (e) { + t.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + // Test that "premature close" errors are logged with level warn + logStream.on('data', line => { + if (line.res) { + t.equal(line.msg, 'stream closed prematurely') + t.equal(line.level, 30) + } + }) + + fastify.get('/', function (request, reply) { + t.pass('Received request') + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + reallyLongStream.destroy = undefined + reallyLongStream.close = undefined + reallyLongStream.abort = () => t.ok('called') + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.equal(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.pass('Response closed') + }) + }) + }) +}) + +test('Destroying streams prematurely, log is disabled', t => { + t.plan(4) + + let fastify = null + try { + fastify = Fastify({ + logger: false + }) + } catch (e) { + t.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + fastify.get('/', function (request, reply) { + reply.log[kDisableRequestLogging] = true + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + reallyLongStream.destroy = true + reallyLongStream.close = () => t.ok('called') + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.equal(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.pass('Response closed') + }) + }) + }) +}) + +test('should respond with a stream1', t => { + t.plan(5) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + const stream = JSONStream.stringify() + reply.code(200).type('application/json').send(stream) + stream.write({ hello: 'world' }) + stream.end({ a: 42 }) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget(`http://localhost:${fastify.server.address().port}`, function (err, response, body) { + t.error(err) + t.equal(response.headers['content-type'], 'application/json') + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), [{ hello: 'world' }, { a: 42 }]) + }) + }) +}) + +test('return a 404 if the stream emits a 404 error', t => { + t.plan(5) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + t.pass('Received request') + + const reallyLongStream = new Readable({ + read: function () { + setImmediate(() => { + this.emit('error', new errors.NotFound()) + }) + } + }) + + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const port = fastify.server.address().port + + sget(`http://localhost:${port}`, function (err, response) { + t.error(err) + t.equal(response.headers['content-type'], 'application/json; charset=utf-8') + t.equal(response.statusCode, 404) + }) + }) +}) + +test('should support send module 200 and 404', { skip: semver.gte(process.versions.node, '17.0.0') }, t => { + t.plan(8) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + const stream = send(req.raw, __filename) + reply.code(200).send(stream) + }) + + fastify.get('/error', function (req, reply) { + const stream = send(req.raw, 'non-existing-file') + reply.code(200).send(stream) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const url = getServerUrl(fastify) + + sget(url, function (err, response, data) { + t.error(err) + t.equal(response.headers['content-type'], 'application/javascript; charset=UTF-8') + t.equal(response.statusCode, 200) + + fs.readFile(__filename, (err, expected) => { + t.error(err) + t.equal(expected.toString(), data.toString()) + }) + }) + + sget(url + '/error', function (err, response) { + t.error(err) + t.equal(response.statusCode, 404) + }) + }) +}) diff --git a/test/stream.5.test.js b/test/stream.5.test.js new file mode 100644 index 00000000000..5d08f2fe01e --- /dev/null +++ b/test/stream.5.test.js @@ -0,0 +1,194 @@ +'use strict' + +const t = require('tap') +const test = t.test +const proxyquire = require('proxyquire') +const fs = require('node:fs') +const Readable = require('node:stream').Readable +const sget = require('simple-get').concat +const Fastify = require('..') + +test('should destroy stream when response is ended', t => { + t.plan(4) + const stream = require('node:stream') + const fastify = Fastify() + + fastify.get('/error', function (req, reply) { + const reallyLongStream = new stream.Readable({ + read: function () { }, + destroy: function (err, callback) { + t.ok('called') + callback(err) + } + }) + reply.code(200).send(reallyLongStream) + reply.raw.end(Buffer.from('hello\n')) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('should mark reply as sent before pumping the payload stream into response for async route handler', t => { + t.plan(3) + + const handleRequest = proxyquire('../lib/handleRequest', { + './wrapThenable': (thenable, reply) => { + thenable.then(function (payload) { + t.equal(reply.sent, true) + }) + } + }) + + const route = proxyquire('../lib/route', { + './handleRequest': handleRequest + }) + + const Fastify = proxyquire('..', { + './lib/route': route + }) + + const fastify = Fastify() + + fastify.get('/', async function (req, reply) { + const stream = fs.createReadStream(__filename, 'utf8') + return reply.code(200).send(stream) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.error(err) + t.equal(res.payload, fs.readFileSync(__filename, 'utf8')) + fastify.close() + }) +}) + +test('reply.send handles aborted requests', t => { + t.plan(2) + + const spyLogger = { + level: 'error', + fatal: () => { }, + error: () => { + t.fail('should not log an error') + }, + warn: () => { }, + info: () => { }, + debug: () => { }, + trace: () => { }, + child: () => { return spyLogger } + } + const fastify = Fastify({ + logger: spyLogger + }) + + fastify.get('/', (req, reply) => { + setTimeout(() => { + const stream = new Readable({ + read: function () { + this.push(null) + } + }) + reply.send(stream) + }, 6) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const port = fastify.server.address().port + const http = require('node:http') + const req = http.get(`http://localhost:${port}`) + .on('error', (err) => { + t.equal(err.code, 'ECONNRESET') + fastify.close() + }) + + setTimeout(() => { + req.abort() + }, 1) + }) +}) + +test('request terminated should not crash fastify', t => { + t.plan(10) + + const spyLogger = { + level: 'error', + fatal: () => { }, + error: () => { + t.fail('should not log an error') + }, + warn: () => { }, + info: () => { }, + debug: () => { }, + trace: () => { }, + child: () => { return spyLogger } + } + const fastify = Fastify({ + logger: spyLogger + }) + + fastify.get('/', async (req, reply) => { + const stream = new Readable() + stream._read = () => { } + reply.header('content-type', 'text/html; charset=utf-8') + reply.header('transfer-encoding', 'chunked') + stream.push('

HTML

') + + reply.send(stream) + + await new Promise((resolve) => { setTimeout(resolve, 100).unref() }) + + stream.push('

should disply on second stream

') + stream.push(null) + return reply + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(() => { fastify.close() }) + + const port = fastify.server.address().port + const http = require('node:http') + const req = http.get(`http://localhost:${port}`, function (res) { + const { statusCode, headers } = res + t.equal(statusCode, 200) + t.equal(headers['content-type'], 'text/html; charset=utf-8') + t.equal(headers['transfer-encoding'], 'chunked') + res.on('data', function (chunk) { + t.equal(chunk.toString(), '

HTML

') + }) + + setTimeout(() => { + req.destroy() + + // the server is not crash, we can connect it + http.get(`http://localhost:${port}`, function (res) { + const { statusCode, headers } = res + t.equal(statusCode, 200) + t.equal(headers['content-type'], 'text/html; charset=utf-8') + t.equal(headers['transfer-encoding'], 'chunked') + let payload = '' + res.on('data', function (chunk) { + payload += chunk.toString() + }) + res.on('end', function () { + t.equal(payload, '

HTML

should disply on second stream

') + t.pass('should end properly') + }) + }) + }, 1) + }) + }) +}) diff --git a/test/stream.test.js b/test/stream.test.js deleted file mode 100644 index b5d34bc9ff1..00000000000 --- a/test/stream.test.js +++ /dev/null @@ -1,816 +0,0 @@ -'use strict' - -const t = require('tap') -const test = t.test -const proxyquire = require('proxyquire') -const sget = require('simple-get').concat -const fs = require('node:fs') -const resolve = require('node:path').resolve -const zlib = require('node:zlib') -const pipeline = require('node:stream').pipeline -const Fastify = require('..') -const errors = require('http-errors') -const JSONStream = require('JSONStream') -const send = require('send') -const Readable = require('node:stream').Readable -const split = require('split2') -const semver = require('semver') -const { kDisableRequestLogging } = require('../lib/symbols.js') - -function getUrl (app) { - const { address, port } = app.server.address() - if (address === '::1') { - return `http://[${address}]:${port}` - } else { - return `http://${address}:${port}` - } -} - -test('should respond with a stream', t => { - t.plan(6) - const fastify = Fastify() - - fastify.get('/', function (req, reply) { - const stream = fs.createReadStream(__filename, 'utf8') - reply.code(200).send(stream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget(`http://localhost:${fastify.server.address().port}`, function (err, response, data) { - t.error(err) - t.equal(response.headers['content-type'], undefined) - t.equal(response.statusCode, 200) - - fs.readFile(__filename, (err, expected) => { - t.error(err) - t.equal(expected.toString(), data.toString()) - }) - }) - }) -}) - -test('should respond with a stream (error)', t => { - t.plan(3) - const fastify = Fastify() - - fastify.get('/error', function (req, reply) { - const stream = fs.createReadStream('not-existing-file', 'utf8') - reply.code(200).send(stream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) { - t.error(err) - t.equal(response.statusCode, 500) - }) - }) -}) - -test('should trigger the onSend hook', t => { - t.plan(4) - const fastify = Fastify() - - fastify.get('/', (req, reply) => { - reply.send(fs.createReadStream(__filename, 'utf8')) - }) - - fastify.addHook('onSend', (req, reply, payload, done) => { - t.ok(payload._readableState) - reply.header('Content-Type', 'application/javascript') - done() - }) - - fastify.inject({ - url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/javascript') - t.equal(res.payload, fs.readFileSync(__filename, 'utf8')) - fastify.close() - }) -}) - -test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', t => { - t.plan(5) - const fastify = Fastify() - - fastify.get('/', (req, reply) => { - reply.send(fs.createReadStream('not-existing-file', 'utf8')) - }) - - let counter = 0 - fastify.addHook('onSend', (req, reply, payload, done) => { - if (counter === 0) { - t.ok(payload._readableState) - } else if (counter === 1) { - const error = JSON.parse(payload) - t.equal(error.statusCode, 500) - } - counter++ - done() - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget(`http://localhost:${fastify.server.address().port}`, function (err, response) { - t.error(err) - t.equal(response.statusCode, 500) - }) - }) -}) - -test('onSend hook stream', t => { - t.plan(4) - const fastify = Fastify() - - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - - fastify.addHook('onSend', (req, reply, payload, done) => { - const gzStream = zlib.createGzip() - - reply.header('Content-Encoding', 'gzip') - pipeline( - fs.createReadStream(resolve(process.cwd() + '/test/stream.test.js'), 'utf8'), - gzStream, - t.error - ) - done(null, gzStream) - }) - - fastify.inject({ - url: '/', - method: 'GET' - }, (err, res) => { - t.error(err) - t.equal(res.headers['content-encoding'], 'gzip') - const file = fs.readFileSync(resolve(process.cwd() + '/test/stream.test.js'), 'utf8') - const payload = zlib.gunzipSync(res.rawPayload) - t.equal(payload.toString('utf-8'), file) - fastify.close() - }) -}) - -test('onSend hook stream should work even if payload is not a proper stream', t => { - t.plan(1) - - const reply = proxyquire('../lib/reply', { - 'node:stream': { - finished: (...args) => { - if (args.length === 2) { args[1](new Error('test-error')) } - } - } - }) - const Fastify = proxyquire('..', { - './lib/reply.js': reply - }) - const spyLogger = { - fatal: () => { }, - error: () => { }, - warn: (message) => { - t.equal(message, 'stream payload does not end properly') - fastify.close() - }, - info: () => { }, - debug: () => { }, - trace: () => { }, - child: () => { return spyLogger } - } - - const fastify = Fastify({ logger: spyLogger }) - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - fastify.addHook('onSend', (req, reply, payload, done) => { - const fakeStream = { pipe: () => { } } - done(null, fakeStream) - }) - - fastify.inject({ - url: '/', - method: 'GET' - }) -}) - -test('onSend hook stream should work on payload with "close" ending function', t => { - t.plan(1) - - const reply = proxyquire('../lib/reply', { - 'node:stream': { - finished: (...args) => { - if (args.length === 2) { args[1](new Error('test-error')) } - } - } - }) - const Fastify = proxyquire('..', { - './lib/reply.js': reply - }) - - const fastify = Fastify({ logger: false }) - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - fastify.addHook('onSend', (req, reply, payload, done) => { - const fakeStream = { - pipe: () => { }, - close: (cb) => { - cb() - t.pass() - } - } - done(null, fakeStream) - }) - - fastify.inject({ - url: '/', - method: 'GET' - }) -}) - -test('Destroying streams prematurely', t => { - t.plan(6) - - let fastify = null - const logStream = split(JSON.parse) - try { - fastify = Fastify({ - logger: { - stream: logStream, - level: 'info' - } - }) - } catch (e) { - t.fail() - } - const stream = require('node:stream') - const http = require('node:http') - - // Test that "premature close" errors are logged with level warn - logStream.on('data', line => { - if (line.res) { - t.equal(line.msg, 'stream closed prematurely') - t.equal(line.level, 30) - } - }) - - fastify.get('/', function (request, reply) { - t.pass('Received request') - - let sent = false - const reallyLongStream = new stream.Readable({ - read: function () { - if (!sent) { - this.push(Buffer.from('hello\n')) - } - sent = true - } - }) - - reply.send(reallyLongStream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const port = fastify.server.address().port - - http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) - response.on('readable', function () { - response.destroy() - }) - - // Node bug? Node never emits 'close' here. - response.on('aborted', function () { - t.pass('Response closed') - }) - }) - }) -}) - -test('Destroying streams prematurely should call close method', t => { - t.plan(7) - - let fastify = null - const logStream = split(JSON.parse) - try { - fastify = Fastify({ - logger: { - stream: logStream, - level: 'info' - } - }) - } catch (e) { - t.fail() - } - const stream = require('node:stream') - const http = require('node:http') - - // Test that "premature close" errors are logged with level warn - logStream.on('data', line => { - if (line.res) { - t.equal(line.msg, 'stream closed prematurely') - t.equal(line.level, 30) - } - }) - - fastify.get('/', function (request, reply) { - t.pass('Received request') - - let sent = false - const reallyLongStream = new stream.Readable({ - read: function () { - if (!sent) { - this.push(Buffer.from('hello\n')) - } - sent = true - } - }) - reallyLongStream.destroy = undefined - reallyLongStream.close = () => t.ok('called') - reply.send(reallyLongStream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const port = fastify.server.address().port - - http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) - response.on('readable', function () { - response.destroy() - }) - // Node bug? Node never emits 'close' here. - response.on('aborted', function () { - t.pass('Response closed') - }) - }) - }) -}) - -test('Destroying streams prematurely should call close method when destroy is not a function', t => { - t.plan(7) - - let fastify = null - const logStream = split(JSON.parse) - try { - fastify = Fastify({ - logger: { - stream: logStream, - level: 'info' - } - }) - } catch (e) { - t.fail() - } - const stream = require('node:stream') - const http = require('node:http') - - // Test that "premature close" errors are logged with level warn - logStream.on('data', line => { - if (line.res) { - t.equal(line.msg, 'stream closed prematurely') - t.equal(line.level, 30) - } - }) - - fastify.get('/', function (request, reply) { - t.pass('Received request') - - let sent = false - const reallyLongStream = new stream.Readable({ - read: function () { - if (!sent) { - this.push(Buffer.from('hello\n')) - } - sent = true - } - }) - reallyLongStream.destroy = true - reallyLongStream.close = () => t.ok('called') - reply.send(reallyLongStream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const port = fastify.server.address().port - - http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) - response.on('readable', function () { - response.destroy() - }) - // Node bug? Node never emits 'close' here. - response.on('aborted', function () { - t.pass('Response closed') - }) - }) - }) -}) - -test('Destroying streams prematurely should call abort method', t => { - t.plan(7) - - let fastify = null - const logStream = split(JSON.parse) - try { - fastify = Fastify({ - logger: { - stream: logStream, - level: 'info' - } - }) - } catch (e) { - t.fail() - } - const stream = require('node:stream') - const http = require('node:http') - - // Test that "premature close" errors are logged with level warn - logStream.on('data', line => { - if (line.res) { - t.equal(line.msg, 'stream closed prematurely') - t.equal(line.level, 30) - } - }) - - fastify.get('/', function (request, reply) { - t.pass('Received request') - - let sent = false - const reallyLongStream = new stream.Readable({ - read: function () { - if (!sent) { - this.push(Buffer.from('hello\n')) - } - sent = true - } - }) - reallyLongStream.destroy = undefined - reallyLongStream.close = undefined - reallyLongStream.abort = () => t.ok('called') - reply.send(reallyLongStream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const port = fastify.server.address().port - - http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) - response.on('readable', function () { - response.destroy() - }) - // Node bug? Node never emits 'close' here. - response.on('aborted', function () { - t.pass('Response closed') - }) - }) - }) -}) - -test('Destroying streams prematurely, log is disabled', t => { - t.plan(4) - - let fastify = null - try { - fastify = Fastify({ - logger: false - }) - } catch (e) { - t.fail() - } - const stream = require('node:stream') - const http = require('node:http') - - fastify.get('/', function (request, reply) { - reply.log[kDisableRequestLogging] = true - - let sent = false - const reallyLongStream = new stream.Readable({ - read: function () { - if (!sent) { - this.push(Buffer.from('hello\n')) - } - sent = true - } - }) - reallyLongStream.destroy = true - reallyLongStream.close = () => t.ok('called') - reply.send(reallyLongStream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const port = fastify.server.address().port - - http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) - response.on('readable', function () { - response.destroy() - }) - // Node bug? Node never emits 'close' here. - response.on('aborted', function () { - t.pass('Response closed') - }) - }) - }) -}) - -test('should respond with a stream1', t => { - t.plan(5) - const fastify = Fastify() - - fastify.get('/', function (req, reply) { - const stream = JSONStream.stringify() - reply.code(200).type('application/json').send(stream) - stream.write({ hello: 'world' }) - stream.end({ a: 42 }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget(`http://localhost:${fastify.server.address().port}`, function (err, response, body) { - t.error(err) - t.equal(response.headers['content-type'], 'application/json') - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), [{ hello: 'world' }, { a: 42 }]) - }) - }) -}) - -test('return a 404 if the stream emits a 404 error', t => { - t.plan(5) - - const fastify = Fastify() - - fastify.get('/', function (request, reply) { - t.pass('Received request') - - const reallyLongStream = new Readable({ - read: function () { - setImmediate(() => { - this.emit('error', new errors.NotFound()) - }) - } - }) - - reply.send(reallyLongStream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const port = fastify.server.address().port - - sget(`http://localhost:${port}`, function (err, response) { - t.error(err) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.equal(response.statusCode, 404) - }) - }) -}) - -test('should support send module 200 and 404', { skip: semver.gte(process.versions.node, '17.0.0') }, t => { - t.plan(8) - const fastify = Fastify() - - fastify.get('/', function (req, reply) { - const stream = send(req.raw, __filename) - reply.code(200).send(stream) - }) - - fastify.get('/error', function (req, reply) { - const stream = send(req.raw, 'non-existing-file') - reply.code(200).send(stream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const url = getUrl(fastify) - - sget(url, function (err, response, data) { - t.error(err) - t.equal(response.headers['content-type'], 'application/javascript; charset=UTF-8') - t.equal(response.statusCode, 200) - - fs.readFile(__filename, (err, expected) => { - t.error(err) - t.equal(expected.toString(), data.toString()) - }) - }) - - sget(url + '/error', function (err, response) { - t.error(err) - t.equal(response.statusCode, 404) - }) - }) -}) - -test('should destroy stream when response is ended', t => { - t.plan(4) - const stream = require('node:stream') - const fastify = Fastify() - - fastify.get('/error', function (req, reply) { - const reallyLongStream = new stream.Readable({ - read: function () { }, - destroy: function (err, callback) { - t.ok('called') - callback(err) - } - }) - reply.code(200).send(reallyLongStream) - reply.raw.end(Buffer.from('hello\n')) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) { - t.error(err) - t.equal(response.statusCode, 200) - }) - }) -}) - -test('should mark reply as sent before pumping the payload stream into response for async route handler', t => { - t.plan(3) - - const handleRequest = proxyquire('../lib/handleRequest', { - './wrapThenable': (thenable, reply) => { - thenable.then(function (payload) { - t.equal(reply.sent, true) - }) - } - }) - - const route = proxyquire('../lib/route', { - './handleRequest': handleRequest - }) - - const Fastify = proxyquire('..', { - './lib/route': route - }) - - const fastify = Fastify() - - fastify.get('/', async function (req, reply) { - const stream = fs.createReadStream(__filename, 'utf8') - return reply.code(200).send(stream) - }) - - fastify.inject({ - url: '/', - method: 'GET' - }, (err, res) => { - t.error(err) - t.equal(res.payload, fs.readFileSync(__filename, 'utf8')) - fastify.close() - }) -}) - -test('reply.send handles aborted requests', t => { - t.plan(2) - - const spyLogger = { - level: 'error', - fatal: () => { }, - error: () => { - t.fail('should not log an error') - }, - warn: () => { }, - info: () => { }, - debug: () => { }, - trace: () => { }, - child: () => { return spyLogger } - } - const fastify = Fastify({ - logger: spyLogger - }) - - fastify.get('/', (req, reply) => { - setTimeout(() => { - const stream = new Readable({ - read: function () { - this.push(null) - } - }) - reply.send(stream) - }, 6) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const port = fastify.server.address().port - const http = require('node:http') - const req = http.get(`http://localhost:${port}`) - .on('error', (err) => { - t.equal(err.code, 'ECONNRESET') - fastify.close() - }) - - setTimeout(() => { - req.abort() - }, 1) - }) -}) - -test('request terminated should not crash fastify', t => { - t.plan(10) - - const spyLogger = { - level: 'error', - fatal: () => { }, - error: () => { - t.fail('should not log an error') - }, - warn: () => { }, - info: () => { }, - debug: () => { }, - trace: () => { }, - child: () => { return spyLogger } - } - const fastify = Fastify({ - logger: spyLogger - }) - - fastify.get('/', async (req, reply) => { - const stream = new Readable() - stream._read = () => { } - reply.header('content-type', 'text/html; charset=utf-8') - reply.header('transfer-encoding', 'chunked') - stream.push('

HTML

') - - reply.send(stream) - - await new Promise((resolve) => { setTimeout(resolve, 100).unref() }) - - stream.push('

should disply on second stream

') - stream.push(null) - return reply - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const port = fastify.server.address().port - const http = require('node:http') - const req = http.get(`http://localhost:${port}`, function (res) { - const { statusCode, headers } = res - t.equal(statusCode, 200) - t.equal(headers['content-type'], 'text/html; charset=utf-8') - t.equal(headers['transfer-encoding'], 'chunked') - res.on('data', function (chunk) { - t.equal(chunk.toString(), '

HTML

') - }) - - setTimeout(() => { - req.destroy() - - // the server is not crash, we can connect it - http.get(`http://localhost:${port}`, function (res) { - const { statusCode, headers } = res - t.equal(statusCode, 200) - t.equal(headers['content-type'], 'text/html; charset=utf-8') - t.equal(headers['transfer-encoding'], 'chunked') - let payload = '' - res.on('data', function (chunk) { - payload += chunk.toString() - }) - res.on('end', function () { - t.equal(payload, '

HTML

should disply on second stream

') - t.pass('should end properly') - }) - }) - }, 1) - }) - }) -}) diff --git a/test/trust-proxy.test.js b/test/trust-proxy.test.js index 64f9c5a59d9..707abba027e 100644 --- a/test/trust-proxy.test.js +++ b/test/trust-proxy.test.js @@ -4,7 +4,7 @@ const t = require('tap') const { test, before } = t const sget = require('simple-get').concat const fastify = require('..') -const dns = require('node:dns').promises +const helper = require('./helper') const sgetForwardedRequest = (app, forHeader, path, protoHeader) => { const headers = { @@ -40,10 +40,8 @@ const testRequestValues = (t, req, options) => { } let localhost - before(async function () { - const lookup = await dns.lookup('localhost') - localhost = lookup.address + [localhost] = await helper.getLoopbackHost() }) test('trust proxy, not add properties to node req', (t) => { diff --git a/test/upgrade.test.js b/test/upgrade.test.js index 74c5f794244..fa54d4e90ac 100644 --- a/test/upgrade.test.js +++ b/test/upgrade.test.js @@ -1,14 +1,14 @@ 'use strict' const { test, skip } = require('tap') -const { lookup } = require('node:dns').promises const Fastify = require('..') const { connect } = require('node:net') const { once } = require('node:events') +const dns = require('node:dns').promises async function setup () { - const results = await lookup('localhost', { all: true }) - if (results.length === 1) { + const localAddresses = await dns.lookup('localhost', { all: true }) + if (localAddresses.length === 1) { skip('requires both IPv4 and IPv6') return } From 91c7b448a741fe0013a139f3e69cbf52d6985fc2 Mon Sep 17 00:00:00 2001 From: Ivan Tymoshenko Date: Thu, 12 Oct 2023 21:04:25 +0200 Subject: [PATCH 0488/1295] fix: HEAD route reseting (#5090) --- lib/route.js | 14 +++- test/head.test.js | 204 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 4 deletions(-) diff --git a/lib/route.js b/lib/route.js index 7c097678a29..c1e36e6d992 100644 --- a/lib/route.js +++ b/lib/route.js @@ -182,8 +182,14 @@ function buildRouting (options) { const { exposeHeadRoute } = opts const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes + + const isGetRoute = opts.method === 'GET' || + (Array.isArray(opts.method) && opts.method.includes('GET')) + const isHeadRoute = opts.method === 'HEAD' || + (Array.isArray(opts.method) && opts.method.includes('HEAD')) + // we need to clone a set of initial options for HEAD route - const headOpts = shouldExposeHead && options.method === 'GET' ? { ...options } : null + const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null throwIfAlreadyStarted('Cannot add route!') @@ -326,8 +332,8 @@ function buildRouting (options) { const hasHEADHandler = headHandler !== null // remove the head route created by fastify - if (hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) { - router.off('HEAD', opts.url, { constraints }) + if (isHeadRoute && hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) { + router.off('HEAD', opts.url, constraints) } try { @@ -407,7 +413,7 @@ function buildRouting (options) { // register head route in sync // we must place it after the `this.after` - if (shouldExposeHead && options.method === 'GET' && !hasHEADHandler) { + if (shouldExposeHead && isGetRoute && !isHeadRoute && !hasHEADHandler) { const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend) prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true }) } else if (hasHEADHandler && exposeHeadRoute) { diff --git a/test/head.test.js b/test/head.test.js index 2787c0e9f52..3ef40e6ac7a 100644 --- a/test/head.test.js +++ b/test/head.test.js @@ -56,6 +56,134 @@ test('shorthand - head', t => { } }) +test('shorthand - custom head', t => { + t.plan(1) + try { + fastify.get('/proxy/*', function (req, reply) { + reply.code(200).send(null) + }) + + fastify.head('/proxy/*', function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + }) + + t.pass() + } catch (e) { + t.fail() + } +}) + +test('shorthand - custom head with constraints', t => { + t.plan(1) + try { + fastify.get('/proxy/*', { constraints: { version: '1.0.0' } }, function (req, reply) { + reply.code(200).send(null) + }) + + fastify.head('/proxy/*', { constraints: { version: '1.0.0' } }, function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + }) + + t.pass() + } catch (e) { + t.fail() + } +}) + +test('shorthand - should not reset a head route', t => { + t.plan(1) + try { + fastify.get('/query1', function (req, reply) { + reply.code(200).send(null) + }) + + fastify.put('/query1', function (req, reply) { + reply.code(200).send(null) + }) + + t.pass() + } catch (e) { + t.fail() + } +}) + +test('shorthand - should override head route when setting multiple routes', t => { + t.plan(1) + try { + fastify.route({ + method: 'GET', + url: '/query2', + handler: function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + } + }) + + fastify.route({ + method: ['POST', 'PUT', 'HEAD'], + url: '/query2', + handler: function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + } + }) + + t.pass() + } catch (e) { + console.log(e) + t.fail() + } +}) + +test('shorthand - should override head route when setting multiple routes', t => { + t.plan(1) + try { + fastify.route({ + method: ['GET'], + url: '/query3', + handler: function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + } + }) + + fastify.route({ + method: ['POST', 'PUT', 'HEAD'], + url: '/query3', + handler: function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + } + }) + + t.pass() + } catch (e) { + console.log(e) + t.fail() + } +}) + +test('shorthand - should set get and head route in the same api call', t => { + t.plan(1) + try { + fastify.route({ + method: ['HEAD', 'GET'], + url: '/query4', + handler: function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + } + }) + + t.pass() + } catch (e) { + console.log(e) + t.fail() + } +}) + test('shorthand - head params', t => { t.plan(1) try { @@ -76,6 +204,7 @@ test('shorthand - head, querystring schema', t => { }) t.pass() } catch (e) { + console.log(e) t.fail() } }) @@ -88,6 +217,7 @@ test('missing schema - head', t => { }) t.pass() } catch (e) { + console.log(e) t.fail() } }) @@ -161,4 +291,78 @@ fastify.listen({ port: 0 }, err => { t.equal(response.statusCode, 200) }) }) + + test('shorthand - request head custom head', t => { + t.plan(3) + sget({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/proxy/test' + }, (err, response) => { + t.error(err) + t.equal(response.headers['x-foo'], 'bar') + t.equal(response.statusCode, 200) + }) + }) + + test('shorthand - request head custom head with constraints', t => { + t.plan(3) + sget({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/proxy/test', + headers: { + version: '1.0.0' + } + }, (err, response) => { + t.error(err) + t.equal(response.headers['x-foo'], 'bar') + t.equal(response.statusCode, 200) + }) + }) + + test('shorthand - should not reset a head route', t => { + t.plan(2) + sget({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/query1' + }, (err, response) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) + + test('shorthand - should override head route when setting multiple routes', t => { + t.plan(3) + sget({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/query2' + }, (err, response) => { + t.error(err) + t.equal(response.headers['x-foo'], 'bar') + t.equal(response.statusCode, 200) + }) + }) + + test('shorthand - should override head route when setting multiple routes', t => { + t.plan(3) + sget({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/query3' + }, (err, response) => { + t.error(err) + t.equal(response.headers['x-foo'], 'bar') + t.equal(response.statusCode, 200) + }) + }) + + test('shorthand - should set get and head route in the same api call', t => { + t.plan(3) + sget({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/query4' + }, (err, response) => { + t.error(err) + t.equal(response.headers['x-foo'], 'bar') + t.equal(response.statusCode, 200) + }) + }) }) From a9eea1cdc94259da81eb7a828e2f6e6e260dc6f2 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 13 Oct 2023 09:45:40 +0200 Subject: [PATCH 0489/1295] bumped v4.24.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index b0ee2e8838c..eb711d873c7 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.24.0' +const VERSION = '4.24.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 8d49b507b1a..8d25526a912 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.24.0", + "version": "4.24.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 24fffff11d26ed8ae0ab5ef7406e2233cc7e87b9 Mon Sep 17 00:00:00 2001 From: Arthur Fiorette <47537704+arthurfiorette@users.noreply.github.com> Date: Sat, 14 Oct 2023 16:31:33 -0300 Subject: [PATCH 0490/1295] fix: build problems when `Symbol.asyncDispose` type is not available. (#5096) --- types/instance.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/instance.d.ts b/types/instance.d.ts index 98caa67170b..3100e756ad6 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -143,6 +143,8 @@ export interface FastifyInstance< close(closeListener: () => void): undefined; /** Alias for {@linkcode FastifyInstance.close()} */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - type only available for @types/node >=17 or typescript >= 5.2 [Symbol.asyncDispose](): Promise; // should be able to define something useful with the decorator getter/setter pattern using Generics to enforce the users function returns what they expect it to From 18088ba61b8b4950bf3dd5b1d99341c875c6f51d Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 15 Oct 2023 08:55:02 +0200 Subject: [PATCH 0491/1295] Bumped v4.24.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index eb711d873c7..178f4d2ef47 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.24.1' +const VERSION = '4.24.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 8d25526a912..1dde0427246 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.24.1", + "version": "4.24.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 68e5dd15374ca2b86aef50b1ebddd9fd2bc3b21f Mon Sep 17 00:00:00 2001 From: Simone Sanfratello Date: Mon, 16 Oct 2023 12:17:28 +0200 Subject: [PATCH 0492/1295] fix: timeout on citgm tests (#5101) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1dde0427246..bb0fb191d26 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "unit": "c8 tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap", - "citgm": "tap --jobs=1" + "citgm": "tap --jobs=1 --timeout=120" }, "repository": { "type": "git", From 36fc8c2f9955bbb94d2085282a6f328c98077280 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 18 Oct 2023 17:12:50 +0100 Subject: [PATCH 0493/1295] chore: add missing `use strict` directives (#5106) --- examples/use-plugin.js | 2 ++ integration/server.js | 2 ++ test/buffer.test.js | 2 ++ test/bundler/esbuild/src/fail-plugin-version.js | 2 ++ test/bundler/esbuild/src/index.js | 2 ++ test/bundler/webpack/src/fail-plugin-version.js | 2 ++ test/bundler/webpack/src/index.js | 2 ++ test/bundler/webpack/webpack.config.js | 2 ++ 8 files changed, 16 insertions(+) diff --git a/examples/use-plugin.js b/examples/use-plugin.js index a8bf33c9d3f..8eedefc4f4b 100644 --- a/examples/use-plugin.js +++ b/examples/use-plugin.js @@ -1,3 +1,5 @@ +'use strict' + const fastify = require('../fastify')({ logger: true }) const opts = { diff --git a/integration/server.js b/integration/server.js index 85a7716157a..39db37dfd79 100644 --- a/integration/server.js +++ b/integration/server.js @@ -1,3 +1,5 @@ +'use strict' + const Fastify = require('../fastify') const fastify = Fastify() diff --git a/test/buffer.test.js b/test/buffer.test.js index a82540fbeed..65ba62b2518 100644 --- a/test/buffer.test.js +++ b/test/buffer.test.js @@ -1,3 +1,5 @@ +'use strict' + const t = require('tap') const test = t.test const Fastify = require('..') diff --git a/test/bundler/esbuild/src/fail-plugin-version.js b/test/bundler/esbuild/src/fail-plugin-version.js index d78e216f1fc..f3f850dffcd 100644 --- a/test/bundler/esbuild/src/fail-plugin-version.js +++ b/test/bundler/esbuild/src/fail-plugin-version.js @@ -1,3 +1,5 @@ +'use strict' + const fp = require('fastify-plugin') const fastify = require('../../../../')() diff --git a/test/bundler/esbuild/src/index.js b/test/bundler/esbuild/src/index.js index c80b41bc108..818dfe106d4 100644 --- a/test/bundler/esbuild/src/index.js +++ b/test/bundler/esbuild/src/index.js @@ -1,3 +1,5 @@ +'use strict' + const fastify = require('../../../../')() // Declare a route fastify.get('/', function (request, reply) { diff --git a/test/bundler/webpack/src/fail-plugin-version.js b/test/bundler/webpack/src/fail-plugin-version.js index d78e216f1fc..f3f850dffcd 100644 --- a/test/bundler/webpack/src/fail-plugin-version.js +++ b/test/bundler/webpack/src/fail-plugin-version.js @@ -1,3 +1,5 @@ +'use strict' + const fp = require('fastify-plugin') const fastify = require('../../../../')() diff --git a/test/bundler/webpack/src/index.js b/test/bundler/webpack/src/index.js index c80b41bc108..818dfe106d4 100644 --- a/test/bundler/webpack/src/index.js +++ b/test/bundler/webpack/src/index.js @@ -1,3 +1,5 @@ +'use strict' + const fastify = require('../../../../')() // Declare a route fastify.get('/', function (request, reply) { diff --git a/test/bundler/webpack/webpack.config.js b/test/bundler/webpack/webpack.config.js index dee222c2422..e86edd3f489 100644 --- a/test/bundler/webpack/webpack.config.js +++ b/test/bundler/webpack/webpack.config.js @@ -1,3 +1,5 @@ +'use strict' + const path = require('node:path') module.exports = { From fc6bf5cb9f000ad4cda5e197f4fd22166a15dab9 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 19 Oct 2023 10:50:18 +0200 Subject: [PATCH 0494/1295] Bumped v4.24.3 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 178f4d2ef47..4efb4c28165 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.24.2' +const VERSION = '4.24.3' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index bb0fb191d26..8d015a61524 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.24.2", + "version": "4.24.3", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From dcac2ebc82b6496cf493e552658959d536971c52 Mon Sep 17 00:00:00 2001 From: Fortas Abdeldjalil Date: Mon, 23 Oct 2023 09:29:27 +0200 Subject: [PATCH 0495/1295] feat: Improve RouteShorthandOptions['constraints'] type (#5097) * Improve RouteShorthandOptions['constraints'] type * Use more explicit types for route constraints --- test/types/route.test-d.ts | 38 ++++++++++++++++++++++++++++++++++++++ types/route.d.ts | 7 ++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 5ae041599ae..f944eed5fcc 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -422,6 +422,44 @@ expectType(fastify().hasRoute({ constraints: { version: '1.2.0' } })) +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { host: 'auth.fastify.io' } +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { host: /.*\.fastify\.io/ } +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { host: /.*\.fastify\.io/, version: '1.2.3' } +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { + something: { + name: 'secret', + storage: function () { + return { + get: (type) => { + return null + }, + set: (type, store) => {} + } + }, + deriveConstraint: (req, ctx, done) => {}, + mustMatchWhenDerived: true + } + } +})) + expectType(fastify().route({ url: '/', method: 'get', diff --git a/types/route.d.ts b/types/route.d.ts index b1ad4562187..68ab5b7a425 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -12,6 +12,7 @@ import { ResolveFastifyReplyReturnType } from './type-provider' import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' +import { ConstraintStrategy } from 'find-my-way' export interface FastifyRouteConfig { url: string; @@ -20,6 +21,10 @@ export interface FastifyRouteConfig { export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface {} +export type RouteConstraintType = Omit, 'deriveConstraint'> & { + deriveConstraint(req: RawRequestDefaultExpression, ctx?: Context, done?: (err: Error, ...args: any) => any): any, +} + /** * Route shorthand options for the various shorthand methods */ @@ -43,7 +48,7 @@ export interface RouteShorthandOptions< logLevel?: LogLevel; config?: Omit['config'], 'url' | 'method'>; version?: string; - constraints?: { [name: string]: any }, + constraints?: { version: string } | {host: RegExp | string} | {[name: string]: RouteConstraintType }, prefixTrailingSlash?: 'slash'|'no-slash'|'both'; errorHandler?: ( this: FastifyInstance, From 9b8a7825dc033887d293549e40284284bf27c5a5 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 23 Oct 2023 15:56:33 +0200 Subject: [PATCH 0496/1295] docs: Add @eomm and @jsumners as lead maintainers (#5115) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d70a079eb18..8f35e87821c 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,10 @@ listed in alphabetical order. , * [__Tomas Della Vedova__](https://github.com/delvedor), , +* [__Manuel Spigolon__](https://github.com/eomm), + , +* [__James Sumners__](https://github.com/jsumners), + , ### Fastify Core team * [__Tommaso Allevi__](https://github.com/allevo), From bc5df037c51ee0e414654a7285342a16207293e0 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Sat, 28 Oct 2023 01:32:48 -0700 Subject: [PATCH 0497/1295] fix: reply.send supports Uint8Array payload (#5124) * test: false positive Uint8Array view length False positive test proving bug from #5118 * fix(reply.send): support Uint8Array payloads fixes https://github.com/fastify/fastify/issues/5118 * Update lib/reply.js Co-authored-by: Matteo Collina * chore: address PR comments --------- Co-authored-by: Matteo Collina --- lib/reply.js | 2 +- test/internals/reply.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/reply.js b/lib/reply.js index c0e6bbb5fff..b831bfca441 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -164,7 +164,7 @@ Reply.prototype.send = function (payload) { if (hasContentType === false) { this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET } - const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer) + const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength) onSendHook(this, payloadToSend) return this } diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 8ceb9ebc842..080bf6c27e0 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -2148,3 +2148,29 @@ test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', t.equal(err.code, 'ERR_HTTP_HEADERS_SENT') } }) + +test('Uint8Array view of ArrayBuffer returns correct byteLength', t => { + t.plan(5) + const fastify = Fastify() + + const arrBuf = new ArrayBuffer(100) + const arrView = new Uint8Array(arrBuf, 0, 10) + fastify.get('/', function (req, reply) { + return reply.send(arrView) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + t.teardown(fastify.close.bind(fastify)) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, response) => { + t.error(err) + t.equal(response.headers['content-type'], 'application/octet-stream') + t.equal(response.headers['content-length'], '10') + t.same(response.rawPayload.byteLength, arrView.byteLength) + }) + }) +}) From 500c9a19ab225f266d9b6b606c7b2e40255fade0 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Fri, 27 Oct 2023 15:42:05 -0400 Subject: [PATCH 0498/1295] Migrate deprecation warnings to actual deprecation warnings --- docs/Guides/Plugins-Guide.md | 4 ++-- lib/warnings.js | 30 +++++++++++++++--------------- package.json | 2 +- test/default-route.test.js | 4 ++-- test/internals/reply.test.js | 4 ++-- test/reply-trailers.test.js | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index 50b6fc4a6b3..6aa3ea71b6a 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -493,8 +493,8 @@ use case, you can use the ```js const warning = require('process-warning')() -warning.create('FastifyDeprecation', 'FST_ERROR_CODE', 'message') -warning.emit('FST_ERROR_CODE') +warning.create('MyPluginWarning', 'MP_ERROR_CODE', 'message') +warning.emit('MP_ERROR_CODE') ``` ## Let's start! diff --git a/lib/warnings.js b/lib/warnings.js index ec61b0f7da4..9e8b75c3f77 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -7,35 +7,35 @@ const warning = require('process-warning')() * - FSTDEP005 */ -warning.create('FastifyDeprecation', 'FSTDEP005', 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.') +warning.createDeprecation('FSTDEP005', 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.') -warning.create('FastifyDeprecation', 'FSTDEP006', 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s') +warning.createDeprecation('FSTDEP006', 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s') -warning.create('FastifyDeprecation', 'FSTDEP007', 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.') +warning.createDeprecation('FSTDEP007', 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.') -warning.create('FastifyDeprecation', 'FSTDEP008', 'You are using route constraints via the route { version: "..." } option, use { constraints: { version: "..." } } option instead.') +warning.createDeprecation('FSTDEP008', 'You are using route constraints via the route { version: "..." } option, use { constraints: { version: "..." } } option instead.') -warning.create('FastifyDeprecation', 'FSTDEP009', 'You are using a custom route versioning strategy via the server { versioning: "..." } option, use { constraints: { version: "..." } } option instead.') +warning.createDeprecation('FSTDEP009', 'You are using a custom route versioning strategy via the server { versioning: "..." } option, use { constraints: { version: "..." } } option instead.') -warning.create('FastifyDeprecation', 'FSTDEP010', 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.') +warning.createDeprecation('FSTDEP010', 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.') -warning.create('FastifyDeprecation', 'FSTDEP011', 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.') +warning.createDeprecation('FSTDEP011', 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.') -warning.create('FastifyDeprecation', 'FSTDEP012', 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.') +warning.createDeprecation('FSTDEP012', 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.') -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`.') +warning.createDeprecation('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`.') -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.') +warning.createDeprecation('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.') -warning.create('FastifyDeprecation', 'FSTDEP015', 'You are accessing the deprecated "request.routeSchema" property. Use "request.routeOptions.schema" instead. Property "req.routeSchema" will be removed in `fastify@5`.') +warning.createDeprecation('FSTDEP015', 'You are accessing the deprecated "request.routeSchema" property. Use "request.routeOptions.schema" instead. Property "req.routeSchema" will be removed in `fastify@5`.') -warning.create('FastifyDeprecation', 'FSTDEP016', 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.') +warning.createDeprecation('FSTDEP016', 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.') -warning.create('FastifyDeprecation', 'FSTDEP017', 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.url" instead. Property "req.routerPath" will be removed in `fastify@5`.') +warning.createDeprecation('FSTDEP017', 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.url" instead. Property "req.routerPath" will be removed in `fastify@5`.') -warning.create('FastifyDeprecation', 'FSTDEP018', 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.') +warning.createDeprecation('FSTDEP018', 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.') -warning.create('FastifyDeprecation', 'FSTDEP019', 'reply.context property access is deprecated. Please use "reply.routeOptions.config" or "reply.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.') +warning.createDeprecation('FSTDEP019', 'reply.context property access is deprecated. Please use "reply.routeOptions.config" or "reply.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.') warning.create('FastifyWarning', 'FSTWRN001', 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', { unlimited: true }) diff --git a/package.json b/package.json index 8d015a61524..3fd2e80cf76 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "find-my-way": "^7.7.0", "light-my-request": "^5.11.0", "pino": "^8.16.0", - "process-warning": "^2.2.0", + "process-warning": "^2.3.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", "secure-json-parse": "^2.7.0", diff --git a/test/default-route.test.js b/test/default-route.test.js index 4a8679e0701..fda35b940f2 100644 --- a/test/default-route.test.js +++ b/test/default-route.test.js @@ -18,7 +18,7 @@ test('setDefaultRoute should emit a deprecation warning', t => { process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.name, 'DeprecationWarning') t.equal(warning.code, 'FSTDEP014') } @@ -37,7 +37,7 @@ test('getDefaultRoute should emit a deprecation warning', t => { process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.name, 'DeprecationWarning') t.equal(warning.code, 'FSTDEP014') } diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 080bf6c27e0..72aff8aa3bb 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -1472,7 +1472,7 @@ test('should emit deprecation warning when trying to modify the reply.sent prope process.removeAllListeners('warning') process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.name, 'DeprecationWarning') t.equal(warning.code, deprecationCode) } @@ -1500,7 +1500,7 @@ test('should emit deprecation warning when trying to use the reply.context.confi process.removeAllListeners('warning') process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.name, 'DeprecationWarning') t.equal(warning.code, deprecationCode) } diff --git a/test/reply-trailers.test.js b/test/reply-trailers.test.js index f49275a58b1..57a31bcf086 100644 --- a/test/reply-trailers.test.js +++ b/test/reply-trailers.test.js @@ -200,7 +200,7 @@ test('should emit deprecation warning when using direct return', t => { process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyDeprecation') + t.equal(warning.name, 'DeprecationWarning') t.equal(warning.code, 'FSTDEP013') } t.teardown(() => process.removeListener('warning', onWarning)) From e4d4a213045a12de42dd7a041e75dbbea7d2c54f Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Sat, 28 Oct 2023 14:24:09 +0200 Subject: [PATCH 0499/1295] docs: added documentation about warnings (#5108) --- docs/Reference/Index.md | 2 ++ docs/Reference/Warnings.md | 52 ++++++++++++++++++++++++++++++++++++++ lib/warnings.js | 15 +++++++++++ 3 files changed, 69 insertions(+) create mode 100644 docs/Reference/Warnings.md diff --git a/docs/Reference/Index.md b/docs/Reference/Index.md index e72a380f66a..ef44aed2ec1 100644 --- a/docs/Reference/Index.md +++ b/docs/Reference/Index.md @@ -69,3 +69,5 @@ This table of contents is in alphabetical order. + [Validation and Serialization](./Validation-and-Serialization.md): Details Fastify's support for validating incoming data and how Fastify serializes data for responses. ++ [Warnings](./Warnings.md): Details the warnings Fastify emits and how to + solve them. diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md new file mode 100644 index 00000000000..eaf2de28f2d --- /dev/null +++ b/docs/Reference/Warnings.md @@ -0,0 +1,52 @@ + +

Fastify

+ +## Warnings + +### Warnings In Fastify +Warnings are enabled by default. They can be disabled by using any +of the following methods: + +- setting the `NODE_NO_WARNINGS` environment variable to `1` +- passing the `--no-warnings` flag to the node process +- setting 'no-warnings' in the `NODE_OPTIONS` environment variable + +For more information on how to disable warnings, see [node's documentation](https://nodejs.org/api/cli.html). + +However, disabling warnings is not recommended as it may cause +potential problems when upgrading Fastify versions. +Only experienced users should consider disabling warnings. + +### Fastify Warning Codes + +| Code | Description | How to solve | Discussion | +| ---- | ----------- | ------------ | ---------- | +| **FSTWRN001** | The specified schema for a route is missing. This may indicate the schema is not well specified. | Check the schema for the route. | [#4647](https://github.com/fastify/fastify/pull/4647) | + + +### Fastify Deprecation Codes + +Deprecation codes are further supported by the Node.js CLI options: + +- [--no-deprecation](https://nodejs.org/api/cli.html#--no-deprecation) +- [--throw-deprecation](https://nodejs.org/api/cli.html#--throw-deprecation) +- [--trace-deprecation](https://nodejs.org/api/cli.html#--trace-deprecation) + + +| Code | Description | How to solve | Discussion | +| ---- | ----------- | ------------ | ---------- | +| **FSTDEP005** | You are accessing the deprecated `request.connection` property. | Use `request.socket`. | [#2594](https://github.com/fastify/fastify/pull/2594) | +| **FSTDEP006** | You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. | Do not use Arrays/Objects as values when decorating Request/Reply. | [#2688](https://github.com/fastify/fastify/pull/2688) | +| **FSTDEP007** | You are trying to set a HEAD route using `exposeHeadRoute` route flag when a sibling route is already set. | Remove `exposeHeadRoutes` or explicitly set `exposeHeadRoutes` to `false` | [#2700](https://github.com/fastify/fastify/pull/2700) | +| **FSTDEP008** | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | +| **FSTDEP009** | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | +| **FSTDEP010** | Modifying the `reply.sent` property is deprecated. | Use the `reply.hijack()` method. | [#3140](https://github.com/fastify/fastify/pull/3140) | +| **FSTDEP011** | Variadic listen method is deprecated. | Use `.listen(optionsObject)`. | [#3712](https://github.com/fastify/fastify/pull/3712) | +| **FSTDEP012** | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) | +| **FSTDEP013** | Direct return of "trailers" function is deprecated. | Use "callback" or "async-await" for return value. | [#4380](https://github.com/fastify/fastify/pull/4380) | +| **FSTDEP014** | You are trying to set/access the default route. This property is deprecated. | Use `setNotFoundHandler` if you want to custom a 404 handler or the wildcard (`*`) to match all routes. | [#4480](https://github.com/fastify/fastify/pull/4480) | +| **FSTDEP015** | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| **FSTDEP016** | You are accessing the deprecated `request.routeConfig` property. | Use `request.routeOptions.config`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| **FSTDEP017** | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| **FSTDEP018** | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| **FSTDEP019** | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | diff --git a/lib/warnings.js b/lib/warnings.js index 9e8b75c3f77..905c30454c7 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -5,6 +5,21 @@ const warning = require('process-warning')() /** * Deprecation codes: * - FSTDEP005 + * - FSTDEP006 + * - FSTDEP007 + * - FSTDEP008 + * - FSTDEP009 + * - FSTDEP010 + * - FSTDEP011 + * - FSTDEP012 + * - FSTDEP013 + * - FSTDEP014 + * - FSTDEP015 + * - FSTDEP016 + * - FSTDEP017 + * - FSTDEP018 + * - FSTDEP019 + * - FSTWRN001 */ warning.createDeprecation('FSTDEP005', 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.') From f40c98aa9019058e942ac0b95fafd378c8a673a9 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 29 Oct 2023 14:24:09 +0000 Subject: [PATCH 0500/1295] test(logger): restrict temp file permissions (#5128) --- test/logger/instantiation.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/logger/instantiation.test.js b/test/logger/instantiation.test.js index 9d274672344..ffa38ac8cab 100644 --- a/test/logger/instantiation.test.js +++ b/test/logger/instantiation.test.js @@ -151,7 +151,8 @@ t.test('logger instantiation', (t) => { ] const { file, cleanup } = createTempFile(t) - if (process.env.CITGM) { fs.writeFileSync(file, '') } + // 0600 permissions (read/write for owner only) + if (process.env.CITGM) { fs.writeFileSync(file, '', { mode: 0o600 }) } const fastify = Fastify({ logger: { file } From 5d90c61e179b88cb3776074234df00740bcb53d2 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 29 Oct 2023 14:25:09 +0000 Subject: [PATCH 0501/1295] refactor(lib/hooks): replace `typeof` undefined check (#5127) --- lib/hooks.js | 2 +- test/types/request.test-d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/hooks.js b/lib/hooks.js index 6f7357ca54a..45fdb2b3322 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -331,7 +331,7 @@ function preParsingHookRunner (functions, request, reply, cb) { return } - if (typeof newPayload !== 'undefined') { + if (newPayload !== undefined) { request[kRequestPayloadStream] = newPayload } diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 502b877b796..fb6a98a3dfb 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -121,7 +121,7 @@ function putHandler (request: CustomRequest, reply: FastifyReply) { expectType(request.params) expectType(request.headers) expectType(request.query) - if (typeof request.body === 'undefined') { + if (request.body === undefined) { expectType(request.body) } else { expectType(request.body.content) From b002134319fe9b85fe392513447de8a6d16f0a0c Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 29 Oct 2023 14:51:14 +0000 Subject: [PATCH 0502/1295] chore: replace mention of fastify `.io` domain with `.dev` (#5129) --- README.md | 2 +- docs/Guides/Ecosystem.md | 8 +++--- docs/Guides/Getting-Started.md | 6 ++--- docs/Guides/Style-Guide.md | 14 +++++----- docs/Reference/Routes.md | 10 +++---- lib/errors.js | 2 +- test/constrained-routes.test.js | 48 ++++++++++++++++----------------- test/http2/constraint.test.js | 2 +- test/internals/errors.test.js | 2 +- test/schema-examples.test.js | 4 +-- test/types/route.test-d.ts | 6 ++--- 11 files changed, 52 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 8f35e87821c..c24a921f3fe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -
+
{ @@ -293,7 +293,7 @@ const fastifyPlugin = require('fastify-plugin') /** * Connects to a MongoDB database * @param {FastifyInstance} fastify Encapsulated Fastify Instance - * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options + * @param {Object} options plugin options, refer to https://www.fastify.dev/docs/latest/Reference/Plugins/#plugin-options */ async function dbConnector (fastify, options) { fastify.register(require('@fastify/mongodb'), { @@ -312,7 +312,7 @@ module.exports = fastifyPlugin(dbConnector) /** * A plugin that provide encapsulated routes * @param {FastifyInstance} fastify encapsulated fastify instance - * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options + * @param {Object} options plugin options, refer to https://www.fastify.dev/docs/latest/Reference/Plugins/#plugin-options */ async function routes (fastify, options) { const collection = fastify.mongo.db.collection('test_collection') diff --git a/docs/Guides/Style-Guide.md b/docs/Guides/Style-Guide.md index 16f2c73087f..97ca8c8bd5f 100644 --- a/docs/Guides/Style-Guide.md +++ b/docs/Guides/Style-Guide.md @@ -13,7 +13,7 @@ This guide is for anyone who loves to build with Fastify or wants to contribute to our documentation. You do not need to be an expert in writing technical documentation. This guide is here to help you. -Visit the [contribute](https://www.fastify.io/contribute) page on our website or +Visit the [contribute](https://www.fastify.dev/contribute) page on our website or read the [CONTRIBUTING.md](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md) file on GitHub to join our Open Source folks. @@ -70,12 +70,12 @@ markdown. **Example** ``` -To learn more about hooks, see [Fastify hooks](https://www.fastify.io/docs/latest/Reference/Hooks/). +To learn more about hooks, see [Fastify hooks](https://www.fastify.dev/docs/latest/Reference/Hooks/). ``` Result: >To learn more about hooks, see [Fastify ->hooks](https://www.fastify.io/docs/latest/Reference/Hooks/). +>hooks](https://www.fastify.dev/docs/latest/Reference/Hooks/). @@ -224,18 +224,18 @@ hyperlink should look: // Add clear & brief description -[Fastify Plugins] (https://www.fastify.io/docs/latest/Plugins/) +[Fastify Plugins] (https://www.fastify.dev/docs/latest/Plugins/) // incomplete description -[Fastify] (https://www.fastify.io/docs/latest/Plugins/) +[Fastify] (https://www.fastify.dev/docs/latest/Plugins/) // Adding title in link brackets -[](https://www.fastify.io/docs/latest/Plugins/ "fastify plugin") +[](https://www.fastify.dev/docs/latest/Plugins/ "fastify plugin") // Empty title -[](https://www.fastify.io/docs/latest/Plugins/) +[](https://www.fastify.dev/docs/latest/Plugins/) // Adding links localhost URLs instead of using code strings (``) [http://localhost:3000/](http://localhost:3000/) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index de2132b9ecf..0cbc52188a0 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -715,9 +715,9 @@ arbitrary host matching. fastify.route({ method: 'GET', url: '/', - constraints: { host: 'auth.fastify.io' }, + constraints: { host: 'auth.fastify.dev' }, handler: function (request, reply) { - reply.send('hello world from auth.fastify.io') + reply.send('hello world from auth.fastify.dev') } }) @@ -735,10 +735,10 @@ fastify.inject({ method: 'GET', url: '/', headers: { - 'Host': 'auth.fastify.io' + 'Host': 'auth.fastify.dev' } }, (err, res) => { - // => 'hello world from auth.fastify.io' + // => 'hello world from auth.fastify.dev' }) ``` @@ -749,7 +749,7 @@ matching wildcard subdomains (or any other pattern): fastify.route({ method: 'GET', url: '/', - constraints: { host: /.*\.fastify\.io/ }, // will match any subdomain of fastify.io + constraints: { host: /.*\.fastify\.io/ }, // will match any subdomain of fastify.dev handler: function (request, reply) { reply.send('hello world from ' + request.headers.host) } diff --git a/lib/errors.js b/lib/errors.js index a371bcbac37..d800d23ae74 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -179,7 +179,7 @@ const codes = { */ FST_ERR_MISSING_MIDDLEWARE: createError( 'FST_ERR_MISSING_MIDDLEWARE', - 'You must register a plugin for handling middlewares, visit fastify.io/docs/latest/Reference/Middleware/ for more info.', + 'You must register a plugin for handling middlewares, visit fastify.dev/docs/latest/Reference/Middleware/ for more info.', 500 ), diff --git a/test/constrained-routes.test.js b/test/constrained-routes.test.js index e462f052182..250d74567a4 100644 --- a/test/constrained-routes.test.js +++ b/test/constrained-routes.test.js @@ -11,7 +11,7 @@ test('Should register a host constrained route', t => { fastify.route({ method: 'GET', url: '/', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.send({ hello: 'world' }) } @@ -21,7 +21,7 @@ test('Should register a host constrained route', t => { method: 'GET', url: '/', headers: { - host: 'fastify.io' + host: 'fastify.dev' } }, (err, res) => { t.error(err) @@ -56,9 +56,9 @@ test('Should register the same route with host constraints', t => { fastify.route({ method: 'GET', url: '/', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { - reply.send('fastify.io') + reply.send('fastify.dev') } }) @@ -75,12 +75,12 @@ test('Should register the same route with host constraints', t => { method: 'GET', url: '/', headers: { - host: 'fastify.io' + host: 'fastify.dev' } }, (err, res) => { t.error(err) t.equal(res.statusCode, 200) - t.equal(res.payload, 'fastify.io') + t.equal(res.payload, 'fastify.dev') }) fastify.inject({ @@ -479,7 +479,7 @@ test('The hasConstraintStrategy should return false for default constraints unti fastify.route({ method: 'GET', url: '/', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.send({ hello: 'from any other domain' }) } @@ -533,9 +533,9 @@ test('Should allow registering an unconstrained route after a constrained route' fastify.route({ method: 'GET', url: '/', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { - reply.send({ hello: 'from fastify.io' }) + reply.send({ hello: 'from fastify.dev' }) } }) @@ -551,11 +551,11 @@ test('Should allow registering an unconstrained route after a constrained route' method: 'GET', url: '/', headers: { - host: 'fastify.io' + host: 'fastify.dev' } }, (err, res) => { t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from fastify.io' }) + t.same(JSON.parse(res.payload), { hello: 'from fastify.dev' }) t.equal(res.statusCode, 200) }) @@ -580,7 +580,7 @@ test('Should allow registering constrained routes in a prefixed plugin', t => { fastify.register(async (scope, opts) => { scope.route({ method: 'GET', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, path: '/route', handler: (req, reply) => { reply.send({ ok: true }) @@ -592,7 +592,7 @@ test('Should allow registering constrained routes in a prefixed plugin', t => { method: 'GET', url: '/prefix/route', headers: { - host: 'fastify.io' + host: 'fastify.dev' } }, (err, res) => { t.error(err) @@ -608,7 +608,7 @@ test('Should allow registering a constrained GET route after a constrained HEAD fastify.route({ method: 'HEAD', url: '/', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.header('content-type', 'text/plain') reply.send('custom HEAD response') @@ -618,7 +618,7 @@ test('Should allow registering a constrained GET route after a constrained HEAD fastify.route({ method: 'GET', url: '/', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.send({ hello: 'from any other domain' }) } @@ -628,7 +628,7 @@ test('Should allow registering a constrained GET route after a constrained HEAD method: 'HEAD', url: '/', headers: { - host: 'fastify.io' + host: 'fastify.dev' } }, (err, res) => { t.error(err) @@ -653,7 +653,7 @@ test('Should allow registering a constrained GET route after an unconstrained HE fastify.route({ method: 'GET', url: '/', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.header('content-type', 'text/plain') reply.send('Hello from constrains: length is about 41') @@ -664,7 +664,7 @@ test('Should allow registering a constrained GET route after an unconstrained HE method: 'HEAD', url: '/', headers: { - host: 'fastify.io' + host: 'fastify.dev' } }, (err, res) => { t.error(err) @@ -682,7 +682,7 @@ test('Will not try to re-createprefixed HEAD route if it already exists and expo scope.route({ method: 'HEAD', path: '/route', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.header('content-type', 'text/plain') reply.send('custom HEAD response') @@ -691,7 +691,7 @@ test('Will not try to re-createprefixed HEAD route if it already exists and expo scope.route({ method: 'GET', path: '/route', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.send({ ok: true }) } @@ -723,7 +723,7 @@ test('allows separate constrained and unconstrained HEAD routes', async (t) => { scope.route({ method: 'HEAD', path: '/route', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.header('content-type', 'text/plain') reply.send('constrained HEAD response') @@ -733,7 +733,7 @@ test('allows separate constrained and unconstrained HEAD routes', async (t) => { scope.route({ method: 'GET', path: '/route', - constraints: { host: 'fastify.io' }, + constraints: { host: 'fastify.dev' }, handler: (req, reply) => { reply.send({ ok: true }) } @@ -869,7 +869,7 @@ test('Allow regex constraints in routes', t => { fastify.route({ method: 'GET', url: '/', - constraints: { host: /.*\.fastify\.io/ }, + constraints: { host: /.*\.fastify\.dev$/ }, handler: (req, reply) => { reply.send({ hello: 'from fastify dev domain' }) } @@ -879,7 +879,7 @@ test('Allow regex constraints in routes', t => { method: 'GET', url: '/', headers: { - host: 'dev.fastify.io' + host: 'dev.fastify.dev' } }, (err, res) => { t.error(err) diff --git a/test/http2/constraint.test.js b/test/http2/constraint.test.js index 6c0162bf1dd..b2286ab2a01 100644 --- a/test/http2/constraint.test.js +++ b/test/http2/constraint.test.js @@ -28,7 +28,7 @@ test('A route supports host constraints under http2 protocol and secure connecti t.fail('Key/cert loading failed', e) } - const constrain = 'fastify.io' + const constrain = 'fastify.dev' fastify.route({ method: 'GET', diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index fe207bf8879..dbd1789689a 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -292,7 +292,7 @@ test('FST_ERR_MISSING_MIDDLEWARE', t => { const error = new errors.FST_ERR_MISSING_MIDDLEWARE() t.equal(error.name, 'FastifyError') t.equal(error.code, 'FST_ERR_MISSING_MIDDLEWARE') - t.equal(error.message, 'You must register a plugin for handling middlewares, visit fastify.io/docs/latest/Reference/Middleware/ for more info.') + t.equal(error.message, 'You must register a plugin for handling middlewares, visit fastify.dev/docs/latest/Reference/Middleware/ for more info.') t.equal(error.statusCode, 500) t.ok(error instanceof Error) }) diff --git a/test/schema-examples.test.js b/test/schema-examples.test.js index 70b6b16503e..fc2a26ccdf7 100644 --- a/test/schema-examples.test.js +++ b/test/schema-examples.test.js @@ -540,7 +540,7 @@ test('should be able to handle formats of ajv-formats when added by plugins opti method: 'POST', payload: { id: '254381a5-888c-4b41-8116-e3b1a54980bd', - email: 'info@fastify.io' + email: 'info@fastify.dev' }, url: '/' }, (_err, res) => { @@ -552,7 +552,7 @@ test('should be able to handle formats of ajv-formats when added by plugins opti method: 'POST', payload: { id: 'invalid', - email: 'info@fastify.io' + email: 'info@fastify.dev' }, url: '/' }, (_err, res) => { diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index f944eed5fcc..a1d7132d761 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -425,19 +425,19 @@ expectType(fastify().hasRoute({ expectType(fastify().hasRoute({ url: '/', method: 'GET', - constraints: { host: 'auth.fastify.io' } + constraints: { host: 'auth.fastify.dev' } })) expectType(fastify().hasRoute({ url: '/', method: 'GET', - constraints: { host: /.*\.fastify\.io/ } + constraints: { host: /.*\.fastify\.dev$/ } })) expectType(fastify().hasRoute({ url: '/', method: 'GET', - constraints: { host: /.*\.fastify\.io/, version: '1.2.3' } + constraints: { host: /.*\.fastify\.dev$/, version: '1.2.3' } })) expectType(fastify().hasRoute({ From c3a9f49d534026eb0305c2948a0d99fc05692f96 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 30 Oct 2023 14:23:53 -0700 Subject: [PATCH 0503/1295] docs(security): add prose explaining OpenSSF CII Best Practices badge results (#5111) --- SECURITY.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index ef95ffcf90a..85f72e4bc77 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -130,3 +130,30 @@ work as a member of the Fastify Core team. * [__KaKa Ng__](https://github.com/climba03003) * [__James Sumners__](https://github.com/jsumners), , + +## OpenSSF CII Best Practices + +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585) + +There are three “tiers”: passing, silver, and gold. + +### Passing +We meet 100% of the “passing” criteria. + +### Silver +We meet 87% of the “silver” criteria. The gaps are as follows: + - we do not have a DCO or a CLA process for contributions. + - we do not currently document + “what the user can and cannot expect in terms of security” for our project. + - we do not currently document ”the architecture (aka high-level design)” + for our project. + +### Gold +We meet 70% of the “gold” criteria. The gaps are as follows: + - we do not yet have the “silver” badge; see all the gaps above. + - We do not include a copyright or license statement in each source file. + Efforts are underway to change this archaic practice into a + suggestion instead of a hard requirement. + - There are a few unanswered questions around cryptography that are + waiting for clarification. + From 396b8b95bf0f1313b9deeb387d68fdc7ffa97abe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:33:04 +0000 Subject: [PATCH 0504/1295] chore: Bump actions/setup-node from 3 to 4 (#5134) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark-parser.yml | 2 +- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 10 +++++----- .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/md-lint.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index f28cf5d6e50..b9e1fad5370 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -27,7 +27,7 @@ jobs: ref: ${{github.event.pull_request.head.sha}} repository: ${{github.event.pull_request.head.repo.full_name}} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d467c5c46dc..a16608f7840 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -27,7 +27,7 @@ jobs: ref: ${{github.event.pull_request.head.sha}} repository: ${{github.event.pull_request.head.repo.full_name}} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e048e737646..afd33f4e3a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 'lts/*' cache: 'npm' @@ -72,7 +72,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 'lts/*' cache: 'npm' @@ -122,7 +122,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -150,7 +150,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' @@ -178,7 +178,7 @@ jobs: with: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 74f3dbf3614..1bbf852e8f9 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -13,7 +13,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 3a2112243e5..903e27e8e33 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -13,7 +13,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index f9e785ec009..f6efd0c64b6 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -38,7 +38,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index 77be1ce2b73..d3601a6cda7 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 'lts/*' diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 291e60b826f..7db25b827b4 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -30,7 +30,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -62,7 +62,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} From 7aa802ed224b91ca559edec469a6b903e89a7f88 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 2 Nov 2023 06:54:43 -0400 Subject: [PATCH 0505/1295] Fixes #5113: routeOptions missing type for handler property (#5136) --- test/types/request.test-d.ts | 1 + types/request.d.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index fb6a98a3dfb..0debbdebeea 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -82,6 +82,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.routeOptions.config) expectType(request.routeSchema) expectType(request.routeOptions.schema) + expectType(request.routeOptions.handler) expectType(request.headers) request.headers = {} diff --git a/types/request.d.ts b/types/request.d.ts index 75e13475ee0..5bd9ae3bb11 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -2,7 +2,7 @@ import { ErrorObject } from '@fastify/ajv-compiler' import { FastifyRequestContext, FastifyContextConfig } from './context' import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' -import { RouteGenericInterface, FastifyRouteConfig } from './route' +import { RouteGenericInterface, FastifyRouteConfig, RouteHandlerMethod } from './route' import { FastifySchema } from './schema' import { FastifyRequestType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyRequestType } from './type-provider' import { ContextConfigDefault, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './utils' @@ -31,6 +31,7 @@ export interface RequestRouteOptions Date: Thu, 2 Nov 2023 20:14:18 +0000 Subject: [PATCH 0506/1295] docs(readme): fix ci badge path (#5138) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c24a921f3fe..194ddb73e2d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@
-[![CI](https://github.com/fastify/fastify/workflows/ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/ci.yml) +[![CI](https://github.com/fastify/fastify/actions/workflows/ci.yml/badge.svg)](https://github.com/fastify/fastify/actions/workflows/ci.yml) [![Package Manager CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml) [![Web From 67ee5958e1d75caf338e3392640eb1af18c44f7a Mon Sep 17 00:00:00 2001 From: John Ko Date: Sun, 5 Nov 2023 12:17:19 -0800 Subject: [PATCH 0507/1295] docs: typo in Typescript docs (#5145) --- docs/Reference/TypeScript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 3189f5458cb..3fa07159e27 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -256,7 +256,7 @@ can do it as follows: const fastify = Fastify().withTypeProvider() - app.post<{ Body: UserType, Reply: UserType }>( + fastify.post<{ Body: UserType, Reply: UserType }>( '/', { schema: { From 9dc30b504002aba9db5e432c21807018f107bdc5 Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Mon, 6 Nov 2023 19:27:36 +0100 Subject: [PATCH 0508/1295] feat(plugins): mixing async and callback style now returns a warning (#5139) * fix(plugins): mixing async and callback style now returns a warning * test(hooks, register): updated tests to not emit warnings * fix(pluginUtils): emit warning after registering plugin name to better identify the wrong plugin * applied @Eomm suggestions * docs(warning): updated description of warning and added discussion link * syle(pluginUtils): revert unwanted style change --- docs/Reference/Warnings.md | 1 + lib/pluginUtils.js | 11 ++++++++++- lib/warnings.js | 3 +++ test/hooks.test.js | 4 +--- test/plugin.4.test.js | 28 ++++++++++++++++++++++++++++ test/register.test.js | 10 +++++----- 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index eaf2de28f2d..34a0fab0e1b 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -22,6 +22,7 @@ Only experienced users should consider disabling warnings. | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | | **FSTWRN001** | The specified schema for a route is missing. This may indicate the schema is not well specified. | Check the schema for the route. | [#4647](https://github.com/fastify/fastify/pull/4647) | +| **FSTWRN002** | The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`. | Do not mix async and callback style. | [#5139](https://github.com/fastify/fastify/pull/5139) | ### Fastify Deprecation Codes diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 70be3fb0ee6..9429a40c7d5 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -11,6 +11,7 @@ const { FST_ERR_PLUGIN_VERSION_MISMATCH, FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE } = require('./errors') +const warning = require('./warnings.js') function getMeta (fn) { return fn[Symbol.for('plugin-meta')] @@ -132,10 +133,18 @@ function registerPluginName (fn) { const name = meta.name if (!name) return this[kRegisteredPlugins].push(name) + return name +} + +function checkPluginHealthiness (fn, pluginName = 'anonymous') { + if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) { + warning.emit('FSTWRN002', pluginName) + } } function registerPlugin (fn) { - registerPluginName.call(this, fn) + const pluginName = registerPluginName.call(this, fn) + checkPluginHealthiness.call(this, fn, pluginName) checkVersion.call(this, fn) checkDecorators.call(this, fn) checkDependencies.call(this, fn) diff --git a/lib/warnings.js b/lib/warnings.js index 905c30454c7..5ec503a6234 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -20,6 +20,7 @@ const warning = require('process-warning')() * - FSTDEP018 * - FSTDEP019 * - FSTWRN001 + * - FSTWRN002 */ warning.createDeprecation('FSTDEP005', 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.') @@ -54,4 +55,6 @@ warning.createDeprecation('FSTDEP019', 'reply.context property access is depreca warning.create('FastifyWarning', 'FSTWRN001', 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', { unlimited: true }) +warning.create('FastifyWarning', 'FSTWRN002', 'The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`', { unlimited: true }) + module.exports = warning diff --git a/test/hooks.test.js b/test/hooks.test.js index 02acbd0c6cc..1cd2968546d 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3469,7 +3469,7 @@ test('onRequestAbort should support encapsulation', t => { done() }) - fastify.register(async function (_child, _, done) { + fastify.register(async function (_child, _) { child = _child fastify.addHook('onRequestAbort', async function (req) { @@ -3488,8 +3488,6 @@ test('onRequestAbort should support encapsulation', t => { t.equal(++order, 3, 'called in route') } }) - - done() }) fastify.listen({ port: 0 }, err => { diff --git a/test/plugin.4.test.js b/test/plugin.4.test.js index a162f939cd2..562aabd29c9 100644 --- a/test/plugin.4.test.js +++ b/test/plugin.4.test.js @@ -414,3 +414,31 @@ test('hasPlugin returns true when using encapsulation', async t => { await fastify.ready() }) + +test('registering plugins with mixed style should return a warning', async t => { + t.plan(4) + + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifyWarning') + t.equal(warning.code, 'FSTWRN002') + } + + const fastify = Fastify() + + const anonymousPlugin = async (app, opts, done) => { + done() + } + + const pluginName = 'error-plugin' + const errorPlugin = async (app, opts, done) => { + done() + } + + const namedPlugin = fp(errorPlugin, { name: pluginName }) + + fastify.register(namedPlugin) + fastify.register(anonymousPlugin) + + await fastify.ready() +}) diff --git a/test/register.test.js b/test/register.test.js index 2c9dddcbe83..99c95746b0e 100644 --- a/test/register.test.js +++ b/test/register.test.js @@ -105,20 +105,20 @@ test('awaitable register and after', async t => { let second = false let third = false - await fastify.register(async (instance, opts, done) => { + await fastify.register(async (instance, opts) => { first = true }) t.equal(first, true) - fastify.register(async (instance, opts, done) => { + fastify.register(async (instance, opts) => { second = true }) await fastify.after() t.equal(second, true) - fastify.register(async (instance, opts, done) => { + fastify.register(async (instance, opts) => { third = true }) @@ -145,7 +145,7 @@ test('awaitable register error handling', async t => { await t.rejects(fastify.after(), e) - fastify.register(async (instance, opts, done) => { + fastify.register(async (instance, opts) => { t.fail('should not be executed') }) @@ -167,7 +167,7 @@ test('awaitable after error handling', async t => { await t.rejects(fastify.after(), e) - fastify.register(async (instance, opts, done) => { + fastify.register(async (instance, opts) => { t.fail('should not be executed') }) From c0ab598c0f0a415dbbcdf3273927fc467fa56b8d Mon Sep 17 00:00:00 2001 From: Fawaz Ahmed Date: Tue, 7 Nov 2023 04:37:30 +0530 Subject: [PATCH 0509/1295] docs: mention about multipart support (#5144) --- docs/Reference/ContentTypeParser.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index c995299348f..1e09d7c81fe 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -4,6 +4,8 @@ Natively, Fastify only supports `'application/json'` and `'text/plain'` content types. If the content type is not one of these, an `FST_ERR_CTP_INVALID_MEDIA_TYPE` error will be thrown. +Other common content types are supported through the use of +[plugins](https://fastify.dev/ecosystem/). The default charset is `utf-8`. If you need to support different content types, you can use the `addContentTypeParser` API. *The default JSON and/or plain text From 52bbc3716159024dac67248cdb88708f558c36e4 Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Thu, 9 Nov 2023 18:59:57 +0100 Subject: [PATCH 0510/1295] feat(plugins): mixing async and callback style now returns an error (#5141) --- docs/Reference/Errors.md | 5 +++++ lib/errors.js | 6 ++++++ lib/pluginUtils.js | 6 +++--- test/internals/errors.test.js | 20 ++++++++++++++----- test/plugin.4.test.js | 36 +++++++++++++++++++++++++---------- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index df9f3ee4a66..4c4a3d09e68 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -529,6 +529,11 @@ Plugin did not start in time. Default timeout (in milliseconds): `10000` The decorator is not present in the instance. +#### FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER + + +The plugin being registered mixes async and callback styles. + #### FST_ERR_VALIDATION diff --git a/lib/errors.js b/lib/errors.js index 7bf3194ee14..49f52f1a119 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -434,6 +434,12 @@ const codes = { 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE', "The decorator '%s'%s is not present in %s" ), + FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER: createError( + 'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER', + 'The %s plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.', + 500, + TypeError + ), /** * Avvio Errors diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 9429a40c7d5..0122d614366 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -9,9 +9,9 @@ const { const { exist, existReply, existRequest } = require('./decorate') const { FST_ERR_PLUGIN_VERSION_MISMATCH, - FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE + FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE, + FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER } = require('./errors') -const warning = require('./warnings.js') function getMeta (fn) { return fn[Symbol.for('plugin-meta')] @@ -138,7 +138,7 @@ function registerPluginName (fn) { function checkPluginHealthiness (fn, pluginName = 'anonymous') { if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) { - warning.emit('FSTWRN002', pluginName) + throw new FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER(pluginName) } } diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index dcd77e57396..bd407dc304c 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -test('should expose 80 errors', t => { +test('should expose 81 errors', t => { t.plan(1) const exportedKeys = Object.keys(errors) let counter = 0 @@ -14,11 +14,11 @@ test('should expose 80 errors', t => { counter++ } } - t.equal(counter, 80) + t.equal(counter, 81) }) test('ensure name and codes of Errors are identical', t => { - t.plan(80) + t.plan(81) const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -767,6 +767,16 @@ test('FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE', t => { t.ok(error instanceof Error) }) +test('FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER('easter-egg') + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER') + t.equal(error.message, 'The easter-egg plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + test('FST_ERR_PLUGIN_CALLBACK_NOT_FN', t => { t.plan(5) const error = new errors.FST_ERR_PLUGIN_CALLBACK_NOT_FN() @@ -838,7 +848,7 @@ test('FST_ERR_LISTEN_OPTIONS_INVALID', t => { }) test('Ensure that all errors are in Errors.md documented', t => { - t.plan(80) + t.plan(81) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -850,7 +860,7 @@ test('Ensure that all errors are in Errors.md documented', t => { }) test('Ensure that non-existing errors are not in Errors.md documented', t => { - t.plan(80) + t.plan(81) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /#### ([0-9a-zA-Z_]+)\n/g diff --git a/test/plugin.4.test.js b/test/plugin.4.test.js index 562aabd29c9..4aea52f9ec7 100644 --- a/test/plugin.4.test.js +++ b/test/plugin.4.test.js @@ -7,6 +7,7 @@ const test = t.test const Fastify = require('../fastify') const fp = require('fastify-plugin') const fakeTimer = require('@sinonjs/fake-timers') +const { FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER } = require('../lib/errors') test('pluginTimeout', t => { t.plan(5) @@ -415,14 +416,8 @@ test('hasPlugin returns true when using encapsulation', async t => { await fastify.ready() }) -test('registering plugins with mixed style should return a warning', async t => { - t.plan(4) - - process.on('warning', onWarning) - function onWarning (warning) { - t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, 'FSTWRN002') - } +test('registering anonymous plugin with mixed style should throw', async t => { + t.plan(2) const fastify = Fastify() @@ -430,6 +425,22 @@ test('registering plugins with mixed style should return a warning', async t => done() } + fastify.register(anonymousPlugin) + + try { + await fastify.ready() + t.fail('should throw') + } catch (error) { + t.type(error, FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) + t.equal(error.message, 'The anonymous plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + } +}) + +test('registering named plugin with mixed style should throw', async t => { + t.plan(2) + + const fastify = Fastify() + const pluginName = 'error-plugin' const errorPlugin = async (app, opts, done) => { done() @@ -438,7 +449,12 @@ test('registering plugins with mixed style should return a warning', async t => const namedPlugin = fp(errorPlugin, { name: pluginName }) fastify.register(namedPlugin) - fastify.register(anonymousPlugin) - await fastify.ready() + try { + await fastify.ready() + t.fail('should throw') + } catch (error) { + t.type(error, FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) + t.equal(error.message, 'The error-plugin plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + } }) From 4eee3c9f29f7a5efc4f34f9b619b10c204406b53 Mon Sep 17 00:00:00 2001 From: Jonas Galvez Date: Fri, 10 Nov 2023 04:53:39 -0300 Subject: [PATCH 0511/1295] docs: add @fastify/vite to core plugins list (#5153) This package received core status a long time ago but it was still missing from this list. --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 4ff68a90c52..d1b7305cfa7 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -138,6 +138,8 @@ section. the `Request` object with a method to access raw URL components. - [`@fastify/view`](https://github.com/fastify/point-of-view) Templates rendering (_ejs, pug, handlebars, marko_) plugin support for Fastify. +- [`@fastify/vite`](https://github.com/fastify/fastify-vite) Integration with + [Vite](https://vitejs.dev/), allows for serving SPA/MPA/SSR Vite applications. - [`@fastify/websocket`](https://github.com/fastify/fastify-websocket) WebSocket support for Fastify. Built upon [ws](https://github.com/websockets/ws). From 77d6fe62d6e14d6a148730100e9d58712331eb1c Mon Sep 17 00:00:00 2001 From: Hans Pagel Date: Fri, 10 Nov 2023 16:59:07 +0100 Subject: [PATCH 0512/1295] docs: add @scalar/fastify-api-reference to community plugins list (#5154) This commit adds `@scalar/fastify-api-reference` to the list of community plugins. This plugin renders modern OpenAPI/Swagger docs based on `@fastify/swagger`. --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index d1b7305cfa7..45c84cd4254 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -197,6 +197,8 @@ section. Fast sodium-based crypto for @mgcrea/fastify-session - [`@mgcrea/pino-pretty-compact`](https://github.com/mgcrea/pino-pretty-compact) A custom compact pino-base prettifier +- [`@scalar/fastify-api-reference`](https://github.com/scalar/scalar/tree/main/packages/fastify-api-reference) + Beautiful OpenAPI/Swagger API references for Fastify - [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs) SeaweedFS for Fastify - [`apollo-server-fastify`](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify) From b4a413889f78029b9709e991e8869d450c26c9a0 Mon Sep 17 00:00:00 2001 From: shadahmad7 Date: Sat, 11 Nov 2023 00:27:26 +0530 Subject: [PATCH 0513/1295] #5151-Removed routeOptions reference from Reply.md --- docs/Reference/Reply.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 94674b940fe..21c95446f1c 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -86,7 +86,6 @@ object that exposes the following functions and properties: - `.log` - The logger instance of the incoming request. - `.request` - The incoming request. - `.context` - Deprecated, access the [Request's context](./Request.md) property. -- `.routeOptions` - Access the [Request's routeOptions](./Request.md) property. ```js fastify.get('/', options, function (request, reply) { From 8f1513bae4d5c97ea5de7443123ca5fcd37988f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Acosta?= Date: Wed, 15 Nov 2023 13:34:44 -0300 Subject: [PATCH 0514/1295] docs(ecosystem): add fastify-uws (#5160) * Update Ecosystem.md * Update Ecosystem.md * Update Ecosystem.md --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 45c84cd4254..8cb2d6fad1c 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -632,6 +632,8 @@ middlewares into Fastify plugins Fastify plugin to work with TypeORM. - [`fastify-user-agent`](https://github.com/Eomm/fastify-user-agent) parses your request's `user-agent` header. +- [`fastify-uws`](https://github.com/geut/fastify-uws) A Fastify plugin to + use the web server [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js). - [`fastify-vhost`](https://github.com/patrickpissurno/fastify-vhost) Proxy subdomain HTTP requests to another server (useful if you want to point multiple subdomains to the same IP address, while running different servers on From 99a405a4e347629595ac5062c74d91675b1b34e5 Mon Sep 17 00:00:00 2001 From: Giovanni Bertoncelli <67106822+giovanni-bertoncelli@users.noreply.github.com> Date: Wed, 15 Nov 2023 17:03:04 +0100 Subject: [PATCH 0515/1295] docs: removed unmaintained fastify-nodemailer from ecosystem --- docs/Guides/Ecosystem.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 8cb2d6fad1c..3cec7407079 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -470,8 +470,6 @@ middlewares into Fastify plugins Add `additionalProperties: false` by default to your JSON Schemas. - [`fastify-no-icon`](https://github.com/jsumners/fastify-no-icon) Plugin to eliminate thrown errors for `/favicon.ico` requests. -- [`fastify-nodemailer`](https://github.com/lependu/fastify-nodemailer) Plugin - to share [nodemailer](https://nodemailer.com) transporter across Fastify. - [`fastify-normalize-request-reply`](https://github.com/ericrglass/fastify-normalize-request-reply) Plugin to normalize the request and reply to the Express version 4.x request and response, which allows use of middleware, like swagger-stats, that was From 6a337b0e02e2d28d031f409275c230e5b590dc76 Mon Sep 17 00:00:00 2001 From: Brett Willis Date: Fri, 24 Nov 2023 00:19:36 +1300 Subject: [PATCH 0516/1295] docs: clarify handling of streams and buffers (#5166) * docs: clarify handling of streams and buffers * docs: fix typo Co-authored-by: Andrey Chalkin --------- Co-authored-by: Andrey Chalkin --- docs/Reference/Reply.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 21c95446f1c..b3f579ce4f0 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -665,8 +665,12 @@ fastify.get('/json', options, function (request, reply) { #### Streams -*send* can also handle streams by setting the `'Content-Type'` header to -`'application/octet-stream'`. +If you are sending a stream and you have not set a `'Content-Type'` header, +*send* will set it to `'application/octet-stream'`. + +As noted above, streams are considered to be pre-serialized, so they will be +sent unmodified without response validation. + ```js fastify.get('/streams', function (request, reply) { const fs = require('node:fs') @@ -690,6 +694,10 @@ fastify.get('/streams', async function (request, reply) { If you are sending a buffer and you have not set a `'Content-Type'` header, *send* will set it to `'application/octet-stream'`. + +As noted above, Buffers are considered to be pre-serialized, so they will be +sent unmodified without response validation. + ```js const fs = require('node:fs') fastify.get('/streams', function (request, reply) { @@ -713,8 +721,12 @@ fastify.get('/streams', async function (request, reply) { #### TypedArrays -`send` manages TypedArray and sets the `'Content-Type'=application/octet-stream'` -header if not already set. +`send` manages TypedArray like a Buffer, and sets the `'Content-Type'` +header to `'application/octet-stream'` if not already set. + +As noted above, TypedArray/Buffers are considered to be pre-serialized, so they +will be sent unmodified without response validation. + ```js const fs = require('node:fs') fastify.get('/streams', function (request, reply) { From 9ab24be4621e4988e682686843664a42f4071f3d Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Thu, 23 Nov 2023 18:23:33 +0100 Subject: [PATCH 0517/1295] docs(#5142): aligned errors and warnings documentation (#5162) --- docs/Reference/Errors.md | 568 ++++++++++------------------------ docs/Reference/Warnings.md | 58 +++- test/internals/errors.test.js | 4 +- 3 files changed, 214 insertions(+), 416 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index b95c3aac360..9590d39fb53 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -3,6 +3,95 @@ ## Errors +**Table of contents** +- [Errors](#errors) + - [Error Handling In Node.js](#error-handling-in-node.js) + - [Uncaught Errors](#uncaught-errors) + - [Catching Errors In Promises](#catching-errors-in-promises) + - [Errors In Fastify](#errors-in-fastify) + - [Errors In Input Data](#errors-in-input-data) + - [Catching Uncaught Errors In Fastify](#catching-uncaught-errors-in-fastify) + - [Errors In Fastify Lifecycle Hooks And A Custom Error Handler](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler) + - [Fastify Error Codes](#fastify-error-codes) + - [FST_ERR_NOT_FOUND](#fst_err_not_found) + - [FST_ERR_OPTIONS_NOT_OBJ](#fst_err_options_not_obj) + - [FST_ERR_QSP_NOT_FN](#fst_err_qsp_not_fn) + - [FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN](#fst_err_schema_controller_bucket_opt_not_fn) + - [FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN](#fst_err_schema_error_formatter_not_fn) + - [FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ](#fst_err_ajv_custom_options_opt_not_obj) + - [FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR](#fst_err_ajv_custom_options_opt_not_arr) + - [FST_ERR_VERSION_CONSTRAINT_NOT_STR](#fst_err_version_constraint_not_str) + - [FST_ERR_CTP_ALREADY_PRESENT](#fst_err_ctp_already_present) + - [FST_ERR_CTP_INVALID_TYPE](#fst_err_ctp_invalid_type) + - [FST_ERR_CTP_EMPTY_TYPE](#fst_err_ctp_empty_type) + - [FST_ERR_CTP_INVALID_HANDLER](#fst_err_ctp_invalid_handler) + - [FST_ERR_CTP_INVALID_PARSE_TYPE](#fst_err_ctp_invalid_parse_type) + - [FST_ERR_CTP_BODY_TOO_LARGE](#fst_err_ctp_body_too_large) + - [FST_ERR_CTP_INVALID_MEDIA_TYPE](#fst_err_ctp_invalid_media_type) + - [FST_ERR_CTP_INVALID_CONTENT_LENGTH](#fst_err_ctp_invalid_content_length) + - [FST_ERR_CTP_EMPTY_JSON_BODY](#fst_err_ctp_empty_json_body) + - [FST_ERR_CTP_INSTANCE_ALREADY_STARTED](#fst_err_ctp_instance_already_started) + - [FST_ERR_INSTANCE_ALREADY_LISTENING](#fst_err_instance_already_listening) + - [FST_ERR_DEC_ALREADY_PRESENT](#fst_err_dec_already_present) + - [FST_ERR_DEC_DEPENDENCY_INVALID_TYPE](#fst_err_dec_dependency_invalid_type) + - [FST_ERR_DEC_MISSING_DEPENDENCY](#fst_err_dec_missing_dependency) + - [FST_ERR_DEC_AFTER_START](#fst_err_dec_after_start) + - [FST_ERR_HOOK_INVALID_TYPE](#fst_err_hook_invalid_type) + - [FST_ERR_HOOK_INVALID_HANDLER](#fst_err_hook_invalid_handler) + - [FST_ERR_HOOK_INVALID_ASYNC_HANDLER](#fst_err_hook_invalid_async_handler) + - [FST_ERR_HOOK_NOT_SUPPORTED](#fst_err_hook_not_supported) + - [FST_ERR_MISSING_MIDDLEWARE](#fst_err_missing_middleware) + - [FST_ERR_HOOK_TIMEOUT](#fst_err_hook_timeout) + - [FST_ERR_LOG_INVALID_DESTINATION](#fst_err_log_invalid_destination) + - [FST_ERR_LOG_INVALID_LOGGER](#fst_err_log_invalid_logger) + - [FST_ERR_REP_INVALID_PAYLOAD_TYPE](#fst_err_rep_invalid_payload_type) + - [FST_ERR_REP_ALREADY_SENT](#fst_err_rep_already_sent) + - [FST_ERR_REP_SENT_VALUE](#fst_err_rep_sent_value) + - [FST_ERR_SEND_INSIDE_ONERR](#fst_err_send_inside_onerr) + - [FST_ERR_SEND_UNDEFINED_ERR](#fst_err_send_undefined_err) + - [FST_ERR_BAD_STATUS_CODE](#fst_err_bad_status_code) + - [FST_ERR_BAD_TRAILER_NAME](#fst_err_bad_trailer_name) + - [FST_ERR_BAD_TRAILER_VALUE](#fst_err_bad_trailer_value) + - [FST_ERR_FAILED_ERROR_SERIALIZATION](#fst_err_failed_error_serialization) + - [FST_ERR_MISSING_SERIALIZATION_FN](#fst_err_missing_serialization_fn) + - [FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN](#fst_err_missing_contenttype_serialization_fn) + - [FST_ERR_REQ_INVALID_VALIDATION_INVOCATION](#fst_err_req_invalid_validation_invocation) + - [FST_ERR_SCH_MISSING_ID](#fst_err_sch_missing_id) + - [FST_ERR_SCH_ALREADY_PRESENT](#fst_err_sch_already_present) + - [FST_ERR_SCH_CONTENT_MISSING_SCHEMA](#fst_err_sch_content_missing_schema) + - [FST_ERR_SCH_DUPLICATE](#fst_err_sch_duplicate) + - [FST_ERR_SCH_VALIDATION_BUILD](#fst_err_sch_validation_build) + - [FST_ERR_SCH_SERIALIZATION_BUILD](#fst_err_sch_serialization_build) + - [FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX](#fst_err_sch_response_schema_not_nested_2xx) + - [FST_ERR_HTTP2_INVALID_VERSION](#fst_err_http2_invalid_version) + - [FST_ERR_INIT_OPTS_INVALID](#fst_err_init_opts_invalid) + - [FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE](#fst_err_force_close_connections_idle_not_available) + - [FST_ERR_DUPLICATED_ROUTE](#fst_err_duplicated_route) + - [FST_ERR_BAD_URL](#fst_err_bad_url) + - [FST_ERR_ASYNC_CONSTRAINT](#fst_err_async_constraint) + - [FST_ERR_DEFAULT_ROUTE_INVALID_TYPE](#fst_err_default_route_invalid_type) + - [FST_ERR_INVALID_URL](#fst_err_invalid_url) + - [FST_ERR_ROUTE_OPTIONS_NOT_OBJ](#fst_err_route_options_not_obj) + - [FST_ERR_ROUTE_DUPLICATED_HANDLER](#fst_err_route_duplicated_handler) + - [FST_ERR_ROUTE_HANDLER_NOT_FN](#fst_err_route_handler_not_fn) + - [FST_ERR_ROUTE_MISSING_HANDLER](#fst_err_route_missing_handler) + - [FST_ERR_ROUTE_METHOD_INVALID](#fst_err_route_method_invalid) + - [FST_ERR_ROUTE_METHOD_NOT_SUPPORTED](#fst_err_route_method_not_supported) + - [FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED](#fst_err_route_body_validation_schema_not_supported) + - [FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT](#fst_err_route_body_limit_option_not_int) + - [FST_ERR_ROUTE_REWRITE_NOT_STR](#fst_err_route_rewrite_not_str) + - [FST_ERR_REOPENED_CLOSE_SERVER](#fst_err_reopened_close_server) + - [FST_ERR_REOPENED_SERVER](#fst_err_reopened_server) + - [FST_ERR_PLUGIN_VERSION_MISMATCH](#fst_err_plugin_version_mismatch) + - [FST_ERR_PLUGIN_CALLBACK_NOT_FN](#fst_err_plugin_callback_not_fn) + - [FST_ERR_PLUGIN_NOT_VALID](#fst_err_plugin_not_valid) + - [FST_ERR_ROOT_PLG_BOOTED](#fst_err_root_plg_booted) + - [FST_ERR_PARENT_PLUGIN_BOOTED](#fst_err_parent_plugin_booted) + - [FST_ERR_PLUGIN_TIMEOUT](#fst_err_plugin_timeout) + - [FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE](#fst_err_plugin_not_present_in_instance) + - [FST_ERR_VALIDATION](#fst_err_validation) + - [FST_ERR_LISTEN_OPTIONS_INVALID](#fst_err_listen_options_invalid) + ### Error Handling In Node.js @@ -128,401 +217,86 @@ fastify.listen({ port: 3000 }, function (err, address) { }) ``` -#### FST_ERR_NOT_FOUND - - -404 Not Found. - -#### FST_ERR_OPTIONS_NOT_OBJ - - -Fastify options must be an object. - -#### FST_ERR_QSP_NOT_FN - - -QueryStringParser option should be a function. - -#### FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN - - -SchemaController.bucket option should be a function. - -#### FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN - - -SchemaErrorFormatter option should be a non async function. - -#### FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ - - -ajv.customOptions option should be an object. - -#### FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR - - -ajv.plugins option should be an array. - -#### FST_ERR_VERSION_CONSTRAINT_NOT_STR - - -Version constraint should be a string. - -#### FST_ERR_CTP_ALREADY_PRESENT - - -The parser for this content type was already registered. - -#### FST_ERR_CTP_INVALID_TYPE - - -The `Content-Type` should be a string. - -#### FST_ERR_CTP_EMPTY_TYPE - - -The content type cannot be an empty string. - -#### FST_ERR_CTP_INVALID_HANDLER - - -An invalid handler was passed for the content type. - -#### FST_ERR_CTP_INVALID_PARSE_TYPE - - -The provided parse type is not supported. Accepted values are `string` or -`buffer`. - -#### FST_ERR_CTP_BODY_TOO_LARGE - - -The request body is larger than the provided limit. - -This setting can be defined in the Fastify server instance: -[`bodyLimit`](./Server.md#bodylimit) - -#### FST_ERR_CTP_INVALID_MEDIA_TYPE - - -The received media type is not supported (i.e. there is no suitable -`Content-Type` parser for it). - -#### FST_ERR_CTP_INVALID_CONTENT_LENGTH - - -Request body size did not match `Content-Length`. - -#### FST_ERR_CTP_EMPTY_JSON_BODY - - -Body cannot be empty when content-type is set to `application/json`. - -#### FST_ERR_CTP_INSTANCE_ALREADY_STARTED - - -Fastify is already started. - -#### FST_ERR_INSTANCE_ALREADY_LISTENING - - -Fastify instance is already listening. - -#### FST_ERR_DEC_ALREADY_PRESENT - - -A decorator with the same name is already registered. - -#### FST_ERR_DEC_DEPENDENCY_INVALID_TYPE - - -The dependencies of decorator must be of type `Array`. - -#### FST_ERR_DEC_MISSING_DEPENDENCY - - -The decorator cannot be registered due to a missing dependency. - -#### FST_ERR_DEC_AFTER_START - - -The decorator cannot be added after start. - -#### FST_ERR_HOOK_INVALID_TYPE - - -The hook name must be a string. - -#### FST_ERR_HOOK_INVALID_HANDLER - - -The hook callback must be a function. - -#### FST_ERR_HOOK_INVALID_ASYNC_HANDLER - - -Async function has too many arguments. Async hooks should not use the `done` argument. - -#### FST_ERR_HOOK_NOT_SUPPORTED - - -The hook is not supported. - -#### FST_ERR_MISSING_MIDDLEWARE - - -You must register a plugin for handling middlewares, -visit [`Middleware`](./Middleware.md) for more info. - -#### FST_ERR_HOOK_TIMEOUT - - -A callback for a hook timed out - -#### FST_ERR_LOG_INVALID_DESTINATION - - -The logger accepts either a `'stream'` or a `'file'` as the destination. - -#### FST_ERR_LOG_INVALID_LOGGER - - -The logger should have all these methods: `'info'`, `'error'`, -`'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`. - -#### FST_ERR_REP_INVALID_PAYLOAD_TYPE - - -Reply payload can be either a `string` or a `Buffer`. - -#### FST_ERR_REP_ALREADY_SENT - - -A response was already sent. - -#### FST_ERR_REP_SENT_VALUE - - -The only possible value for `reply.sent` is `true`. - -#### FST_ERR_SEND_INSIDE_ONERR - - -You cannot use `send` inside the `onError` hook. - -#### FST_ERR_SEND_UNDEFINED_ERR - - -Undefined error has occurred. - -#### FST_ERR_BAD_STATUS_CODE - - -Called `reply` with an invalid status code. - -#### FST_ERR_BAD_TRAILER_NAME - - -Called `reply.trailer` with an invalid header name. - -#### FST_ERR_BAD_TRAILER_VALUE - - -Called `reply.trailer` with an invalid type. Expected a function. - -#### FST_ERR_FAILED_ERROR_SERIALIZATION - - -Failed to serialize an error. - -#### FST_ERR_MISSING_SERIALIZATION_FN - - -Missing serialization function. - -#### FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN - - -Missing serialization function. - -#### FST_ERR_REQ_INVALID_VALIDATION_INVOCATION - - -Invalid validation invocation. Missing validation function for -HTTP part nor schema provided. - -#### FST_ERR_SCH_MISSING_ID - - -The schema provided does not have `$id` property. - -#### FST_ERR_SCH_ALREADY_PRESENT - - -A schema with the same `$id` already exists. - -#### FST_ERR_SCH_CONTENT_MISSING_SCHEMA - - -A schema is missing for the corresponding content type. - -#### FST_ERR_SCH_DUPLICATE - - -Schema with the same `$id` already present! - -#### FST_ERR_SCH_VALIDATION_BUILD - - -The JSON schema provided for validation to a route is not valid. - -#### FST_ERR_SCH_SERIALIZATION_BUILD - - -The JSON schema provided for serialization of a route response is not valid. - -#### FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX - - -Response schemas should be nested under a valid status code (2XX). - -#### FST_ERR_HTTP2_INVALID_VERSION - - -HTTP2 is available only from node >= 8.8.1. - -#### FST_ERR_INIT_OPTS_INVALID - - -Invalid initialization options. - -#### FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE - - -Cannot set forceCloseConnections to `idle` as your HTTP server -does not support `closeIdleConnections` method. - -#### FST_ERR_DUPLICATED_ROUTE - - -The HTTP method already has a registered controller for that URL - -#### FST_ERR_BAD_URL - - -The router received an invalid url. - -#### FST_ERR_ASYNC_CONSTRAINT - - -The router received an error when using asynchronous constraints. - -#### FST_ERR_DEFAULT_ROUTE_INVALID_TYPE - - -The `defaultRoute` type should be a function. - -#### FST_ERR_INVALID_URL - - -URL must be a string. - -#### FST_ERR_ROUTE_OPTIONS_NOT_OBJ - - -Options for the route must be an object. - -#### FST_ERR_ROUTE_DUPLICATED_HANDLER - - -Duplicate handler for the route is not allowed. - -#### FST_ERR_ROUTE_HANDLER_NOT_FN - - -Handler for the route must be a function. - -#### FST_ERR_ROUTE_MISSING_HANDLER - - -Missing handler function for the route. - -#### FST_ERR_ROUTE_METHOD_INVALID - - -Method is not a valid value. - -#### FST_ERR_ROUTE_METHOD_NOT_SUPPORTED - - -Method is not supported for the route. - -#### FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED - - -Body validation schema route is not supported. - -#### FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT - - -BodyLimit option must be an integer. - -#### FST_ERR_ROUTE_REWRITE_NOT_STR - - -Rewrite url needs to be of type "string". - -#### FST_ERR_REOPENED_CLOSE_SERVER - - -Fastify has already been closed and cannot be reopened. - -#### FST_ERR_REOPENED_SERVER - - -Fastify is already listening. - -#### FST_ERR_PLUGIN_VERSION_MISMATCH - - -Installed Fastify plugin mismatched expected version. - -#### FST_ERR_PLUGIN_CALLBACK_NOT_FN - - -Callback for a hook is not a function (mapped directly from `avvio`) - -#### FST_ERR_PLUGIN_NOT_VALID - - -Plugin must be a function or a promise. - -#### FST_ERR_ROOT_PLG_BOOTED - - -Root plugin has already booted (mapped directly from `avvio`) - -#### FST_ERR_PARENT_PLUGIN_BOOTED - - -Impossible to load plugin because the parent (mapped directly from `avvio`) - -#### FST_ERR_PLUGIN_TIMEOUT - - -Plugin did not start in time. Default timeout (in milliseconds): `10000` - -#### FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE - - -The decorator is not present in the instance. - -#### FST_ERR_VALIDATION - - -The Request failed the payload validation. - -#### FST_ERR_LISTEN_OPTIONS_INVALID - +Below is a table with all the error codes that Fastify uses. + +| Code | Description | How to solve | Discussion | +|------|-------------|--------------|------------| +| FST_ERR_NOT_FOUND | 404 Not Found | - | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_OPTIONS_NOT_OBJ | Fastify options wrongly specified. | Fastify options should be an object. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_QSP_NOT_FN | QueryStringParser wrongly specified. | QueryStringParser option should be a function. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN | SchemaController.bucket wrongly specified. | SchemaController.bucket option should be a function. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN | SchemaErrorFormatter option wrongly specified. | SchemaErrorFormatter option should be a non async function. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ | ajv.customOptions wrongly specified. | ajv.customOptions option should be an object. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR | ajv.plugins option wrongly specified. | ajv.plugins option should be an array. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_VERSION_CONSTRAINT_NOT_STR | Version constraint wrongly specified. | Version constraint should be a string. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_CTP_ALREADY_PRESENT | The parser for this content type was already registered. | Use a different content type or delete the already registered parser. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_CTP_INVALID_TYPE | `Content-Type` wrongly specified | The `Content-Type` should be a string. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_CTP_EMPTY_TYPE | `Content-Type` is an empty string. | `Content-Type` cannot be an empty string. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_CTP_INVALID_HANDLER | Invalid handler for the content type. | Use a different handler. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_CTP_INVALID_PARSE_TYPE | The provided parse type is not supported. | Accepted values are string or buffer. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_CTP_BODY_TOO_LARGE | The request body is larger than the provided limit. | Increase the limit in the Fastify server instance setting: [bodyLimit](./Server.md#bodylimit) | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_CTP_INVALID_MEDIA_TYPE | The received media type is not supported (i.e. there is no suitable `Content-Type` parser for it). | Use a different content type. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_CTP_INVALID_CONTENT_LENGTH | Request body size did not match Content-Length. | Check the request body size and the Content-Length header. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_CTP_EMPTY_JSON_BODY | Body cannot be empty when content-type is set to application/json. | Check the request body. | [#1253](https://github.com/fastify/fastify/pull/1253) | +| FST_ERR_CTP_INSTANCE_ALREADY_STARTED | Fastify is already started. | - | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_INSTANCE_ALREADY_LISTENING | Fastify instance is already listening. | - | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_DEC_ALREADY_PRESENT | A decorator with the same name is already registered. | Use a different decorator name. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_DEC_DEPENDENCY_INVALID_TYPE | The dependencies of decorator must be of type `Array`. | Use an array for the dependencies. | [#3090](https://github.com/fastify/fastify/pull/3090) | +| FST_ERR_DEC_MISSING_DEPENDENCY | The decorator cannot be registered due to a missing dependency. | Register the missing dependency. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_DEC_AFTER_START | The decorator cannot be added after start. | Add the decorator before starting the server. | [#2128](https://github.com/fastify/fastify/pull/2128) | +| FST_ERR_HOOK_INVALID_TYPE | The hook name must be a string. | Use a string for the hook name. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_HOOK_INVALID_HANDLER | The hook callback must be a function. | Use a function for the hook callback. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_HOOK_INVALID_ASYNC_HANDLER | Async function has too many arguments. Async hooks should not use the `done` argument. | Remove the `done` argument from the async hook. | [#4367](https://github.com/fastify/fastify/pull/4367) | +| FST_ERR_HOOK_NOT_SUPPORTED | The hook is not supported. | Use a supported hook. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_MISSING_MIDDLEWARE | You must register a plugin for handling middlewares, visit [`Middleware`](./Middleware.md) for more info. | Register a plugin for handling middlewares. | [#2014](https://github.com/fastify/fastify/pull/2014) | +| FST_ERR_HOOK_TIMEOUT | A callback for a hook timed out. | Increase the timeout for the hook. | [#3106](https://github.com/fastify/fastify/pull/3106) | +| FST_ERR_LOG_INVALID_DESTINATION | The logger does not accept the specified destination. | Use a `'stream'` or a `'file'` as the destination. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_LOG_INVALID_LOGGER | The logger should have all these methods: `'info'`, `'error'`, `'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`. | Use a logger with all the required methods. | [#4520](https://github.com/fastify/fastify/pull/4520) | +| FST_ERR_REP_INVALID_PAYLOAD_TYPE | Reply payload can be either a `string` or a `Buffer`. | Use a `string` or a `Buffer` for the payload. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_REP_ALREADY_SENT | A response was already sent. | - | [#1336](https://github.com/fastify/fastify/pull/1336) | +| FST_ERR_REP_SENT_VALUE | The only possible value for `reply.sent` is `true`. | - | [#1336](https://github.com/fastify/fastify/pull/1336) | +| FST_ERR_SEND_INSIDE_ONERR | You cannot use `send` inside the `onError` hook. | - | [#1348](https://github.com/fastify/fastify/pull/1348) | +| FST_ERR_SEND_UNDEFINED_ERR | Undefined error has occurred. | - | [#2074](https://github.com/fastify/fastify/pull/2074) | +| FST_ERR_BAD_STATUS_CODE | The status code is not valid. | Use a valid status code. | [#2082](https://github.com/fastify/fastify/pull/2082) | +| FST_ERR_BAD_TRAILER_NAME | Called `reply.trailer` with an invalid header name. | Use a valid header name. | [#3794](https://github.com/fastify/fastify/pull/3794) | +| FST_ERR_BAD_TRAILER_VALUE | Called `reply.trailer` with an invalid type. Expected a function. | Use a function. | [#3794](https://github.com/fastify/fastify/pull/3794) | +| FST_ERR_FAILED_ERROR_SERIALIZATION | Failed to serialize an error. | - | [#4601](https://github.com/fastify/fastify/pull/4601) | +| FST_ERR_MISSING_SERIALIZATION_FN | Missing serialization function. | Add a serialization function. | [#3970](https://github.com/fastify/fastify/pull/3970) | +| FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN | Missing `Content-Type` serialization function. | Add a serialization function. | [#4264](https://github.com/fastify/fastify/pull/4264) | +| FST_ERR_REQ_INVALID_VALIDATION_INVOCATION | Invalid validation invocation. Missing validation function for HTTP part nor schema provided. | Add a validation function. | [#3970](https://github.com/fastify/fastify/pull/3970) | +| FST_ERR_SCH_MISSING_ID | The schema provided does not have `$id` property. | Add a `$id` property. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_SCH_ALREADY_PRESENT | A schema with the same `$id` already exists. | Use a different `$id`. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_SCH_CONTENT_MISSING_SCHEMA | A schema is missing for the corresponding content type. | Add a schema. | [#4264](https://github.com/fastify/fastify/pull/4264) | +| FST_ERR_SCH_DUPLICATE | Schema with the same attribute already present! | Use a different attribute. | [#1954](https://github.com/fastify/fastify/pull/1954) | +| FST_ERR_SCH_VALIDATION_BUILD | The JSON schema provided for validation to a route is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) | +| FST_ERR_SCH_SERIALIZATION_BUILD | The JSON schema provided for serialization of a route response is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) | +| FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX | Response schemas should be nested under a valid status code (2XX). | Use a valid status code. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_HTTP2_INVALID_VERSION | HTTP2 is available only from node >= 8.8.1. | Use a higher version of node. | [#1346](https://github.com/fastify/fastify/pull/1346) | +| FST_ERR_INIT_OPTS_INVALID | Invalid initialization options. | Use valid initialization options. | [#1471](https://github.com/fastify/fastify/pull/1471) | +| FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE | Cannot set forceCloseConnections to `idle` as your HTTP server does not support `closeIdleConnections` method. | Use a different value for `forceCloseConnections`. | [#3925](https://github.com/fastify/fastify/pull/3925) | +| FST_ERR_DUPLICATED_ROUTE | The HTTP method already has a registered controller for that URL. | Use a different URL or register the controller for another HTTP method. | [#2954](https://github.com/fastify/fastify/pull/2954) | +| FST_ERR_BAD_URL | The router received an invalid url. | Use a valid URL. | [#2106](https://github.com/fastify/fastify/pull/2106) | +| FST_ERR_ASYNC_CONSTRAINT | The router received an error when using asynchronous constraints. | - | [#4323](https://github.com/fastify/fastify/pull/4323) | +| FST_ERR_DEFAULT_ROUTE_INVALID_TYPE | The `defaultRoute` type should be a function. | Use a function for the `defaultRoute`. | [#2733](https://github.com/fastify/fastify/pull/2733) | +| FST_ERR_INVALID_URL | URL must be a string. | Use a string for the URL. | [#3653](https://github.com/fastify/fastify/pull/3653) | +| FST_ERR_ROUTE_OPTIONS_NOT_OBJ | Options for the route must be an object. | Use an object for the route options. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_ROUTE_DUPLICATED_HANDLER | Duplicate handler for the route is not allowed. | Use a different handler. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_ROUTE_HANDLER_NOT_FN | Handler for the route must be a function. | Use a function for the handler. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_ROUTE_MISSING_HANDLER | Missing handler function for the route. | Add a handler function. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_ROUTE_METHOD_INVALID | Method is not a valid value. | Use a valid value for the method. | [#4750](https://github.com/fastify/fastify/pull/4750) | +| FST_ERR_ROUTE_METHOD_NOT_SUPPORTED | Method is not supported for the route. | Use a supported method. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED | Body validation schema route is not supported. | Use a different different method for the route. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT | `bodyLimit` option must be an integer. | Use an integer for the `bodyLimit` option. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_ROUTE_REWRITE_NOT_STR | `rewriteUrl` needs to be of type `string`. | Use a string for the `rewriteUrl`. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_REOPENED_CLOSE_SERVER | Fastify has already been closed and cannot be reopened. | - | [#2415](https://github.com/fastify/fastify/pull/2415) | +| FST_ERR_REOPENED_SERVER | Fastify is already listening. | - | [#2415](https://github.com/fastify/fastify/pull/2415) | +| FST_ERR_PLUGIN_VERSION_MISMATCH | Installed Fastify plugin mismatched expected version. | Use a compatible version of the plugin. | [#2549](https://github.com/fastify/fastify/pull/2549) | +| FST_ERR_PLUGIN_CALLBACK_NOT_FN | Callback for a hook is not a function. | Use a function for the callback. | [#3106](https://github.com/fastify/fastify/pull/3106) | +| FST_ERR_PLUGIN_NOT_VALID | Plugin must be a function or a promise. | Use a function or a promise for the plugin. | [#3106](https://github.com/fastify/fastify/pull/3106) | +| FST_ERR_ROOT_PLG_BOOTED | Root plugin has already booted. | - | [#3106](https://github.com/fastify/fastify/pull/3106) | +| FST_ERR_PARENT_PLUGIN_BOOTED | Impossible to load plugin because the parent (mapped directly from `avvio`) | - | [#3106](https://github.com/fastify/fastify/pull/3106) | +| FST_ERR_PLUGIN_TIMEOUT | Plugin did not start in time. | Increase the timeout for the plugin. | [#3106](https://github.com/fastify/fastify/pull/3106) | +| FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE | The decorator is not present in the instance. | - | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_VALIDATION | The Request failed the payload validation. | Check the request payload. | [#4824](https://github.com/fastify/fastify/pull/4824) | +| FST_ERR_LISTEN_OPTIONS_INVALID | Invalid listen options. | Check the listen options. | [#4886](https://github.com/fastify/fastify/pull/4886) | -Invalid listen options. diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 34a0fab0e1b..35ef423510c 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -1,6 +1,30 @@

Fastify

+**Table of contents** +- [Warnings](#warnings) + - [Warnings In Fastify](#warnings-in-fastify) + - [Fastify Warning Codes](#fastify-warning-codes) + - [FSTWRN001](#FSTWRN001) + - [FSTWRN002](#FSTWRN002) + - [Fastify Deprecation Codes](#fastify-deprecation-codes) + - [FSTDEP005](#FSTDEP005) + - [FSTDEP006](#FSTDEP006) + - [FSTDEP007](#FSTDEP007) + - [FSTDEP008](#FSTDEP008) + - [FSTDEP009](#FSTDEP009) + - [FSTDEP010](#FSTDEP010) + - [FSTDEP011](#FSTDEP011) + - [FSTDEP012](#FSTDEP012) + - [FSTDEP013](#FSTDEP013) + - [FSTDEP014](#FSTDEP014) + - [FSTDEP015](#FSTDEP015) + - [FSTDEP016](#FSTDEP016) + - [FSTDEP017](#FSTDEP017) + - [FSTDEP018](#FSTDEP018) + - [FSTDEP019](#FSTDEP019) + + ## Warnings ### Warnings In Fastify @@ -21,8 +45,8 @@ Only experienced users should consider disabling warnings. | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | -| **FSTWRN001** | The specified schema for a route is missing. This may indicate the schema is not well specified. | Check the schema for the route. | [#4647](https://github.com/fastify/fastify/pull/4647) | -| **FSTWRN002** | The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`. | Do not mix async and callback style. | [#5139](https://github.com/fastify/fastify/pull/5139) | +| FSTWRN001 | The specified schema for a route is missing. This may indicate the schema is not well specified. | Check the schema for the route. | [#4647](https://github.com/fastify/fastify/pull/4647) | +| FSTWRN002 | The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`. | Do not mix async and callback style. | [#5139](https://github.com/fastify/fastify/pull/5139) | ### Fastify Deprecation Codes @@ -36,18 +60,18 @@ Deprecation codes are further supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | -| **FSTDEP005** | You are accessing the deprecated `request.connection` property. | Use `request.socket`. | [#2594](https://github.com/fastify/fastify/pull/2594) | -| **FSTDEP006** | You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. | Do not use Arrays/Objects as values when decorating Request/Reply. | [#2688](https://github.com/fastify/fastify/pull/2688) | -| **FSTDEP007** | You are trying to set a HEAD route using `exposeHeadRoute` route flag when a sibling route is already set. | Remove `exposeHeadRoutes` or explicitly set `exposeHeadRoutes` to `false` | [#2700](https://github.com/fastify/fastify/pull/2700) | -| **FSTDEP008** | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | -| **FSTDEP009** | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | -| **FSTDEP010** | Modifying the `reply.sent` property is deprecated. | Use the `reply.hijack()` method. | [#3140](https://github.com/fastify/fastify/pull/3140) | -| **FSTDEP011** | Variadic listen method is deprecated. | Use `.listen(optionsObject)`. | [#3712](https://github.com/fastify/fastify/pull/3712) | -| **FSTDEP012** | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) | -| **FSTDEP013** | Direct return of "trailers" function is deprecated. | Use "callback" or "async-await" for return value. | [#4380](https://github.com/fastify/fastify/pull/4380) | -| **FSTDEP014** | You are trying to set/access the default route. This property is deprecated. | Use `setNotFoundHandler` if you want to custom a 404 handler or the wildcard (`*`) to match all routes. | [#4480](https://github.com/fastify/fastify/pull/4480) | -| **FSTDEP015** | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) | -| **FSTDEP016** | You are accessing the deprecated `request.routeConfig` property. | Use `request.routeOptions.config`. | [#4470](https://github.com/fastify/fastify/pull/4470) | -| **FSTDEP017** | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) | -| **FSTDEP018** | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | -| **FSTDEP019** | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | +| FSTDEP005 | You are accessing the deprecated `request.connection` property. | Use `request.socket`. | [#2594](https://github.com/fastify/fastify/pull/2594) | +| FSTDEP006 | You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. | Do not use Arrays/Objects as values when decorating Request/Reply. | [#2688](https://github.com/fastify/fastify/pull/2688) | +| FSTDEP007 | You are trying to set a HEAD route using `exposeHeadRoute` route flag when a sibling route is already set. | Remove `exposeHeadRoutes` or explicitly set `exposeHeadRoutes` to `false` | [#2700](https://github.com/fastify/fastify/pull/2700) | +| FSTDEP008 | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | +| FSTDEP009 | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | +| FSTDEP010 | Modifying the `reply.sent` property is deprecated. | Use the `reply.hijack()` method. | [#3140](https://github.com/fastify/fastify/pull/3140) | +| FSTDEP011 | Variadic listen method is deprecated. | Use `.listen(optionsObject)`. | [#3712](https://github.com/fastify/fastify/pull/3712) | +| FSTDEP012 | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) | +| FSTDEP013 | Direct return of "trailers" function is deprecated. | Use "callback" or "async-await" for return value. | [#4380](https://github.com/fastify/fastify/pull/4380) | +| FSTDEP014 | You are trying to set/access the default route. This property is deprecated. | Use `setNotFoundHandler` if you want to custom a 404 handler or the wildcard (`*`) to match all routes. | [#4480](https://github.com/fastify/fastify/pull/4480) | +| FSTDEP015 | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| FSTDEP016 | You are accessing the deprecated `request.routeConfig` property. | Use `request.routeOptions.config`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| FSTDEP017 | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index dbd1789689a..5f94ab61ab0 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -824,7 +824,7 @@ test('Ensure that all errors are in Errors.md documented', t => { const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { - t.ok(errorsMd.includes(`#### ${key}\n`), key) + t.ok(errorsMd.includes(`${key.toUpperCase()}`), key) } } }) @@ -833,7 +833,7 @@ test('Ensure that non-existing errors are not in Errors.md documented', t => { t.plan(78) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') - const matchRE = /#### ([0-9a-zA-Z_]+)\n/g + const matchRE = /([0-9a-zA-Z_]+)<\/a>/g const matches = errorsMd.matchAll(matchRE) const exportedKeys = Object.keys(errors) From 1891f243ab8666ef926218691135eb032008632a Mon Sep 17 00:00:00 2001 From: Rj Manhas <117674421+RjManhas@users.noreply.github.com> Date: Sat, 25 Nov 2023 03:24:51 -0700 Subject: [PATCH 0518/1295] docs(reference/hooks): add information about prehandler (#5163) * add infomation about the beforehandler in the ref page for hooks * Update docs/Reference/Hooks.md Co-authored-by: Manuel Spigolon * Apply suggestions from code review * Update docs/Reference/Hooks.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> --------- Co-authored-by: Manuel Spigolon Co-authored-by: Aras Abbasi Co-authored-by: Frazer Smith Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> --- docs/Reference/Hooks.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 34b53c696c7..5b270afa890 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -130,6 +130,10 @@ fastify.addHook('preValidation', async (request, reply) => { ``` ### preHandler + +The `preHandler` hook allows you to specify a function that is executed before +a routes's handler. + ```js fastify.addHook('preHandler', (request, reply, done) => { // some code From f808f966d0329dcee51f71d175e65f97d9aeb979 Mon Sep 17 00:00:00 2001 From: Munif Tanjim Date: Sat, 2 Dec 2023 21:33:16 +0600 Subject: [PATCH 0519/1295] fix: type FastifyInstance['route'] and RouteShorthandMethod (#5155) --- test/types/logger.test-d.ts | 14 ++++++++++++++ types/instance.d.ts | 20 ++++++++++---------- types/route.d.ts | 7 ++++--- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index dfbefdf3a1e..d2e8a605738 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -74,6 +74,20 @@ P.Logger expectType(serverWithPino.log) +serverWithPino.route({ + method: 'GET', + url: '/', + handler (request) { + expectType(this.log) + expectType(request.log) + } +}) + +serverWithPino.get('/', function (request) { + expectType(this.log) + expectType(request.log) +}) + const serverWithLogOptions = fastify< Server, IncomingMessage, diff --git a/types/instance.d.ts b/types/instance.d.ts index 3100e756ad6..5d0fe611caf 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -202,16 +202,16 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, const SchemaCompiler extends FastifySchema = FastifySchema, - >(opts: RouteOptions): FastifyInstance; - - get: RouteShorthandMethod; - head: RouteShorthandMethod; - post: RouteShorthandMethod; - put: RouteShorthandMethod; - delete: RouteShorthandMethod; - options: RouteShorthandMethod; - patch: RouteShorthandMethod; - all: RouteShorthandMethod; + >(opts: RouteOptions): FastifyInstance; + + get: RouteShorthandMethod; + head: RouteShorthandMethod; + post: RouteShorthandMethod; + put: RouteShorthandMethod; + delete: RouteShorthandMethod; + options: RouteShorthandMethod; + patch: RouteShorthandMethod; + all: RouteShorthandMethod; hasRoute< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, diff --git a/types/route.d.ts b/types/route.d.ts index 68ab5b7a425..b79bb18d342 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -125,17 +125,18 @@ export interface RouteShorthandMethod< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger > { - ( + ( path: string, opts: RouteShorthandOptions, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, handler: RouteHandlerMethod ): FastifyInstance; - ( + ( path: string, opts: RouteShorthandOptionsWithHandler ): FastifyInstance; From 7a134b5b884079c50eeba228e9887d005d8b6206 Mon Sep 17 00:00:00 2001 From: bngarren Date: Sun, 3 Dec 2023 20:08:06 +0000 Subject: [PATCH 0520/1295] Fix: small typo in docs --- docs/Reference/Request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 63cd7b4cbab..224e2e782e4 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -161,7 +161,7 @@ for more information on how to compile validation function. This function will compile a validation schema and return a function that can be used to validate data. The function returned (a.k.a. _validation function_) is compiled -by using the provided [`SchemaControler#ValidationCompiler`](./Server.md#schema-controller). +by using the provided [`SchemaController#ValidationCompiler`](./Server.md#schema-controller). A `WeakMap` is used to cached this, reducing compilation calls. The optional parameter `httpPart`, if provided, is forwarded directly From b1b44fcbccaf8ac3791c2b0c090f5acfd7bb6fbf Mon Sep 17 00:00:00 2001 From: "Willow (GHOST)" Date: Sun, 3 Dec 2023 20:27:51 +0000 Subject: [PATCH 0521/1295] chore: gitpodify (#5168) --- .gitpod.yml | 10 ++++++++++ .npmignore | 1 + README.md | 1 + docs/Guides/Contributing.md | 2 ++ 4 files changed, 14 insertions(+) create mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000000..93bdc1a22b8 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,10 @@ +# Gitpod Configuration File +# SEE https://www.gitpod.io/docs/references/gitpod-yml + +tasks: + - init: npm install + name: Install Dependencies + +vscode: + extensions: + - "dbaeumer.vscode-eslint" diff --git a/.npmignore b/.npmignore index 33caf9bf839..87cea5a3cb6 100644 --- a/.npmignore +++ b/.npmignore @@ -8,6 +8,7 @@ tools/ CODE_OF_CONDUCT.md CONTRIBUTING.md .clinic +.gitpod.yml # test certification test/https/fastify.cert diff --git a/README.md b/README.md index 194ddb73e2d..ceb9c7f0366 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ downloads](https://img.shields.io/npm/dm/fastify.svg?style=flat)](https://www.np [![Security Responsible Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yellow.svg)](https://github.com/fastify/fastify/blob/main/SECURITY.md) [![Discord](https://img.shields.io/discord/725613461949906985)](https://discord.gg/fastify) +[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=blue)](https://gitpod.io/#https://github.com/fastify/fastify)
diff --git a/docs/Guides/Contributing.md b/docs/Guides/Contributing.md index 2dfc80908eb..334e9f990e9 100644 --- a/docs/Guides/Contributing.md +++ b/docs/Guides/Contributing.md @@ -82,6 +82,8 @@ that automatically "correct" code and documentation do not follow a style that conforms to the styles this project uses. Notably, this project uses [StandardJS](https://standardjs.com) for code formatting. +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/fastify/fastify) + ### Using Visual Studio Code From d61f3847368812e875e2f1abb55fe4f36ef5c11b Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Mon, 4 Dec 2023 07:39:16 +1000 Subject: [PATCH 0522/1295] docs(ecosystem): Add Apitally (#5175) * Add Apitally to ecosystem page in docs * Update Apitally description * Fix line length --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 3cec7407079..c0001d0d0a3 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -201,6 +201,9 @@ section. Beautiful OpenAPI/Swagger API references for Fastify - [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs) SeaweedFS for Fastify +- [`apitally`](https://github.com/apitally/nodejs-client) Fastify plugin to + integrate with [Apitally](https://apitally.io), a simple API monitoring & + API key management solution. - [`apollo-server-fastify`](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify) Run an [Apollo Server](https://github.com/apollographql/apollo-server) to serve GraphQL with Fastify. From 84ad944d7324fad338a09043fbb82f041a02db98 Mon Sep 17 00:00:00 2001 From: Valentin Agachi Date: Sun, 3 Dec 2023 22:58:00 +0100 Subject: [PATCH 0523/1295] Update reply.context deprecation warning (#5179) --- lib/warnings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/warnings.js b/lib/warnings.js index 5ec503a6234..5ce1ad452ed 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -51,7 +51,7 @@ warning.createDeprecation('FSTDEP017', 'You are accessing the deprecated "reques warning.createDeprecation('FSTDEP018', 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.') -warning.createDeprecation('FSTDEP019', 'reply.context property access is deprecated. Please use "reply.routeOptions.config" or "reply.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.') +warning.createDeprecation('FSTDEP019', 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.') warning.create('FastifyWarning', 'FSTWRN001', 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', { unlimited: true }) From 5d29f5c0fca40403bece7fbc84eda7cc33d9f73e Mon Sep 17 00:00:00 2001 From: Fredrik Johansen Date: Tue, 5 Dec 2023 20:29:50 +0100 Subject: [PATCH 0524/1295] docs(ecosystem): adds @blastorg/fastify/aws-dynamodb-cache to community plugins list (#5158) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index c0001d0d0a3..fdd4c2d418c 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -147,6 +147,8 @@ section. - [`@applicazza/fastify-nextjs`](https://github.com/applicazza/fastify-nextjs) Alternate Fastify and Next.js integration. +- [`@blastorg/fastify-aws-dynamodb-cache`](https://github.com/blastorg/fastify-aws-dynamodb-cache) + A plugin to help with caching API responses using AWS DynamoDB. - [`@clerk/fastify`](https://github.com/clerkinc/javascript/tree/main/packages/fastify) Add authentication and user management to your Fastify application with Clerk. - [`@coobaha/typed-fastify`](https://github.com/Coobaha/typed-fastify) Strongly From f2be4c581abc3be6d762097249209807de91700b Mon Sep 17 00:00:00 2001 From: Tarun Chauhan Date: Fri, 8 Dec 2023 04:24:33 +0530 Subject: [PATCH 0525/1295] docs: update preHandler hook example (#5189) --- docs/Reference/Hooks.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 5b270afa890..0d3cb9df709 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -350,9 +350,11 @@ fastify.addHook('onRequest', (request, reply, done) => { // Works with async functions too fastify.addHook('preHandler', async (request, reply) => { - await something() - reply.send({ hello: 'world' }) + setTimeout(() => { + reply.send({ hello: 'from prehandler' }) + }) return reply // mandatory, so the request is not executed further +// Commenting the line above will allow the hooks to continue and fail with FST_ERR_REP_ALREADY_SENT }) ``` From 5e23534aa93cbf6cd9819707bcaaf0e841c6ffc2 Mon Sep 17 00:00:00 2001 From: Dmitry Kudryavtsev Date: Fri, 8 Dec 2023 19:22:42 +0100 Subject: [PATCH 0526/1295] types: added http header types to reply (#5046) * added http header types to reply * fix lint * patch accordingly * update PR * fix --------- Co-authored-by: Uzlopak --- test/types/reply.test-d.ts | 31 +++++++++++++++++++++++++------ types/reply.d.ts | 17 +++++++---------- types/utils.d.ts | 10 ++++++++++ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 837d1ddfa20..74f17e3a5ac 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -22,12 +22,12 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType(reply.statusCode) expectType(reply.sent) expectType<((payload?: unknown) => FastifyReply)>(reply.send) - expectType<(key: string, value: any) => FastifyReply>(reply.header) - expectType<(values: {[key: string]: any}) => FastifyReply>(reply.headers) - expectType<(key: string) => number | string | string[] | undefined>(reply.getHeader) - expectType<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders) - expectType<(key: string) => FastifyReply>(reply.removeHeader) - expectType<(key: string) => boolean>(reply.hasHeader) + expectAssignable<(key: string, value: any) => FastifyReply>(reply.header) + expectAssignable<(values: {[key: string]: any}) => FastifyReply>(reply.headers) + expectAssignable<(key: string) => number | string | string[] | undefined>(reply.getHeader) + expectAssignable<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders) + expectAssignable<(key: string) => FastifyReply>(reply.removeHeader) + expectAssignable<(key: string) => boolean>(reply.hasHeader) expectType<{(statusCode: number, url: string): FastifyReply; (url: string): FastifyReply }>(reply.redirect) expectType<() => FastifyReply>(reply.hijack) expectType<() => void>(reply.callNotFound) @@ -162,3 +162,22 @@ server.get('get-invalid-http-codes-reply-error', async fu 999: false }) }) + +const httpHeaderHandler: RouteHandlerMethod = function (_request, reply) { + // accept is a header provided by @types/node + reply.getHeader('accept') + reply.getHeaders().accept // eslint-disable-line no-unused-expressions + reply.hasHeader('accept') + reply.header('accept', 'test') + reply.headers({ accept: 'test' }) + reply.removeHeader('accept') + + // x-fastify-test is not a header provided by @types/node + // and should not result in a typing error + reply.getHeader('x-fastify-test') + reply.getHeaders()['x-fastify-test'] // eslint-disable-line no-unused-expressions + reply.hasHeader('x-fastify-test') + reply.header('x-fastify-test', 'test') + reply.headers({ 'x-fastify-test': 'test' }) + reply.removeHeader('x-fastify-test') +} diff --git a/types/reply.d.ts b/types/reply.d.ts index 3c81d739715..ee9091e475e 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -6,7 +6,7 @@ import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider' -import { CodeToReplyKey, ContextConfigDefault, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils' +import { CodeToReplyKey, ContextConfigDefault, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes, HttpHeader } from './utils' export interface ReplyGenericInterface { Reply?: ReplyDefault; @@ -48,15 +48,12 @@ export interface FastifyReply< statusCode: number; sent: boolean; send(payload?: ReplyType): FastifyReply; - header(key: string, value: any): FastifyReply; - headers(values: {[key: string]: any}): FastifyReply; - getHeader(key: string): number | string | string[] | undefined; - getHeaders(): { - // Node's `getHeaders()` can return numbers and arrays, so they're included here as possible types. - [key: string]: number | string | string[] | undefined; - }; - removeHeader(key: string): FastifyReply; - hasHeader(key: string): boolean; + header(key: HttpHeader, value: any): FastifyReply; + headers(values: Partial>): FastifyReply; + getHeader(key: HttpHeader): number | string | string[] | undefined; + getHeaders(): Record; + removeHeader(key: HttpHeader): FastifyReply; + hasHeader(key: HttpHeader): boolean; // Note: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param redirect(statusCode: number, url: string): FastifyReply; redirect(url: string): FastifyReply; diff --git a/types/utils.d.ts b/types/utils.d.ts index 7edbf347ede..139461f0daa 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -78,3 +78,13 @@ export type RecordKeysToLowercase = Input extends Record ]: Input[Key]; } : Input; + +type OmitIndexSignature = { + [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; +}; + +/** + * HTTP header strings + * Use this type only for input values, not for output values. + */ +export type HttpHeader = keyof OmitIndexSignature | (string & Record); From 49272d1fd660c518ee19719f663a7ada1c975917 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 8 Dec 2023 20:17:17 +0100 Subject: [PATCH 0527/1295] test: add tests for TOC of errors.md (#5194) --- test/internals/errors.test.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 5f94ab61ab0..4ab0709b207 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -817,6 +817,31 @@ test('FST_ERR_LISTEN_OPTIONS_INVALID', t => { t.ok(error instanceof TypeError) }) +test('Ensure that all errors are in Errors.md TOC', t => { + t.plan(78) + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') + + const exportedKeys = Object.keys(errors) + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + t.ok(errorsMd.includes(` - [${key.toUpperCase()}](#${key.toLowerCase()})`), key) + } + } +}) + +test('Ensure that non-existing errors are not in Errors.md TOC', t => { + t.plan(78) + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') + + const matchRE = / {4}- \[([A-Z0-9_]+)\]\(#[a-z0-9_]+\)/g + const matches = errorsMd.matchAll(matchRE) + const exportedKeys = Object.keys(errors) + + for (const match of matches) { + t.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) + } +}) + test('Ensure that all errors are in Errors.md documented', t => { t.plan(78) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') From b9336a3f3979f0d024a73222e193d1317bafc69a Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 8 Dec 2023 20:27:19 +0100 Subject: [PATCH 0528/1295] ci: pin node 18 to 18.18.2 (#5197) --- .github/workflows/ci.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afd33f4e3a4..0cddb70da04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,7 +109,7 @@ jobs: strategy: matrix: - node-version: [14, 16, 18, 20] + node-version: [14, 16, '18.18.2', 20] os: [macos-latest, ubuntu-latest, windows-latest] exclude: # excludes node 14 on Windows diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index f6efd0c64b6..2a73ea1b7f9 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: - node-version: [16, 18, 20] + node-version: [16, '18.18.2', 20] os: [ubuntu-latest] pnpm-version: [8] # pnpm@8 does not support Node.js 14 so include it separately diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 7db25b827b4..1cb79e878bd 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [16, 18, 20] + node-version: [16, '18.18.2', 20] os: [ubuntu-latest] pnpm-version: [8] # pnpm@8 does not support Node.js 14 so include it separately @@ -53,7 +53,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [14, 16, 18, 20] + node-version: [14, 16, '18.18.2', 20] os: [ubuntu-latest] steps: From 85b00182f024aed51bcb93c3ccb2003b1d4bb8a7 Mon Sep 17 00:00:00 2001 From: Florian De la comble Date: Sat, 9 Dec 2023 08:43:31 +0100 Subject: [PATCH 0529/1295] docs(ecosystem): add http-wizard (#5132) --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index fdd4c2d418c..d1305c82b27 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -662,6 +662,9 @@ middlewares into Fastify plugins Parse XML payload / request body into JS / JSON object. - [`fastify-xray`](https://github.com/jeromemacias/fastify-xray) Fastify plugin for AWS XRay recording. +- [`http-wizard`](https://github.com/flodlc/http-wizard) + Exports a typescript api client for your Fastify api and ensures fullstack type + safety for your project. - [`i18next-http-middleware`](https://github.com/i18next/i18next-http-middleware#fastify-usage) An [i18next](https://www.i18next.com) based i18n (internationalization) middleware to be used with Node.js web frameworks like Express or Fastify and From 6dcd4b692e7288532e567c265341dcb4a19ad924 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Dec 2023 02:44:20 +0000 Subject: [PATCH 0530/1295] chore: Bump actions/github-script from 6 to 7 (#5183) Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/github-script dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint-ecosystem-order.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index 4d9c7d73723..b079aa1fddf 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -23,7 +23,7 @@ jobs: persist-credentials: false - name: Lint Doc - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From e9764f3c004bc5c67441456d0e4612d35988db6c Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sun, 10 Dec 2023 11:28:03 +0100 Subject: [PATCH 0531/1295] ci: fix broken ci by skipping tests if node v > 18.19.0 (#5195) --- .github/workflows/ci.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- test/close-pipelining.test.js | 8 ++++---- test/close.test.js | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cddb70da04..afd33f4e3a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,7 +109,7 @@ jobs: strategy: matrix: - node-version: [14, 16, '18.18.2', 20] + node-version: [14, 16, 18, 20] os: [macos-latest, ubuntu-latest, windows-latest] exclude: # excludes node 14 on Windows diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 2a73ea1b7f9..f6efd0c64b6 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: - node-version: [16, '18.18.2', 20] + node-version: [16, 18, 20] os: [ubuntu-latest] pnpm-version: [8] # pnpm@8 does not support Node.js 14 so include it separately diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 1cb79e878bd..7db25b827b4 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [16, '18.18.2', 20] + node-version: [16, 18, 20] os: [ubuntu-latest] pnpm-version: [8] # pnpm@8 does not support Node.js 14 so include it separately @@ -53,7 +53,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [14, 16, '18.18.2', 20] + node-version: [14, 16, 18, 20] os: [ubuntu-latest] steps: diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index 0a822998659..1c9451314b3 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -36,8 +36,8 @@ test('Should return 503 while closing - pipelining', async t => { await instance.close() }) -const isVgte19 = semver.gte(process.version, '19.0.0') -test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v19.x', { skip: isVgte19 }, async t => { +const isNodeVersionGte1819 = semver.gte(process.version, '18.19.0') +test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v18.19.x', { skip: isNodeVersionGte1819 }, async t => { const fastify = Fastify({ return503OnClosing: false, forceCloseConnections: false @@ -67,8 +67,8 @@ test('Should not return 503 while closing - pipelining - return503OnClosing: fal await instance.close() }) -test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip Node < v19.x', { skip: !isVgte19 }, async t => { - // Since Node v19, we will always invoke server.closeIdleConnections() +test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip Node < v18.19.x', { skip: !isNodeVersionGte1819 }, async t => { + // Since Node v18, we will always invoke server.closeIdleConnections() // therefore our socket will be closed const fastify = Fastify({ return503OnClosing: false, diff --git a/test/close.test.js b/test/close.test.js index 3870639d039..6b8a9e0b610 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -204,8 +204,8 @@ test('Should return error while closing (callback) - injection', t => { }) }) -const isV19plus = semver.gte(process.version, '19.0.0') -test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, t => { +const isNodeVersionGte1819 = semver.gte(process.version, '18.19.0') +test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node >= v18.19.x', { skip: isNodeVersionGte1819 }, t => { const fastify = Fastify({ return503OnClosing: false, forceCloseConnections: false @@ -243,7 +243,7 @@ test('Current opened connection should continue to work after closing and return }) }) -test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, t => { +test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v18.19.x', { skip: !isNodeVersionGte1819 }, t => { t.plan(4) const fastify = Fastify({ return503OnClosing: false, From 49b62f56a35f1a9a958de6aec07cee851ac2aa43 Mon Sep 17 00:00:00 2001 From: AJ Bienz Date: Sun, 10 Dec 2023 06:27:58 -0500 Subject: [PATCH 0532/1295] fix: allow async hooks in `RouteShorthandOptions` without breaking `request` and `reply` types (#5147) --- test/types/hooks.test-d.ts | 125 ++++++++++++++++++++++++- types/hooks.d.ts | 183 +++++++++++++++++++++++++++++++++++++ types/route.d.ts | 43 +++++---- 3 files changed, 328 insertions(+), 23 deletions(-) diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 45bf7219725..bf2ec7d06d7 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -14,7 +14,7 @@ import fastify, { RegisterOptions, RouteOptions } from '../../fastify' -import { RequestPayload, preHandlerAsyncHookHandler } from '../../types/hooks' +import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, preHandlerAsyncHookHandler } from '../../types/hooks' import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' const server = fastify() @@ -392,6 +392,129 @@ server.route({ } }) +server.route({ + method: 'GET', + url: '/', + handler: (request, reply) => { + expectType(request) + expectType(reply) + }, + onRequest: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + onRequestAbort: (request, done) => { + expectType(request) + expectType(done) + }, + preParsing: (request, reply, payload, done) => { + expectType(request) + expectType(reply) + expectType(payload) + expectType<(err?: TError | null | undefined, res?: RequestPayload | undefined) => void>(done) + }, + preValidation: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + preHandler: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + preSerialization: (request, reply, payload, done) => { + expectType(request) + expectType(reply) + expectType(payload) + expectType(done) + }, + onSend: (request, reply, payload, done) => { + expectType(request) + expectType(reply) + expectType(payload) + expectType(done) + }, + onResponse: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + onTimeout: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + onError: (request, reply, error, done) => { + expectType(request) + expectType(reply) + expectType(error) + expectType<() => void>(done) + } +}) + +server.get('/', { + onRequest: async (request, reply) => { + expectType(request) + expectType(reply) + }, + onRequestAbort: async (request, reply) => { + expectType(request) + }, + preParsing: async (request, reply, payload) => { + expectType(request) + expectType(reply) + expectType(payload) + }, + preValidation: async (request, reply) => { + expectType(request) + expectType(reply) + }, + preHandler: async (request, reply) => { + expectType(request) + expectType(reply) + }, + preSerialization: async (request, reply, payload) => { + expectType(request) + expectType(reply) + expectType(payload) + }, + onSend: async (request, reply, payload) => { + expectType(request) + expectType(reply) + expectType(payload) + }, + onResponse: async (request, reply) => { + expectType(request) + expectType(reply) + }, + onTimeout: async (request, reply) => { + expectType(request) + expectType(reply) + }, + onError: async (request, reply, error) => { + expectType(request) + expectType(reply) + expectType(error) + } +}, async (request, reply) => { + expectType(request) + expectType(reply) +}) + +// TODO: Should throw errors +// expectError(server.get('/', { onRequest: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onRequestAbort: async (request, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { preParsing: async (request, reply, payload, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { preValidation: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { preHandler: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { preSerialization: async (request, reply, payload, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onSend: async (request, reply, payload, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onResponse: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onTimeout: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onError: async (request, reply, error, done) => {} }, async (request, reply) => {})) + server.addHook('preClose', function (done) { expectType(this) expectAssignable<(err?: FastifyError) => void>(done) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index d80974ff948..ef824ae3194 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -61,6 +61,24 @@ export interface onRequestAsyncHookHandler< ): Promise; } +// helper type which infers whether onRequestHookHandler or onRequestAsyncHookHandler are +// applicable based on the specified return type. +export type onRequestMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onRequestHookHandler + : onRequestAsyncHookHandler + /** * `preParsing` is the second hook to be executed in the request lifecycle. The previous hook was `onRequest`, the next hook will be `preValidation`. * Notice: in the `preParsing` hook, request.body will always be null, because the body parsing happens before the `preHandler` hook. @@ -102,6 +120,24 @@ export interface preParsingAsyncHookHandler< ): Promise; } +// helper type which infers whether preParsingHookHandler or preParsingAsyncHookHandler are +// applicable based on the specified return type. +export type preParsingMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? preParsingHookHandler + : preParsingAsyncHookHandler + /** * `preValidation` is the third hook to be executed in the request lifecycle. The previous hook was `preParsing`, the next hook will be `preHandler`. */ @@ -140,6 +176,24 @@ export interface preValidationAsyncHookHandler< ): Promise; } +// helper type which infers whether preValidationHookHandler or preValidationAsyncHookHandler are +// applicable based on the specified return type. +export type preValidationMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? preValidationHookHandler + : preValidationAsyncHookHandler + /** * `preHandler` is the fourth hook to be executed in the request lifecycle. The previous hook was `preValidation`, the next hook will be `preSerialization`. */ @@ -178,6 +232,24 @@ export interface preHandlerAsyncHookHandler< ): Promise; } +// helper type which infers whether preHandlerHookHandler or preHandlerAsyncHookHandler are +// applicable based on the specified return type. +export type preHandlerMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? preHandlerHookHandler + : preHandlerAsyncHookHandler + // This is used within the `preSerialization` and `onSend` hook handlers interface DoneFuncWithErrOrRes { (): void; @@ -228,6 +300,25 @@ export interface preSerializationAsyncHookHandler< ): Promise; } +// helper type which infers whether preSerializationHookHandler or preSerializationAsyncHookHandler are +// applicable based on the specified return type. +export type preSerializationMetaHookHandler< + PreSerializationPayload = unknown, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? preSerializationHookHandler + : preSerializationAsyncHookHandler + /** * You can change the payload with the `onSend` hook. It is the sixth hook to be executed in the request lifecycle. The previous hook was `preSerialization`, the next hook will be `onResponse`. * Note: If you change the payload, you may only change it to a string, a Buffer, a stream, or null. @@ -271,6 +362,25 @@ export interface onSendAsyncHookHandler< ): Promise; } +// helper type which infers whether onSendHookHandler or onSendAsyncHookHandler are +// applicable based on the specified return type. +export type onSendMetaHookHandler< + OnSendPayload = unknown, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onSendHookHandler + : onSendAsyncHookHandler + /** * `onResponse` is the seventh and last hook in the request hook lifecycle. The previous hook was `onSend`, there is no next hook. * The onResponse hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example to gather statistics. @@ -310,6 +420,24 @@ export interface onResponseAsyncHookHandler< ): Promise; } +// helper type which infers whether onResponseHookHandler or onResponseAsyncHookHandler are +// applicable based on the specified return type. +export type onResponseMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onResponseHookHandler + : onResponseAsyncHookHandler + /** * `onTimeout` is useful if you need to monitor the request timed out in your service. (if the `connectionTimeout` property is set on the fastify instance) * The onTimeout hook is executed when a request is timed out and the http socket has been hanged up. Therefore you will not be able to send data to the client. @@ -349,6 +477,24 @@ export interface onTimeoutAsyncHookHandler< ): Promise; } +// helper type which infers whether onTimeoutHookHandler or onTimeoutAsyncHookHandler are +// applicable based on the specified return type. +export type onTimeoutMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onTimeoutHookHandler + : onTimeoutAsyncHookHandler + /** * This hook is useful if you need to do some custom error logging or add some specific header in case of error. * It is not intended for changing the error, and calling reply.send will throw an exception. @@ -394,6 +540,25 @@ export interface onErrorAsyncHookHandler< ): Promise; } +// helper type which infers whether onErrorHookHandler or onErrorAsyncHookHandler are +// applicable based on the specified return type. +export type onErrorMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + TError extends Error = FastifyError, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onErrorHookHandler + : onErrorAsyncHookHandler + /** * `onRequestAbort` is useful if you need to monitor the if the client aborts the request (if the `request.raw.aborted` property is set to `true`). * The `onRequestAbort` hook is executed when a client closes the connection before the entire request has been received. Therefore, you will not be able to send data to the client. @@ -432,6 +597,24 @@ export interface onRequestAbortAsyncHookHandler< ): Promise; } +// helper type which infers whether onRequestAbortHookHandler or onRequestAbortHookHandler are +// applicable based on the specified return type. +export type onRequestAbortMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onRequestAbortHookHandler + : onRequestAbortAsyncHookHandler + export type LifecycleHook = 'onRequest' | 'preParsing' | 'preValidation' diff --git a/types/route.d.ts b/types/route.d.ts index b79bb18d342..3ee4079071c 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -1,6 +1,6 @@ import { FastifyError } from '@fastify/error' import { FastifyRequestContext } from './context' -import { onErrorHookHandler, onRequestAbortHookHandler, onRequestHookHandler, onResponseHookHandler, onSendHookHandler, onTimeoutHookHandler, preHandlerHookHandler, preParsingHookHandler, preSerializationHookHandler, preValidationHookHandler } from './hooks' +import { onErrorMetaHookHandler, onRequestAbortMetaHookHandler, onRequestMetaHookHandler, onResponseMetaHookHandler, onSendMetaHookHandler, onTimeoutMetaHookHandler, preHandlerMetaHookHandler, preParsingMetaHookHandler, preSerializationMetaHookHandler, preValidationMetaHookHandler } from './hooks' import { FastifyInstance } from './instance' import { FastifyBaseLogger, FastifyChildLoggerFactory, LogLevel } from './logger' import { FastifyReply, ReplyGenericInterface } from './reply' @@ -60,28 +60,27 @@ export interface RouteShorthandOptions< schemaErrorFormatter?: SchemaErrorFormatter; // hooks - onRequest?: onRequestHookHandler - | onRequestHookHandler[]; - preParsing?: preParsingHookHandler - | preParsingHookHandler[]; - preValidation?: preValidationHookHandler - | preValidationHookHandler[]; - preHandler?: preHandlerHookHandler - | preHandlerHookHandler[]; - preSerialization?: preSerializationHookHandler - | preSerializationHookHandler[]; - onSend?: onSendHookHandler - | onSendHookHandler[]; - onResponse?: onResponseHookHandler - | onResponseHookHandler[]; - onTimeout?: onTimeoutHookHandler - | onTimeoutHookHandler[]; - onError?: onErrorHookHandler - | onErrorHookHandler[]; - onRequestAbort?: onRequestAbortHookHandler - | onRequestAbortHookHandler[]; + onRequest?: onRequestMetaHookHandler + | onRequestMetaHookHandler[]; + preParsing?: preParsingMetaHookHandler + | preParsingMetaHookHandler[]; + preValidation?: preValidationMetaHookHandler + | preValidationMetaHookHandler[]; + preHandler?: preHandlerMetaHookHandler + | preHandlerMetaHookHandler[]; + preSerialization?: preSerializationMetaHookHandler + | preSerializationMetaHookHandler[]; + onSend?: onSendMetaHookHandler + | onSendMetaHookHandler[]; + onResponse?: onResponseMetaHookHandler + | onResponseMetaHookHandler[]; + onTimeout?: onTimeoutMetaHookHandler + | onTimeoutMetaHookHandler[]; + onError?: onErrorMetaHookHandler + | onErrorMetaHookHandler[]; + onRequestAbort?: onRequestAbortMetaHookHandler + | onRequestAbortMetaHookHandler[]; } - /** * Route handler method declaration. */ From cd84d135e663fb24fa543760a3d318e5f672449f Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Tue, 12 Dec 2023 20:54:28 +0100 Subject: [PATCH 0533/1295] fix(#5180): close secondary bindings after primary is closed (#5201) * fix(#5180): close secondary bindings after primary is closed * test: handle ipv6 correctly --- lib/server.js | 41 +++++++++++++++--------------------- test/server.test.js | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/lib/server.js b/lib/server.js index 6f2d06514cc..ed516e7b3fa 100644 --- a/lib/server.js +++ b/lib/server.js @@ -163,29 +163,6 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o /* istanbul ignore next: the else won't be taken unless listening fails */ if (!_ignoreErr) { this[kServerBindings].push(secondaryServer) - // Due to the nature of the feature is not possible to know - // ahead of time the number of bindings that will be made. - // For instance, each binding is hooked to be closed at their own - // pace through the `onClose` hook. - // It also allows them to handle possible connections already - // attached to them if any. - /* c8 ignore next 20 */ - this.onClose((instance, done) => { - if (instance[kState].listening) { - // No new TCP connections are accepted - // We swallow any error from the secondary - // server - secondaryServer.close(() => done()) - if (serverOpts.forceCloseConnections === 'idle') { - // Not needed in Node 19 - secondaryServer.closeIdleConnections() - } else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) { - secondaryServer.closeAllConnections() - } - } else { - done() - } - }) } if (bound === binding) { @@ -196,7 +173,23 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o }) const secondaryServer = getServerInstance(serverOpts, httpHandler) - const closeSecondary = () => { secondaryServer.close(() => { }) } + const closeSecondary = () => { + // To avoid fall into situations where the close of the + // secondary server is triggered before the preClose hook + // is done running, we better wait until the main server + // is closed. + // No new TCP connections are accepted + // We swallow any error from the secondary + // server + secondaryServer.close(() => {}) + if (serverOpts.forceCloseConnections === 'idle') { + // Not needed in Node 19 + secondaryServer.closeIdleConnections() + } else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) { + secondaryServer.closeAllConnections() + } + } + secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade')) mainServer.on('unref', closeSecondary) mainServer.on('close', closeSecondary) diff --git a/test/server.test.js b/test/server.test.js index f662fc8443f..1627024bc2b 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -4,6 +4,7 @@ const t = require('tap') const test = t.test const Fastify = require('..') const semver = require('semver') +const undici = require('undici') test('listen should accept null port', t => { t.plan(1) @@ -123,3 +124,53 @@ test('abort signal', { skip: semver.lt(process.version, '16.0.0') }, t => { t.end() }) + +t.test('#5180 - preClose should be called before closing secondary server', t => { + t.plan(2) + const fastify = Fastify({ forceCloseConnections: true }) + let flag = false + t.teardown(fastify.close.bind(fastify)) + + fastify.addHook('preClose', async () => { + flag = true + }) + + fastify.get('/', async (req, reply) => { + await new Promise((resolve) => { + setTimeout(() => resolve(1), 1000) + }) + + return { hello: 'world' } + }) + + fastify.listen({ port: 0 }, (err) => { + t.error(err) + const addresses = fastify.addresses() + const mainServerAddress = fastify.server.address() + let secondaryAddress + for (const addr of addresses) { + if (addr.family !== mainServerAddress.family) { + secondaryAddress = addr + secondaryAddress.address = secondaryAddress.family === 'IPv6' + ? `[${secondaryAddress.address}]` + : secondaryAddress.address + break + } + } + + if (!secondaryAddress) { + t.pass('no secondary server') + return + } + + undici.request(`http://${secondaryAddress.address}:${secondaryAddress.port}/`) + .then( + () => { t.fail('Request should not succeed') }, + () => { t.ok(flag) } + ) + + setTimeout(() => { + fastify.close() + }, 250) + }) +}) From 7c778af1d3a742a674e669729fbf228b03c86f11 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 12 Dec 2023 22:25:05 +0100 Subject: [PATCH 0534/1295] chore: update process-warning (#5206) * chore: bump new process-warning * test: update code --- fastify.js | 4 +- lib/decorate.js | 4 +- lib/pluginUtils.js | 4 +- lib/reply.js | 8 +- lib/request.js | 21 +++-- lib/route.js | 14 ++- lib/server.js | 6 +- lib/validation.js | 10 +- lib/warnings.js | 168 ++++++++++++++++++++++------------ package.json | 2 +- test/decorator.test.js | 49 +++++----- test/default-route.test.js | 10 +- test/internals/reply.test.js | 12 +-- test/route.7.test.js | 13 +-- test/schema-feature.test.js | 22 ++--- test/versioned-routes.test.js | 13 +-- 16 files changed, 214 insertions(+), 146 deletions(-) diff --git a/fastify.js b/fastify.js index 4efb4c28165..354a94158aa 100644 --- a/fastify.js +++ b/fastify.js @@ -47,7 +47,7 @@ const { buildRouting, validateBodyLimitOption } = require('./lib/route') const build404 = require('./lib/fourOhFour') const getSecuredInitialConfig = require('./lib/initialConfigValidation') const override = require('./lib/pluginOverride') -const warning = require('./lib/warnings') +const { FSTDEP009 } = require('./lib/warnings') const noopSet = require('./lib/noop-set') const { appendStackTrace, @@ -152,7 +152,7 @@ function fastify (options) { let constraints = options.constraints if (options.versioning) { - warning.emit('FSTDEP009') + FSTDEP009() constraints = { ...constraints, version: { diff --git a/lib/decorate.js b/lib/decorate.js index 2181c1c4606..3fe7cc7b3f4 100644 --- a/lib/decorate.js +++ b/lib/decorate.js @@ -16,7 +16,7 @@ const { FST_ERR_DEC_DEPENDENCY_INVALID_TYPE } = require('./errors') -const warning = require('./warnings') +const { FSTDEP006 } = require('./warnings') function decorate (instance, name, fn, dependencies) { if (Object.prototype.hasOwnProperty.call(instance, name)) { @@ -58,7 +58,7 @@ function decorateConstructor (konstructor, name, fn, dependencies) { function checkReferenceType (name, fn) { if (typeof fn === 'object' && fn && !(typeof fn.getter === 'function' || typeof fn.setter === 'function')) { - warning.emit('FSTDEP006', name) + FSTDEP006(name) } } diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 9429a40c7d5..28ae84dbad9 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -11,7 +11,7 @@ const { FST_ERR_PLUGIN_VERSION_MISMATCH, FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE } = require('./errors') -const warning = require('./warnings.js') +const { FSTWRN002 } = require('./warnings.js') function getMeta (fn) { return fn[Symbol.for('plugin-meta')] @@ -138,7 +138,7 @@ function registerPluginName (fn) { function checkPluginHealthiness (fn, pluginName = 'anonymous') { if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) { - warning.emit('FSTWRN002', pluginName) + FSTWRN002(pluginName) } } diff --git a/lib/reply.js b/lib/reply.js index b831bfca441..dfecb7ebd83 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -53,7 +53,7 @@ const { FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') -const warning = require('./warnings') +const { FSTDEP010, FSTDEP013, FSTDEP019 } = require('./warnings') function Reply (res, request, log) { this.raw = res @@ -80,7 +80,7 @@ Object.defineProperties(Reply.prototype, { // Is temporary to avoid constant conflicts between `next` and `main` context: { get () { - warning.emit('FSTDEP019') + FSTDEP019() return this.request[kRouteContext] } }, @@ -96,7 +96,7 @@ Object.defineProperties(Reply.prototype, { return (this[kReplyHijacked] || this.raw.writableEnded) === true }, set (value) { - warning.emit('FSTDEP010') + FSTDEP010() if (value !== true) { throw new FST_ERR_REP_SENT_VALUE() @@ -748,7 +748,7 @@ function sendTrailer (payload, res, reply) { result.then((v) => cb(null, v), cb) } else if (result !== null && result !== undefined) { // TODO: should be removed in fastify@5 - warning.emit('FSTDEP013') + FSTDEP013() cb(null, result) } } diff --git a/lib/request.js b/lib/request.js index 46c3174c232..d334099f0df 100644 --- a/lib/request.js +++ b/lib/request.js @@ -2,7 +2,14 @@ const proxyAddr = require('proxy-addr') const semver = require('semver') -const warning = require('./warnings') +const { + FSTDEP005, + FSTDEP012, + FSTDEP015, + FSTDEP016, + FSTDEP017, + FSTDEP018 +} = require('./warnings') const { kHasBeenDecorated, kSchemaBody, @@ -166,13 +173,13 @@ Object.defineProperties(Request.prototype, { }, context: { get () { - warning.emit('FSTDEP012') + FSTDEP012() return this[kRouteContext] } }, routerPath: { get () { - warning.emit('FSTDEP017') + FSTDEP017() return this[kRouteContext].config?.url } }, @@ -208,19 +215,19 @@ Object.defineProperties(Request.prototype, { }, routerMethod: { get () { - warning.emit('FSTDEP018') + FSTDEP018() return this[kRouteContext].config?.method } }, routeConfig: { get () { - warning.emit('FSTDEP016') + FSTDEP016() return this[kRouteContext][kPublicRouteContext]?.config } }, routeSchema: { get () { - warning.emit('FSTDEP015') + FSTDEP015() return this[kRouteContext][kPublicRouteContext].schema } }, @@ -233,7 +240,7 @@ Object.defineProperties(Request.prototype, { get () { /* istanbul ignore next */ if (semver.gte(process.versions.node, '13.0.0')) { - warning.emit('FSTDEP005') + FSTDEP005() } return this.raw.connection } diff --git a/lib/route.js b/lib/route.js index c1e36e6d992..61f47cd1143 100644 --- a/lib/route.js +++ b/lib/route.js @@ -7,7 +7,11 @@ const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeou const { supportedMethods } = require('./httpMethods') const { normalizeSchema } = require('./schemas') const { parseHeadOnSendHandlers } = require('./headRoute') -const warning = require('./warnings') +const { + FSTDEP007, + FSTDEP008, + FSTDEP014 +} = require('./warnings') const { compileSchemasForValidation, @@ -100,11 +104,11 @@ function buildRouting (options) { hasRoute, prepareRoute, getDefaultRoute: function () { - warning.emit('FSTDEP014') + FSTDEP014() return router.defaultRoute }, setDefaultRoute: function (defaultRoute) { - warning.emit('FSTDEP014') + FSTDEP014() if (typeof defaultRoute !== 'function') { throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE() } @@ -324,7 +328,7 @@ function buildRouting (options) { }) if (opts.version) { - warning.emit('FSTDEP008') + FSTDEP008() constraints.version = opts.version } @@ -417,7 +421,7 @@ function buildRouting (options) { const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend) prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true }) } else if (hasHEADHandler && exposeHeadRoute) { - warning.emit('FSTDEP007') + FSTDEP007() } } } diff --git a/lib/server.js b/lib/server.js index ed516e7b3fa..1d7dec82109 100644 --- a/lib/server.js +++ b/lib/server.js @@ -4,7 +4,7 @@ const http = require('node:http') const https = require('node:https') const dns = require('node:dns') -const warnings = require('./warnings') +const { FSTDEP011 } = require('./warnings') const { kState, kOptions, kServerBindings } = require('./symbols') const { onListenHookRunner } = require('./hooks') const { @@ -36,12 +36,12 @@ function createServer (options, httpHandler) { if (arguments.length === 0) { listenOptions = normalizeListenArgs([]) } else if (arguments.length > 0 && (firstArgType !== '[object Object]' && firstArgType !== '[object Function]')) { - warnings.emit('FSTDEP011') + FSTDEP011() listenOptions = normalizeListenArgs(Array.from(arguments)) cb = listenOptions.cb } else if (args.length > 1) { // `.listen(obj, a, ..., n, callback )` - warnings.emit('FSTDEP011') + FSTDEP011() // Deal with `.listen(port, host, backlog, [cb])` const hostPath = listenOptions.path ? [listenOptions.path] : [listenOptions.port ?? 0, listenOptions.host ?? 'localhost'] Object.assign(listenOptions, normalizeListenArgs([...hostPath, ...args])) diff --git a/lib/validation.js b/lib/validation.js index fe684dff306..a83d948cbf9 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -13,7 +13,7 @@ const { FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX } = require('./errors') -const warning = require('./warnings') +const { FSTWRN001 } = require('./warnings') function compileSchemasForSerialization (context, compile) { if (!context.schema || !context.schema.response) { @@ -83,25 +83,25 @@ function compileSchemasForValidation (context, compile, isCustom) { } context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' }) } else if (Object.prototype.hasOwnProperty.call(schema, 'headers')) { - warning.emit('FSTWRN001', 'headers', method, url) + FSTWRN001('headers', method, url) } if (schema.body) { context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) } else if (Object.prototype.hasOwnProperty.call(schema, 'body')) { - warning.emit('FSTWRN001', 'body', method, url) + FSTWRN001('body', method, url) } if (schema.querystring) { context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' }) } else if (Object.prototype.hasOwnProperty.call(schema, 'querystring')) { - warning.emit('FSTWRN001', 'querystring', method, url) + FSTWRN001('querystring', method, url) } if (schema.params) { context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' }) } else if (Object.prototype.hasOwnProperty.call(schema, 'params')) { - warning.emit('FSTWRN001', 'params', method, url) + FSTWRN001('params', method, url) } } diff --git a/lib/warnings.js b/lib/warnings.js index 5ce1ad452ed..609f73b5d71 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -1,60 +1,112 @@ 'use strict' -const warning = require('process-warning')() - -/** - * Deprecation codes: - * - FSTDEP005 - * - FSTDEP006 - * - FSTDEP007 - * - FSTDEP008 - * - FSTDEP009 - * - FSTDEP010 - * - FSTDEP011 - * - FSTDEP012 - * - FSTDEP013 - * - FSTDEP014 - * - FSTDEP015 - * - FSTDEP016 - * - FSTDEP017 - * - FSTDEP018 - * - FSTDEP019 - * - FSTWRN001 - * - FSTWRN002 - */ - -warning.createDeprecation('FSTDEP005', 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.') - -warning.createDeprecation('FSTDEP006', 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s') - -warning.createDeprecation('FSTDEP007', 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.') - -warning.createDeprecation('FSTDEP008', 'You are using route constraints via the route { version: "..." } option, use { constraints: { version: "..." } } option instead.') - -warning.createDeprecation('FSTDEP009', 'You are using a custom route versioning strategy via the server { versioning: "..." } option, use { constraints: { version: "..." } } option instead.') - -warning.createDeprecation('FSTDEP010', 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.') - -warning.createDeprecation('FSTDEP011', 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.') - -warning.createDeprecation('FSTDEP012', 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.') - -warning.createDeprecation('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`.') - -warning.createDeprecation('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.') - -warning.createDeprecation('FSTDEP015', 'You are accessing the deprecated "request.routeSchema" property. Use "request.routeOptions.schema" instead. Property "req.routeSchema" will be removed in `fastify@5`.') - -warning.createDeprecation('FSTDEP016', 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.') - -warning.createDeprecation('FSTDEP017', 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.url" instead. Property "req.routerPath" will be removed in `fastify@5`.') - -warning.createDeprecation('FSTDEP018', 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.') - -warning.createDeprecation('FSTDEP019', 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.') - -warning.create('FastifyWarning', 'FSTWRN001', 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', { unlimited: true }) - -warning.create('FastifyWarning', 'FSTWRN002', 'The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`', { unlimited: true }) - -module.exports = warning +const { createDeprecation, createWarning } = require('process-warning') + +const FSTDEP005 = createDeprecation({ + code: 'FSTDEP005', + message: 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.' +}) + +const FSTDEP006 = createDeprecation({ + code: 'FSTDEP006', + message: 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s' +}) + +const FSTDEP007 = createDeprecation({ + code: 'FSTDEP007', + message: 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.' +}) + +const FSTDEP008 = createDeprecation({ + code: 'FSTDEP008', + message: 'You are using route constraints via the route { version: "..." } option, use { constraints: { version: "..." } } option instead.' +}) + +const FSTDEP009 = createDeprecation({ + code: 'FSTDEP009', + message: 'You are using a custom route versioning strategy via the server { versioning: "..." } option, use { constraints: { version: "..." } } option instead.' +}) + +const FSTDEP010 = createDeprecation({ + code: 'FSTDEP010', + message: 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.' +}) + +const FSTDEP011 = createDeprecation({ + code: 'FSTDEP011', + message: 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.' +}) + +const FSTDEP012 = createDeprecation({ + code: 'FSTDEP012', + message: 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.' +}) + +const FSTDEP013 = createDeprecation({ + code: 'FSTDEP013', + message: '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`.' +}) + +const FSTDEP014 = createDeprecation({ + code: 'FSTDEP014', + message: '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.' +}) + +const FSTDEP015 = createDeprecation({ + code: 'FSTDEP015', + message: 'You are accessing the deprecated "request.routeSchema" property. Use "request.routeOptions.schema" instead. Property "req.routeSchema" will be removed in `fastify@5`.' +}) + +const FSTDEP016 = createDeprecation({ + code: 'FSTDEP016', + message: 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.' +}) + +const FSTDEP017 = createDeprecation({ + code: 'FSTDEP017', + message: 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.url" instead. Property "req.routerPath" will be removed in `fastify@5`.' +}) + +const FSTDEP018 = createDeprecation({ + code: 'FSTDEP018', + message: 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.' +}) + +const FSTDEP019 = createDeprecation({ + code: 'FSTDEP019', + message: 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.' +}) + +const FSTWRN001 = createWarning({ + name: 'FastifyWarning', + code: 'FSTWRN001', + message: 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.', + unlimited: true +}) + +const FSTWRN002 = createWarning({ + name: 'FastifyWarning', + code: 'FSTWRN002', + message: 'The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`', + unlimited: true +}) + +module.exports = { + FSTDEP005, + FSTDEP006, + FSTDEP007, + FSTDEP008, + FSTDEP009, + FSTDEP010, + FSTDEP011, + FSTDEP012, + FSTDEP013, + FSTDEP014, + FSTDEP015, + FSTDEP016, + FSTDEP017, + FSTDEP018, + FSTDEP019, + FSTWRN001, + FSTWRN002 +} diff --git a/package.json b/package.json index 3fd2e80cf76..84863318a8e 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "find-my-way": "^7.7.0", "light-my-request": "^5.11.0", "pino": "^8.16.0", - "process-warning": "^2.3.0", + "process-warning": "^3.0.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", "secure-json-parse": "^2.7.0", diff --git a/test/decorator.test.js b/test/decorator.test.js index d36e7568397..f16ba772fb2 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -790,31 +790,33 @@ test('decorate* should throw if called after ready', async t => { }) test('decorate* should emit warning if an array is passed', t => { - t.plan(2) - function onWarning (code, name) { + t.plan(1) + + function onWarning (name) { t.equal(name, 'test_array') - t.equal(code, 'FSTDEP006') - } - const warning = { - emit: onWarning } - const decorate = proxyquire('../lib/decorate', { './warnings': warning }) + const decorate = proxyquire('../lib/decorate', { + './warnings': { + FSTDEP006: onWarning + } + }) const fastify = proxyquire('..', { './lib/decorate.js': decorate })() fastify.decorateRequest('test_array', []) }) test('decorate* should emit warning if object type is passed', t => { - t.plan(2) - function onWarning (code, name) { + t.plan(1) + + function onWarning (name) { t.equal(name, 'test_object') - t.equal(code, 'FSTDEP006') - } - const warning = { - emit: onWarning } - const decorate = proxyquire('../lib/decorate', { './warnings': warning }) + const decorate = proxyquire('../lib/decorate', { + './warnings': { + FSTDEP006: onWarning + } + }) const fastify = proxyquire('..', { './lib/decorate.js': decorate })() fastify.decorateRequest('test_object', { foo: 'bar' }) }) @@ -823,10 +825,12 @@ test('decorate* should not emit warning if object with getter/setter is passed', function onWarning (warning) { t.fail('Should not call a warn') } - const warning = { - emit: onWarning - } - const decorate = proxyquire('../lib/decorate', { './warnings': warning }) + + const decorate = proxyquire('../lib/decorate', { + './warnings': { + FSTDEP006: onWarning + } + }) const fastify = proxyquire('..', { './lib/decorate.js': decorate })() fastify.decorateRequest('test_getter_setter', { @@ -844,11 +848,12 @@ test('decorate* should not emit warning if string,bool,numbers are passed', t => function onWarning (warning) { t.fail('Should not call a warn') } - const warning = { - emit: onWarning - } - const decorate = proxyquire('../lib/decorate', { './warnings': warning }) + const decorate = proxyquire('../lib/decorate', { + './warnings': { + FSTDEP006: onWarning + } + }) const fastify = proxyquire('..', { './lib/decorate.js': decorate })() fastify.decorateRequest('test_str', 'foo') diff --git a/test/default-route.test.js b/test/default-route.test.js index fda35b940f2..78557385e7f 100644 --- a/test/default-route.test.js +++ b/test/default-route.test.js @@ -3,7 +3,7 @@ const t = require('tap') const test = t.test const Fastify = require('..') -const warning = require('../lib/warnings') +const { FSTDEP014 } = require('../lib/warnings') // Silence the standard warning logs. We will test the messages explicitly. process.removeAllListeners('warning') @@ -19,12 +19,12 @@ test('setDefaultRoute should emit a deprecation warning', t => { process.on('warning', onWarning) function onWarning (warning) { t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, 'FSTDEP014') + t.equal(warning.code, FSTDEP014.code) } t.teardown(() => { process.removeListener('warning', onWarning) - warning.emitted.set('FSTDEP014', false) + FSTDEP014.emitted = false }) fastify.setDefaultRoute(defaultRoute) @@ -38,12 +38,12 @@ test('getDefaultRoute should emit a deprecation warning', t => { process.on('warning', onWarning) function onWarning (warning) { t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, 'FSTDEP014') + t.equal(warning.code, FSTDEP014.code) } t.teardown(() => { process.removeListener('warning', onWarning) - warning.emitted.set('FSTDEP014', false) + FSTDEP014.emitted = false }) fastify.getDefaultRoute() diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 72aff8aa3bb..cfd40c015ed 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -19,7 +19,7 @@ const { } = require('../../lib/symbols') const fs = require('node:fs') const path = require('node:path') -const warning = require('../../lib/warnings') +const { FSTDEP019, FSTDEP010 } = require('../../lib/warnings') const agent = new http.Agent({ keepAlive: false }) @@ -1466,14 +1466,13 @@ test('should emit deprecation warning when trying to modify the reply.sent prope t.plan(4) const fastify = Fastify() - const deprecationCode = 'FSTDEP010' - warning.emitted.delete(deprecationCode) + FSTDEP010.emitted = false process.removeAllListeners('warning') process.on('warning', onWarning) function onWarning (warning) { t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, deprecationCode) + t.equal(warning.code, FSTDEP010.code) } fastify.get('/', (req, reply) => { @@ -1494,14 +1493,13 @@ test('should emit deprecation warning when trying to use the reply.context.confi t.plan(4) const fastify = Fastify() - const deprecationCode = 'FSTDEP019' - warning.emitted.delete(deprecationCode) + FSTDEP019.emitted = false process.removeAllListeners('warning') process.on('warning', onWarning) function onWarning (warning) { t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, deprecationCode) + t.equal(warning.code, FSTDEP019.code) } fastify.get('/', (req, reply) => { diff --git a/test/route.7.test.js b/test/route.7.test.js index aeacecf2a68..536eec5670e 100644 --- a/test/route.7.test.js +++ b/test/route.7.test.js @@ -178,14 +178,15 @@ test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', t => { t.plan(7) - function onWarning (code) { - t.equal(code, 'FSTDEP007') - } - const warning = { - emit: onWarning + function onWarning () { + t.pass('warning emitted') } - const route = proxyquire('../lib/route', { './warnings': warning }) + const route = proxyquire('../lib/route', { + './warnings': { + FSTDEP007: onWarning + } + }) const fastify = proxyquire('..', { './lib/route.js': route })() const resBuffer = Buffer.from('I am a coffee!') diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index 6cc9df29491..8bf208ed317 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -6,7 +6,7 @@ const fp = require('fastify-plugin') const deepClone = require('rfdc')({ circles: true, proto: false }) const Ajv = require('ajv') const { kSchemaController } = require('../lib/symbols.js') -const warning = require('../lib/warnings') +const { FSTWRN001 } = require('../lib/warnings') const echoParams = (req, reply) => { reply.send(req.params) } const echoBody = (req, reply) => { reply.send(req.body) } @@ -261,12 +261,12 @@ test('Should emit warning if the schema headers is undefined', t => { process.on('warning', onWarning) function onWarning (warning) { t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, 'FSTWRN001') + t.equal(warning.code, FSTWRN001.code) } t.teardown(() => { process.removeListener('warning', onWarning) - warning.emitted.set('FSTWRN001', false) + FSTWRN001.emitted = false }) fastify.post('/:id', { @@ -292,12 +292,12 @@ test('Should emit warning if the schema body is undefined', t => { process.on('warning', onWarning) function onWarning (warning) { t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, 'FSTWRN001') + t.equal(warning.code, FSTWRN001.code) } t.teardown(() => { process.removeListener('warning', onWarning) - warning.emitted.set('FSTWRN001', false) + FSTWRN001.emitted = false }) fastify.post('/:id', { @@ -323,12 +323,12 @@ test('Should emit warning if the schema query is undefined', t => { process.on('warning', onWarning) function onWarning (warning) { t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, 'FSTWRN001') + t.equal(warning.code, FSTWRN001.code) } t.teardown(() => { process.removeListener('warning', onWarning) - warning.emitted.set('FSTWRN001', false) + FSTWRN001.emitted = false }) fastify.post('/:id', { @@ -354,12 +354,12 @@ test('Should emit warning if the schema params is undefined', t => { process.on('warning', onWarning) function onWarning (warning) { t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, 'FSTWRN001') + t.equal(warning.code, FSTWRN001.code) } t.teardown(() => { process.removeListener('warning', onWarning) - warning.emitted.set('FSTWRN001', false) + FSTWRN001.emitted = false }) fastify.post('/:id', { @@ -390,14 +390,14 @@ test('Should emit a warning for every route with undefined schema', t => { // => 3 x 4 assertions = 12 assertions function onWarning (warning) { t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, 'FSTWRN001') + t.equal(warning.code, FSTWRN001.code) t.equal(runs++, expectedWarningEmitted.shift()) } process.on('warning', onWarning) t.teardown(() => { process.removeListener('warning', onWarning) - warning.emitted.set('FSTWRN001', false) + FSTWRN001.emitted = false }) fastify.get('/undefinedParams/:id', { diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js index 25954dcb032..7d1e39ae672 100644 --- a/test/versioned-routes.test.js +++ b/test/versioned-routes.test.js @@ -663,14 +663,15 @@ test('Vary header check (for documentation example)', t => { test('Should trigger a warning when a versioned route is registered via version option', t => { t.plan(4) - function onWarning (code) { - t.equal(code, 'FSTDEP008') - } - const warning = { - emit: onWarning + function onWarning () { + t.pass('FSTDEP008 has been emitted') } - const route = proxyquire('../lib/route', { './warnings': warning }) + const route = proxyquire('../lib/route', { + './warnings': { + FSTDEP008: onWarning + } + }) const fastify = proxyquire('..', { './lib/route.js': route })({ exposeHeadRoutes: false }) fastify.route({ From b3a0e6772fe7a9bada1160a1f34549047d1d9972 Mon Sep 17 00:00:00 2001 From: nokazn <41154684+nokazn@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:45:52 +0900 Subject: [PATCH 0535/1295] types: nullish error types in callback function's parameter for `after` and `ready` method (#5191) * types: nullish error types in callback function's parameter * types: update `ready` method's parameter type Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> * types: add tests for `after` & `ready` methods --------- Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --- test/types/instance.test-d.ts | 12 ++++++++++++ types/instance.d.ts | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 598c5aebf61..7a1196430e7 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -256,6 +256,18 @@ expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 }, expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true }, () => {})) expectNotDeprecated(server.listen({ port: 3000, host: '::/0', ipv6Only: true }, () => {})) +// test after method +expectAssignable(server.after()) +expectAssignable(server.after((err) => { + expectType(err) +})) + +// test ready method +expectAssignable(server.ready()) +expectAssignable(server.ready((err) => { + expectType(err) +})) + expectAssignable(server.routing({} as RawRequestDefaultExpression, {} as RawReplyDefaultExpression)) expectType(fastify().get('/', { diff --git a/types/instance.d.ts b/types/instance.d.ts index 5d0fe611caf..20619cf3fab 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -137,7 +137,7 @@ export interface FastifyInstance< getSchemas(): Record; after(): FastifyInstance & PromiseLike; - after(afterListener: (err: Error) => void): FastifyInstance; + after(afterListener: (err: Error | null) => void): FastifyInstance; close(): Promise; close(closeListener: () => void): undefined; @@ -190,7 +190,7 @@ export interface FastifyInstance< listen(port: number | string, address?: string, backlog?: number): Promise; ready(): FastifyInstance & PromiseLike; - ready(readyListener: (err: Error) => void): FastifyInstance; + ready(readyListener: (err: Error | null) => void): FastifyInstance; register: FastifyRegister & PromiseLike>; From 1b588a0bfe9c395762583455a93b135e54f239e3 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Wed, 13 Dec 2023 08:02:09 +0100 Subject: [PATCH 0536/1295] fix(#5049): Remove duplicated calls to onReady (#5051) * fix: call onready once * fix: ensure on-ready promises are triggered in the right order * refactor: Apply suggestions from code review Co-authored-by: Manuel Spigolon * refactor: use promise as flow management --------- Co-authored-by: Manuel Spigolon Co-authored-by: Aras Abbasi --- fastify.js | 51 ++++++++++----- test/fastify-instance.test.js | 120 ++++++++++++++++++++++++++++++++++ test/hooks.on-ready.test.js | 16 +++++ 3 files changed, 170 insertions(+), 17 deletions(-) diff --git a/fastify.js b/fastify.js index 354a94158aa..685e8ba581e 100644 --- a/fastify.js +++ b/fastify.js @@ -217,7 +217,10 @@ function fastify (options) { [kState]: { listening: false, closing: false, - started: false + started: false, + ready: false, + booting: false, + readyPromise: null }, [kKeepAliveConnections]: keepAliveConnections, [kOptions]: options, @@ -562,25 +565,43 @@ function fastify (options) { } function ready (cb) { + if (this[kState].readyPromise !== null) { + if (cb != null) { + this[kState].readyPromise.then(() => cb(null, fastify), cb) + return + } + + return this[kState].readyPromise + } + let resolveReady let rejectReady // run the hooks after returning the promise process.nextTick(runHooks) + // Create a promise no matter what + // It will work as a barrier for all the .ready() calls (ensuring single hook execution) + // as well as a flow control mechanism to chain cbs and further + // promises + this[kState].readyPromise = new Promise(function (resolve, reject) { + resolveReady = resolve + rejectReady = reject + }) + if (!cb) { - return new Promise(function (resolve, reject) { - resolveReady = resolve - rejectReady = reject - }) + return this[kState].readyPromise + } else { + this[kState].readyPromise.then(() => cb(null, fastify), cb) } function runHooks () { // start loading fastify[kAvvioBoot]((err, done) => { - if (err || fastify[kState].started) { + if (err || fastify[kState].started || fastify[kState].ready || fastify[kState].booting) { manageErr(err) } else { + fastify[kState].booting = true hookRunnerApplication('onReady', fastify[kAvvioBoot], fastify, manageErr) } done() @@ -595,18 +616,14 @@ function fastify (options) { ? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message)) : err - if (cb) { - if (err) { - cb(err) - } else { - cb(undefined, fastify) - } - } else { - if (err) { - return rejectReady(err) - } - resolveReady(fastify) + if (err) { + return rejectReady(err) } + + resolveReady(fastify) + fastify[kState].booting = false + fastify[kState].ready = true + fastify[kState].promise = null } } diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index e7c0cc65163..e03ce822fc3 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -132,6 +132,126 @@ test('childLoggerFactory in plugin should be separate from the external one', as t.same(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) }) +test('ready should resolve in order when called multiply times (promises only)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5] + const result = [] + + const promises = [1, 2, 3, 4, 5] + .map((id) => app.ready().then(() => result.push(id))) + + await Promise.all(promises) + + t.strictSame(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should reject in order when called multiply times (promises only)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5] + const result = [] + + app.register((instance, opts, done) => { + setTimeout(() => done(new Error('test')), 500) + }) + + const promises = [1, 2, 3, 4, 5] + .map((id) => app.ready().catch(() => result.push(id))) + + await Promise.all(promises) + + t.strictSame(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should reject in order when called multiply times (callbacks only)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5] + const result = [] + + app.register((instance, opts, done) => { + setTimeout(() => done(new Error('test')), 500) + }) + + expectedOrder.map((id) => app.ready(() => result.push(id))) + + await app.ready().catch(err => { + t.equal(err.message, 'test') + }) + + t.strictSame(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should resolve in order when called multiply times (callbacks only)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5] + const result = [] + + expectedOrder.map((id) => app.ready(() => result.push(id))) + + await app.ready() + + t.strictSame(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should resolve in order when called multiply times (mixed)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5, 6] + const result = [] + + for (const order of expectedOrder) { + if (order % 2) { + app.ready(() => result.push(order)) + } else { + app.ready().then(() => result.push(order)) + } + } + + await app.ready() + + t.strictSame(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should reject in order when called multiply times (mixed)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5, 6] + const result = [] + + app.register((instance, opts, done) => { + setTimeout(() => done(new Error('test')), 500) + }) + + for (const order of expectedOrder) { + if (order % 2) { + app.ready(() => result.push(order)) + } else { + app.ready().then(null, () => result.push(order)) + } + } + + await app.ready().catch(err => { + t.equal(err.message, 'test') + }) + + t.strictSame(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should resolve in order when called multiply times (mixed)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5, 6] + const result = [] + + for (const order of expectedOrder) { + if (order % 2) { + app.ready().then(() => result.push(order)) + } else { + app.ready(() => result.push(order)) + } + } + + await app.ready() + + t.strictSame(result, expectedOrder, 'Should resolve in order') +}) + test('fastify instance should contains listeningOrigin property (with port and host)', async t => { t.plan(1) const port = 3000 diff --git a/test/hooks.on-ready.test.js b/test/hooks.on-ready.test.js index 16553ba7b5a..c61ee60b4fd 100644 --- a/test/hooks.on-ready.test.js +++ b/test/hooks.on-ready.test.js @@ -35,6 +35,22 @@ t.test('onReady should be called in order', t => { fastify.ready(err => t.error(err)) }) +t.test('onReady should be called once', async (t) => { + const app = Fastify() + let counter = 0 + + app.addHook('onReady', async function () { + counter++ + }) + + const promises = [1, 2, 3, 4, 5].map((id) => app.ready().then(() => id)) + + const result = await Promise.race(promises) + + t.strictSame(result, 1, 'Should resolve in order') + t.equal(counter, 1, 'Should call onReady only once') +}) + t.test('async onReady should be called in order', async t => { t.plan(7) const fastify = Fastify() From b8f2d9ea474e5f4bd906fde9e97807adb6362fe5 Mon Sep 17 00:00:00 2001 From: Agostino Cavarero Date: Wed, 13 Dec 2023 11:40:42 +0100 Subject: [PATCH 0537/1295] chore: remove unused type assertion (#5184) * chore: remove unused type assertion * Bump pino to v8.17.0 Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Co-authored-by: Aras Abbasi Co-authored-by: Matteo Collina --- package.json | 2 +- test/types/request.test-d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 84863318a8e..a9ee0b0deac 100644 --- a/package.json +++ b/package.json @@ -197,7 +197,7 @@ "fast-json-stringify": "^5.8.0", "find-my-way": "^7.7.0", "light-my-request": "^5.11.0", - "pino": "^8.16.0", + "pino": "^8.17.0", "process-warning": "^3.0.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 0debbdebeea..08218127f4c 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -150,7 +150,7 @@ const customLogger: CustomLoggerInterface = { trace: () => { }, debug: () => { }, foo: () => { }, // custom severity logger method - child: () => customLogger as pino.Logger + child: () => customLogger } const serverWithCustomLogger = fastify({ logger: customLogger }) From 635acc8ae50ed9a5a0ca03720a5d1bab70f64b51 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 13 Dec 2023 11:44:33 +0100 Subject: [PATCH 0538/1295] Bumped v4.25.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 685e8ba581e..f6808ba3c4f 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.24.3' +const VERSION = '4.25.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index a9ee0b0deac..92ac79cc5b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.24.3", + "version": "4.25.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 256fb2cf88628e7c28aa082713a988089751c2ab Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 14 Dec 2023 15:23:49 +0800 Subject: [PATCH 0539/1295] fix: route constraints (#5207) * fix: route constraints * test: update * chore: linting --- test/types/route.test-d.ts | 19 ++++++------------- types/route.d.ts | 10 ++++++++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index a1d7132d761..16d4a9de8d0 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -444,19 +444,12 @@ expectType(fastify().hasRoute({ url: '/', method: 'GET', constraints: { - something: { - name: 'secret', - storage: function () { - return { - get: (type) => { - return null - }, - set: (type, store) => {} - } - }, - deriveConstraint: (req, ctx, done) => {}, - mustMatchWhenDerived: true - } + // constraints value should accept any value + number: 12, + date: new Date(), + boolean: true, + function: () => {}, + object: { foo: 'bar' } } })) diff --git a/types/route.d.ts b/types/route.d.ts index 3ee4079071c..70c99b0fe35 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -1,4 +1,5 @@ import { FastifyError } from '@fastify/error' +import { ConstraintStrategy } from 'find-my-way' import { FastifyRequestContext } from './context' import { onErrorMetaHookHandler, onRequestAbortMetaHookHandler, onRequestMetaHookHandler, onResponseMetaHookHandler, onSendMetaHookHandler, onTimeoutMetaHookHandler, preHandlerMetaHookHandler, preParsingMetaHookHandler, preSerializationMetaHookHandler, preValidationMetaHookHandler } from './hooks' import { FastifyInstance } from './instance' @@ -12,7 +13,6 @@ import { ResolveFastifyReplyReturnType } from './type-provider' import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' -import { ConstraintStrategy } from 'find-my-way' export interface FastifyRouteConfig { url: string; @@ -25,6 +25,12 @@ export type RouteConstraintType = Omit, 'deriveConstrain deriveConstraint(req: RawRequestDefaultExpression, ctx?: Context, done?: (err: Error, ...args: any) => any): any, } +export interface RouteConstraint { + version?: string + host?: RegExp | string + [name: string]: unknown +} + /** * Route shorthand options for the various shorthand methods */ @@ -48,7 +54,7 @@ export interface RouteShorthandOptions< logLevel?: LogLevel; config?: Omit['config'], 'url' | 'method'>; version?: string; - constraints?: { version: string } | {host: RegExp | string} | {[name: string]: RouteConstraintType }, + constraints?: RouteConstraint, prefixTrailingSlash?: 'slash'|'no-slash'|'both'; errorHandler?: ( this: FastifyInstance, From e495eaa26800db04859da8582a0fd57f8d7ee0c6 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 14 Dec 2023 16:13:58 +0100 Subject: [PATCH 0540/1295] fix: Better plugin name detection for FSTWRN002 (#5209) Signed-off-by: Matteo Collina --- lib/pluginUtils.js | 6 +++--- test/plugin.4.test.js | 31 +++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 28ae84dbad9..c50d8c62477 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -136,14 +136,14 @@ function registerPluginName (fn) { return name } -function checkPluginHealthiness (fn, pluginName = 'anonymous') { +function checkPluginHealthiness (fn, pluginName) { if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) { - FSTWRN002(pluginName) + FSTWRN002(pluginName || 'anonymous') } } function registerPlugin (fn) { - const pluginName = registerPluginName.call(this, fn) + const pluginName = registerPluginName.call(this, fn) || getPluginName(fn) checkPluginHealthiness.call(this, fn, pluginName) checkVersion.call(this, fn) checkDecorators.call(this, fn) diff --git a/test/plugin.4.test.js b/test/plugin.4.test.js index 562aabd29c9..ac3d419ba12 100644 --- a/test/plugin.4.test.js +++ b/test/plugin.4.test.js @@ -416,20 +416,27 @@ test('hasPlugin returns true when using encapsulation', async t => { }) test('registering plugins with mixed style should return a warning', async t => { - t.plan(4) + t.plan(12) + + const pluginNames = ['error-plugin', 'anonymous', 'anotherPlugin', 'anotherPluginNamed'] + const oldWarnings = process.listeners('warning') + process.removeAllListeners('warning') process.on('warning', onWarning) function onWarning (warning) { + t.match(warning.message, new RegExp(`.*${pluginNames.shift()} plugin being registered mixes async and callback styles.*`)) t.equal(warning.name, 'FastifyWarning') t.equal(warning.code, 'FSTWRN002') } + t.teardown(() => { + process.removeListener('warning', onWarning) + for (const warning of oldWarnings) { + process.on('warning', warning) + } + }) const fastify = Fastify() - const anonymousPlugin = async (app, opts, done) => { - done() - } - const pluginName = 'error-plugin' const errorPlugin = async (app, opts, done) => { done() @@ -437,8 +444,20 @@ test('registering plugins with mixed style should return a warning', async t => const namedPlugin = fp(errorPlugin, { name: pluginName }) + async function anotherPlugin (app, opts, done) { + done() + } + + const anotherPluginNamed = async function (app, opts, done) { + done() + } + fastify.register(namedPlugin) - fastify.register(anonymousPlugin) + fastify.register(async (app, opts, done) => { + done() + }) + fastify.register(anotherPlugin) + fastify.register(anotherPluginNamed) await fastify.ready() }) From af8bd46a7b1d18c68558f07e020180f5e86922b5 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 14 Dec 2023 21:40:57 +0100 Subject: [PATCH 0541/1295] chore: at-large project (#5211) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ceb9c7f0366..f7021eeab15 100644 --- a/README.md +++ b/README.md @@ -377,10 +377,10 @@ to join this group by Lead Maintainers. [](https://openjsf.org/projects/#growth) +width="250px;"/>](https://openjsf.org/projects) -We are a [Growth -Project](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/PROJECT_PROGRESSION.md#growth-stage) +We are a [At-Large +Project](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/PROJECT_PROGRESSION.md#at-large-projects) in the [OpenJS Foundation](https://openjsf.org/). ## Acknowledgements From e3a07eaa444d0e769802195816d4e1718c2fc9ea Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 15 Dec 2023 09:33:49 +0100 Subject: [PATCH 0542/1295] Bumped v4.25.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index f6808ba3c4f..3c84c0be6d8 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.25.0' +const VERSION = '4.25.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 92ac79cc5b4..8da4e4a6ab3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.25.0", + "version": "4.25.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From bc8743a2f225c92ad68cf2ad11bcd6930249fb69 Mon Sep 17 00:00:00 2001 From: Adam Jones Date: Wed, 20 Dec 2023 06:53:27 +0000 Subject: [PATCH 0543/1295] fix: `npm run test:watch` (#5221) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8da4e4a6ab3..c64d905fd1b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", "test:typescript": "tsc test/types/import.ts && tsd", - "test:watch": "npm run unit -- -w --no-coverage-report -R terse", + "test:watch": "npm run unit -- --watch --cov --no-coverage-report --reporter=terse", "unit": "c8 tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap", From b834ac7f18b47415a78c274f8495992d50f46da6 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 24 Dec 2023 17:51:08 +0100 Subject: [PATCH 0544/1295] Always consume stream payloads when responding to 204 with no body (#5231) Signed-off-by: Matteo Collina --- lib/reply.js | 4 ++++ test/reply-code.test.js | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/reply.js b/lib/reply.js index dfecb7ebd83..37fcfb24472 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -605,6 +605,10 @@ function onSendEnd (reply, payload) { reply.removeHeader('content-length') safeWriteHead(reply, statusCode) sendTrailer(undefined, res, reply) + if (typeof payload.resume === 'function') { + payload.on('error', noop) + payload.resume() + } return } diff --git a/test/reply-code.test.js b/test/reply-code.test.js index 00847d5a9cb..13724da6ed5 100644 --- a/test/reply-code.test.js +++ b/test/reply-code.test.js @@ -1,6 +1,7 @@ 'use strict' const t = require('tap') +const { Readable } = require('stream') const test = t.test const Fastify = require('..') @@ -59,7 +60,7 @@ test('code should handle null/undefined/float', t => { }) test('code should handle 204', t => { - t.plan(8) + t.plan(13) const fastify = Fastify() @@ -72,6 +73,18 @@ test('code should handle 204', t => { reply.status(204).send({ message: 'hello' }) }) + fastify.get('/stream/204', function (request, reply) { + const stream = new Readable({ + read () { + this.push(null) + } + }) + stream.on('end', () => { + t.pass('stream ended') + }) + reply.status(204).send(stream) + }) + fastify.inject({ method: 'GET', url: '/204' @@ -91,6 +104,16 @@ test('code should handle 204', t => { t.equal(res.payload, '') t.equal(res.headers['content-length'], undefined) }) + + fastify.inject({ + method: 'GET', + url: '/stream/204' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 204) + t.equal(res.payload, '') + t.equal(res.headers['content-length'], undefined) + }) }) test('code should handle onSend hook on 204', t => { From 0c86bb4f7e2e8ccf82a61b5cda0a038b95b96392 Mon Sep 17 00:00:00 2001 From: Adam Jones Date: Sun, 24 Dec 2023 16:51:26 +0000 Subject: [PATCH 0545/1295] docs: update setErrorHandler to explain not found behaviour (#5218) * docs: add note to setErrorHandler explaining not found behaviour Coming from other frameworks this is somewhat surprising. This documents the behaviour and explains to catch 404 errors users need to set a not found handler. * Replace note blocks with bulleted list --- docs/Reference/Server.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index bbc50928345..b1755eff039 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1532,9 +1532,11 @@ handlers. *async-await* is supported as well. If the error `statusCode` is less than 400, Fastify will automatically set it to 500 before calling the error handler. -> **Note** -> `setErrorHandler` will ***not*** catch any error inside -> an `onResponse` hook because the response has already been sent to the client. +`setErrorHandler` will ***not*** catch: +- errors thrown in an `onResponse` hook because the response has already been + sent to the client. Use the `onSend` hook instead. +- not found (404) errors. Use [`setNotFoundHandler`](#set-not-found-handler) + instead. ```js fastify.setErrorHandler(function (error, request, reply) { From 04a4c9751e3b2501aa0a0866c512e9feb1641fb6 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 24 Dec 2023 17:52:54 +0100 Subject: [PATCH 0546/1295] Bumped v4.25.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 3c84c0be6d8..d128ef3964e 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.25.1' +const VERSION = '4.25.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index c64d905fd1b..91d4aa8f164 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.25.1", + "version": "4.25.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 9e0faf73783de45a510969324ca4cf2284ea0a25 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 26 Dec 2023 13:24:16 +0000 Subject: [PATCH 0547/1295] docs(ecosystem): add missing plugins to core list (#5234) * docs(ecosystem): add missing plugins to core list Signed-off-by: Frazer Smith * Update docs/Guides/Ecosystem.md Signed-off-by: Frazer Smith * Update docs/Guides/Ecosystem.md Signed-off-by: Frazer Smith * Update docs/Guides/Ecosystem.md Signed-off-by: Frazer Smith * Update docs/Guides/Ecosystem.md Signed-off-by: Frazer Smith --------- Signed-off-by: Frazer Smith --- docs/Guides/Ecosystem.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index d1305c82b27..28ac0166615 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -53,6 +53,8 @@ section. configuration. - [`@fastify/etag`](https://github.com/fastify/fastify-etag) Automatically generate ETags for HTTP responses. +- [`@fastify/express`](https://github.com/fastify/fastify-express) Express + compatibility layer for Fastify. - [`@fastify/flash`](https://github.com/fastify/fastify-flash) Set and get flash messages using the session. - [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) Plugin to @@ -69,6 +71,8 @@ section. your HTTP requests to another server, with hooks. - [`@fastify/jwt`](https://github.com/fastify/fastify-jwt) JWT utils for Fastify, internally uses [fast-jwt](https://github.com/nearform/fast-jwt). +- [`@fastify/kafka`](https://github.com/fastify/fastify-kafka) Plugin to interact + with Apache Kafka. - [`@fastify/leveldb`](https://github.com/fastify/fastify-leveldb) Plugin to share a common LevelDB connection across Fastify. - [`@fastify/middie`](https://github.com/fastify/middie) Middleware engine for @@ -78,6 +82,8 @@ section. connection pool across every part of your server. - [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) Multipart support for Fastify. +- [`@fastify/mysql`](https://github.com/fastify/fastify-mysql) Fastify MySQL + connection plugin. - [`@fastify/nextjs`](https://github.com/fastify/fastify-nextjs) React server-side rendering support for Fastify with [Next](https://github.com/zeit/next.js/). @@ -85,6 +91,8 @@ section. [`simple-oauth2`](https://github.com/lelylan/simple-oauth2). - [`@fastify/one-line-logger`](https://github.com/fastify/one-line-logger) Formats Fastify's logs into a nice one-line message. +- [`@fastify/passport`](https://github.com/fastify/fastify-passport) Use Passport + strategies to authenticate requests and protect route. - [`@fastify/postgres`](https://github.com/fastify/fastify-postgres) Fastify PostgreSQL connection plugin, with this you can share the same PostgreSQL connection pool in every part of your server. @@ -104,6 +112,8 @@ section. A simple plugin that enables response validation for Fastify. - [`@fastify/routes`](https://github.com/fastify/fastify-routes) Plugin that provides a `Map` of routes. +- [`@fastify/routes-stats`](https://github.com/fastify/fastify-routes-stats) + Provide stats for routes using `node:perf_hooks`. - [`@fastify/schedule`](https://github.com/fastify/fastify-schedule) Plugin for scheduling periodic jobs, based on [toad-scheduler](https://github.com/kibertoad/toad-scheduler). @@ -121,6 +131,8 @@ section. - [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) Plugin for serving Swagger/OpenAPI documentation for Fastify, supporting dynamic generation. +- [`@fastify/swagger-ui`](https://github.com/fastify/fastify-swagger-ui) Plugin + for serving Swagger UI. - [`@fastify/throttle`](https://github.com/fastify/fastify-throttle) Plugin for throttling the download speed of a request. - [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) @@ -142,6 +154,8 @@ section. [Vite](https://vitejs.dev/), allows for serving SPA/MPA/SSR Vite applications. - [`@fastify/websocket`](https://github.com/fastify/fastify-websocket) WebSocket support for Fastify. Built upon [ws](https://github.com/websockets/ws). +- [`@fastify/zipkin`](https://github.com/fastify/fastify-zipkin) Plugin + for Zipkin distributed tracing system. #### [Community](#community) From a2088adaf527b1e32fa44e8fd235d2361b8a7ca7 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 26 Dec 2023 19:00:14 +0100 Subject: [PATCH 0548/1295] ci: CITGM github workflow (#5233) --- .github/workflows/citgm-package.yml | 93 ++++++++++++++++++++++++ .github/workflows/citgm.yml | 106 ++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 .github/workflows/citgm-package.yml create mode 100644 .github/workflows/citgm.yml diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml new file mode 100644 index 00000000000..cf421bb4251 --- /dev/null +++ b/.github/workflows/citgm-package.yml @@ -0,0 +1,93 @@ +name: CITGM Package + +on: + workflow_dispatch: + inputs: + package: + description: 'Package to test' + required: true + type: string + node-version: + description: 'Node version to test' + required: true + type: string + default: '20' + os: + description: 'Operating System' + required: false + type: choice + default: 'ubuntu-latest' + options: + - 'ubuntu-latest' + - 'windows-latest' + - 'macos-latest' + workflow_call: + inputs: + package: + description: 'Package to test' + required: true + type: string + node-version: + description: 'Node version to test' + required: true + type: string + default: '20' + os: + description: 'Operating System' + required: false + type: string + default: 'ubuntu-latest' +jobs: + core-plugins: + name: CITGM + runs-on: ${{inputs.os}} + permissions: + contents: read + steps: + - name: Check out Fastify + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + cache: 'npm' + cache-dependency-path: package.json + + - name: Install Dependencies for Fastify + run: | + npm install --ignore-scripts + - name: Npm Link Fastify + run: | + npm link + - name: Determine repository URL of ${{inputs.package}} + uses: actions/github-script@v7 + id: repository-url + with: + result-encoding: string + script: | + const response = await fetch('https://registry.npmjs.org/${{inputs.package}}') + const data = await response.json() + const repositoryUrl = data.repository.url + const result = repositoryUrl.match( /.*\/([a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+)\.git/)[1] + return result + - name: Check out ${{inputs.package}} + uses: actions/checkout@v4 + with: + repository: ${{ steps.repository-url.outputs.result }} + path: package + persist-credentials: false + - name: Install Dependencies for ${{inputs.package}} + working-directory: package + run: | + npm install + - name: Sym Link Fastify + working-directory: package + run: | + npm link fastify + - name: Run Tests of ${{inputs.package}} + working-directory: package + run: | + npm test diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml new file mode 100644 index 00000000000..fb81215501a --- /dev/null +++ b/.github/workflows/citgm.yml @@ -0,0 +1,106 @@ +name: CITGM + +on: + pull_request: + types: [labeled] + +jobs: + core-plugins: + name: CITGM + if: ${{ github.event.label.name == 'citgm-core-plugins' }} + permissions: + contents: read + strategy: + fail-fast: false + matrix: + package: + - '@fastify/accepts' + - '@fastify/accepts-serializer' + - '@fastify/any-schema' + - '@fastify/auth' + - '@fastify/autoload' + - '@fastify/awilix' + - '@fastify/aws-lambda' + - '@fastify/basic-auth' + - '@fastify/bearer-auth' + - '@fastify/caching' + - '@fastify/circuit-breaker' + - '@fastify/compress' + - '@fastify/cookie' + - '@fastify/cors' + - '@fastify/csrf-protection' + - '@fastify/diagnostics-channel' + - '@fastify/early-hints' + # - '@fastify/elasticsearch' + - '@fastify/env' + - '@fastify/etag' + - '@fastify/express' + - '@fastify/flash' + - '@fastify/formbody' + - '@fastify/funky' + - '@fastify/helmet' + - '@fastify/hotwire' + - '@fastify/http-proxy' + - '@fastify/jwt' + # - '@fastify/kafka' + - '@fastify/leveldb' + - '@fastify/middie' + # - '@fastify/mongodb' + - '@fastify/multipart' + # - '@fastify/mysql' + - '@fastify/nextjs' + - '@fastify/oauth2' + - '@fastify/one-line-logger' + - '@fastify/passport' + # - '@fastify/postgres' + # - '@fastify/rate-limit' + # - '@fastify/redis' + - '@fastify/reply-from' + - '@fastify/request-context' + - '@fastify/response-validation' + - '@fastify/routes' + - '@fastify/schedule' + - '@fastify/secure-session' + - '@fastify/sensible' + - '@fastify/session' + - '@fastify/soap-client' + - '@fastify/static' + - '@fastify/swagger' + - '@fastify/swagger-ui' + - '@fastify/throttle' + - '@fastify/type-provider-json-schema-to-ts' + - '@fastify/type-provider-typebox' + - '@fastify/under-pressure' + - '@fastify/url-data' + - '@fastify/view' + # - '@fastify/vite' + - '@fastify/websocket' + - '@fastify/zipkin' + node-version: ['20'] + os: [ubuntu-latest] + uses: './.github/workflows/citgm-package.yml' + with: + os: ${{ matrix.os }} + package: ${{ matrix.package }} + node-version: ${{ matrix.node-version }} + + remove-label: + if: always() + needs: + - core-plugins + continue-on-error: true + runs-on: ubuntu-latest + steps: + - name: Remove citgm-core-plugins label + uses: octokit/request-action@v2.x + id: remove-label + with: + route: DELETE /repos/{repo}/issues/{issue_number}/labels/{name} + repo: ${{ github.event.pull_request.head.repo.full_name }} + issue_number: ${{ github.event.pull_request.number }} + name: citgm-core-plugins + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: "echo Successfully removed label" + - run: "echo Could not remove label" + if: ${{ failure() }} From 026cd28ed1cb26c86c3a6af2864911357d84bd0f Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 26 Dec 2023 19:22:55 +0100 Subject: [PATCH 0549/1295] Bump find-may-way to v8.0.0 (#5236) Signed-off-by: Matteo Collina --- lib/route.js | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/route.js b/lib/route.js index 61f47cd1143..b8618588997 100644 --- a/lib/route.js +++ b/lib/route.js @@ -58,6 +58,7 @@ const { buildErrorHandler } = require('./error-handler') const { createChildLogger } = require('./logger') function buildRouting (options) { + options.config.useSemicolonDelimiter = true // To avoid a breaking change const router = FindMyWay(options.config) let avvio diff --git a/package.json b/package.json index 91d4aa8f164..7b5cd46fb58 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,7 @@ "avvio": "^8.2.1", "fast-content-type-parse": "^1.1.0", "fast-json-stringify": "^5.8.0", - "find-my-way": "^7.7.0", + "find-my-way": "^8.0.0", "light-my-request": "^5.11.0", "pino": "^8.17.0", "process-warning": "^3.0.0", From 47f9de46c0bac0fc2669836f47c8fed86ee5fa36 Mon Sep 17 00:00:00 2001 From: derammo <817160+derammo@users.noreply.github.com> Date: Tue, 26 Dec 2023 13:56:52 -0500 Subject: [PATCH 0550/1295] fix: setValidatorCompiler with addSchema (#5188) --- lib/schema-controller.js | 41 +++++++++- test/schema-serialization.test.js | 41 ++++++++++ test/schema-validation.test.js | 121 ++++++++++++++++++++++++++++-- 3 files changed, 193 insertions(+), 10 deletions(-) diff --git a/lib/schema-controller.js b/lib/schema-controller.js index 47915aeea71..119c95c727b 100644 --- a/lib/schema-controller.js +++ b/lib/schema-controller.js @@ -39,12 +39,10 @@ function buildSchemaController (parentSchemaCtrl, opts) { class SchemaController { constructor (parent, options) { - this.opts = options || (parent && parent.opts) + this.opts = options || parent?.opts this.addedSchemas = false this.compilersFactory = this.opts.compilersFactory - this.isCustomValidatorCompiler = this.opts.isCustomValidatorCompiler || false - this.isCustomSerializerCompiler = this.opts.isCustomSerializerCompiler || false if (parent) { this.schemaBucket = this.opts.bucket(parent.getSchemas()) @@ -55,6 +53,8 @@ class SchemaController { this.parent = parent } else { this.schemaBucket = this.opts.bucket() + this.isCustomValidatorCompiler = this.opts.isCustomValidatorCompiler || false + this.isCustomSerializerCompiler = this.opts.isCustomSerializerCompiler || false } } @@ -72,13 +72,46 @@ class SchemaController { return this.schemaBucket.getSchemas() } - // Schema Controller compilers holder setValidatorCompiler (validatorCompiler) { + // Set up as if the fixed validator compiler had been provided + // by a custom 'options.compilersFactory.buildValidator' that + // always returns the same compiler object. This is required because: + // + // - setValidatorCompiler must immediately install a compiler to preserve + // legacy behavior + // - setupValidator will recreate compilers from builders in some + // circumstances, so we have to install this adapter to make it + // behave the same if the legacy API is used + // + // The cloning of the compilersFactory object is necessary because + // we are aliasing the parent compilersFactory if none was provided + // to us (see constructor.) + this.compilersFactory = Object.assign( + {}, + this.compilersFactory, + { buildValidator: () => validatorCompiler }) this.validatorCompiler = validatorCompiler this.isCustomValidatorCompiler = true } setSerializerCompiler (serializerCompiler) { + // Set up as if the fixed serializer compiler had been provided + // by a custom 'options.compilersFactory.buildSerializer' that + // always returns the same compiler object. This is required because: + // + // - setSerializerCompiler must immediately install a compiler to preserve + // legacy behavior + // - setupSerializer will recreate compilers from builders in some + // circumstances, so we have to install this adapter to make it + // behave the same if the legacy API is used + // + // The cloning of the compilersFactory object is necessary because + // we are aliasing the parent compilersFactory if none was provided + // to us (see constructor.) + this.compilersFactory = Object.assign( + {}, + this.compilersFactory, + { buildSerializer: () => serializerCompiler }) this.serializerCompiler = serializerCompiler this.isCustomSerializerCompiler = true } diff --git a/test/schema-serialization.test.js b/test/schema-serialization.test.js index 0910dd4dd51..a0ceb380cf3 100644 --- a/test/schema-serialization.test.js +++ b/test/schema-serialization.test.js @@ -593,6 +593,47 @@ test('Custom setSerializerCompiler returns bad serialized output', t => { }) }) +test('Custom setSerializerCompiler with addSchema', t => { + t.plan(6) + const fastify = Fastify({ exposeHeadRoutes: false }) + + const outSchema = { + $id: 'test', + type: 'object', + whatever: 'need to be parsed by the custom serializer' + } + + fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { + t.equal(method, 'GET') + t.equal(url, '/foo/:id') + t.equal(httpStatus, '200') + t.same(schema, outSchema) + return _data => JSON.stringify({ id: 2 }) + }) + + // provoke re-creation of serialization compiler in setupSerializer + fastify.addSchema({ $id: 'dummy', type: 'object' }) + + fastify.get('/foo/:id', { + handler (_req, reply) { + reply.send({ id: 1 }) + }, + schema: { + response: { + 200: outSchema + } + } + }) + + fastify.inject({ + method: 'GET', + url: '/foo/123' + }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ id: 2 })) + }) +}) + test('Custom serializer per route', async t => { const fastify = Fastify() diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index 9984b9aae63..4d75080ef6a 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -106,7 +106,7 @@ test('Basic validation test', t => { }) test('External AJV instance', t => { - t.plan(4) + t.plan(5) const fastify = Fastify() const ajv = new AJV() @@ -118,6 +118,7 @@ test('External AJV instance', t => { fastify.addSchema(schemaBRefToA) fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { + t.pass('custom validator compiler called') return ajv.compile(schema) }) @@ -151,7 +152,7 @@ test('External AJV instance', t => { }) test('Encapsulation', t => { - t.plan(19) + t.plan(21) const fastify = Fastify() const ajv = new AJV() @@ -164,6 +165,7 @@ test('Encapsulation', t => { fastify.register((instance, opts, done) => { const validator = ({ schema, method, url, httpPart }) => { + t.pass('custom validator compiler called') return ajv.compile(schema) } instance.setValidatorCompiler(validator) @@ -271,7 +273,7 @@ test('Encapsulation', t => { }) test('Triple $ref with a simple $id', t => { - t.plan(6) + t.plan(7) const fastify = Fastify() const ajv = new AJV() @@ -285,6 +287,7 @@ test('Triple $ref with a simple $id', t => { fastify.addSchema(schemaCRefToB) fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { + t.pass('custom validator compiler called') return ajv.compile(schema) }) @@ -1024,19 +1027,125 @@ test("The same $id in route's schema must not overwrite others", t => { test('Custom validator compiler should not mutate schema', async t => { t.plan(2) - class Headers {} + class Headers { } const fastify = Fastify() fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { t.type(schema, Headers) - return () => {} + return () => { } }) fastify.get('/', { schema: { headers: new Headers() } - }, () => {}) + }, () => { }) + + await fastify.ready() +}) + +test('Custom validator builder override by custom validator compiler', async t => { + t.plan(3) + const ajvDefaults = { + removeAdditional: true, + coerceTypes: true, + allErrors: true + } + const ajv1 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_one', type: 'object', validator: () => true }) + const ajv2 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_two', type: 'object', validator: () => true }) + const fastify = Fastify({ schemaController: { compilersFactory: { buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) } } }) + + fastify.setValidatorCompiler((routeSchemaDef) => ajv2.compile(routeSchemaDef.schema)) + + fastify.post('/two/:id', { + schema: { + params: { + type: 'object', + extended_two: true, + properties: { + id: { type: 'number' } + }, + required: ['id'] + } + }, + handler: (req, _reply) => { + t.same(typeof req.params.id, 'number') + t.same(req.params.id, 43) + return 'ok' + } + }) await fastify.ready() + + const two = await fastify.inject({ + method: 'POST', + url: '/two/43' + }) + t.equal(two.statusCode, 200) +}) + +test('Custom validator builder override by custom validator compiler in child instance', async t => { + t.plan(6) + const ajvDefaults = { + removeAdditional: true, + coerceTypes: true, + allErrors: true + } + const ajv1 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_one', type: 'object', validator: () => true }) + const ajv2 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_two', type: 'object', validator: () => true }) + const fastify = Fastify({ schemaController: { compilersFactory: { buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) } } }) + + fastify.register((embedded, _opts, done) => { + embedded.setValidatorCompiler((routeSchemaDef) => ajv2.compile(routeSchemaDef.schema)) + embedded.post('/two/:id', { + schema: { + params: { + type: 'object', + extended_two: true, + properties: { + id: { type: 'number' } + }, + required: ['id'] + } + }, + handler: (req, _reply) => { + t.same(typeof req.params.id, 'number') + t.same(req.params.id, 43) + return 'ok' + } + }) + done() + }) + + fastify.post('/one/:id', { + schema: { + params: { + type: 'object', + extended_one: true, + properties: { + id: { type: 'number' } + }, + required: ['id'] + } + }, + handler: (req, _reply) => { + t.same(typeof req.params.id, 'number') + t.same(req.params.id, 42) + return 'ok' + } + }) + + await fastify.ready() + + const one = await fastify.inject({ + method: 'POST', + url: '/one/42' + }) + t.equal(one.statusCode, 200) + + const two = await fastify.inject({ + method: 'POST', + url: '/two/43' + }) + t.equal(two.statusCode, 200) }) From 7264c8892a11f50fcdf7932110d17ee7ab0fd347 Mon Sep 17 00:00:00 2001 From: Valerio Pizzichini Date: Wed, 27 Dec 2023 22:34:57 +0100 Subject: [PATCH 0551/1295] feat(routes): expose findRoute and param validator (#5230) --- docs/Reference/Server.md | 23 ++++++++ fastify.js | 3 + lib/route.js | 9 ++- test/findRoute.test.js | 118 +++++++++++++++++++++++++++++++++++++ test/types/route.test-d.ts | 11 +++- types/instance.d.ts | 9 ++- 6 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 test/findRoute.test.js diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index b1755eff039..2f48430b85b 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -57,6 +57,7 @@ describes the properties available in that options object. - [routing](#routing) - [route](#route) - [hasRoute](#hasroute) + - [findRoute](#findroute) - [close](#close) - [decorate\*](#decorate) - [register](#register) @@ -1148,6 +1149,28 @@ if (routeExists === false) { } ``` +#### findRoute + + +Method to retrieve a route already registered to the internal router. It +expects an object as the payload. `url` and `method` are mandatory fields. It +is possible to also specify `constraints`. +The method returns a route object or `null` if the route cannot be found. + +```js +const route = fastify.findRoute({ + url: '/artists/:artistId', + method: 'GET', + constraints: { version: '1.0.0' } // optional +}) + +if (route !== null) { + // perform some route checks + console.log(route.params) // `{artistId: ':artistId'}` +} +``` + + #### close diff --git a/fastify.js b/fastify.js index d128ef3964e..71be68ba562 100644 --- a/fastify.js +++ b/fastify.js @@ -285,6 +285,9 @@ function fastify (options) { hasRoute: function _route (options) { return router.hasRoute.call(this, { options }) }, + findRoute: function _findRoute (options) { + return router.findRoute(options) + }, // expose logger instance log: logger, // type provider diff --git a/lib/route.js b/lib/route.js index b8618588997..3004155860b 100644 --- a/lib/route.js +++ b/lib/route.js @@ -121,7 +121,8 @@ function buildRouting (options) { printRoutes: router.prettyPrint.bind(router), addConstraintStrategy, hasConstraintStrategy, - isAsyncConstraint + isAsyncConstraint, + findRoute } function addConstraintStrategy (strategy) { @@ -169,11 +170,15 @@ function buildRouting (options) { } function hasRoute ({ options }) { + return findRoute(options) !== null + } + + function findRoute (options) { return router.find( options.method, options.url || '', options.constraints - ) !== null + ) } /** diff --git a/test/findRoute.test.js b/test/findRoute.test.js new file mode 100644 index 00000000000..a35093e2101 --- /dev/null +++ b/test/findRoute.test.js @@ -0,0 +1,118 @@ +'use strict' + +const { test } = require('tap') +const Fastify = require('..') +const fastifyPlugin = require('fastify-plugin') + +test('findRoute should return null when route cannot be found due to a different method', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + t.equal(fastify.findRoute({ + method: 'POST', + url: '/artists/:artistId' + }), null) +}) + +test('findRoute should return an immutable route to avoid leaking and runtime route modifications', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + let route = fastify.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) + + route.params = { + ...route.params, + id: ':id' + } + + route = fastify.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) + t.same(route.params, { artistId: ':artistId' }) +}) + +test('findRoute should return null when route cannot be found due to a different path', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + t.equal(fastify.findRoute({ + method: 'GET', + url: '/books/:bookId' + }), null) +}) + +test('findRoute should return the route when found', t => { + t.plan(2) + const fastify = Fastify() + + const handler = (req, reply) => reply.send(typeof req.params.artistId) + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler + }) + + const route = fastify.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) + + t.same(route.params, { artistId: ':artistId' }) + t.same(route.store.schema, { params: { artistId: { type: 'integer' } } }) +}) + +test('findRoute should work correctly when used within plugins', t => { + t.plan(1) + const fastify = Fastify() + const handler = (req, reply) => reply.send(typeof req.params.artistId) + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler + }) + + function validateRoutePlugin (instance, opts, done) { + const validateParams = function () { + return instance.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) !== null + } + instance.decorate('validateRoutes', { validateParams }) + done() + } + + fastify.register(fastifyPlugin(validateRoutePlugin)) + + fastify.ready(() => { + t.equal(fastify.validateRoutes.validateParams(), true) + }) +}) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 16d4a9de8d0..4ab86206e8d 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -3,7 +3,9 @@ import * as http from 'http' import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from '../../fastify' import { RequestPayload } from '../../types/hooks' -import { HTTPMethods } from '../../types/utils' +import { HTTPMethods, RawServerBase, RawServerDefault } from '../../types/utils' +import { FindResult, HTTPVersion } from 'find-my-way' +import { FindMyWayFindResult, FindMyWayVersion } from '../../types/instance' /* * Testing Fastify HTTP Routes and Route Shorthands. @@ -453,6 +455,13 @@ expectType(fastify().hasRoute({ } })) +expectType>( + fastify().findRoute({ + url: '/', + method: 'get' + }) +) + expectType(fastify().route({ url: '/', method: 'get', diff --git a/types/instance.d.ts b/types/instance.d.ts index 20619cf3fab..d4eda14d555 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -1,5 +1,5 @@ import { FastifyError } from '@fastify/error' -import { ConstraintStrategy, HTTPVersion } from 'find-my-way' +import { ConstraintStrategy, FindResult, HTTPVersion } from 'find-my-way' import * as http from 'http' import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' @@ -86,6 +86,7 @@ export interface FastifyListenOptions { type NotInInterface = Key extends keyof _Interface ? never : Key type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2 +type FindMyWayFindResult = FindResult> type GetterSetter = T | { getter: (this: This) => T, @@ -219,6 +220,12 @@ export interface FastifyInstance< SchemaCompiler extends FastifySchema = FastifySchema, >(opts: Pick, 'method' | 'url' | 'constraints'>): boolean; + findRoute< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + >(opts: Pick, 'method' | 'url' | 'constraints'>): FindMyWayFindResult; + // addHook: overloads // Lifecycle addHooks From cccb52e40ffef533a019fd61e117ade8fd5edd54 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Wed, 27 Dec 2023 16:44:11 -0500 Subject: [PATCH 0552/1295] feat: add use semicolon delimter config, default = true (#5239) --- build/build-validation.js | 4 +- docs/Reference/Server.md | 27 +++++++ fastify.d.ts | 3 +- fastify.js | 3 +- lib/configValidator.js | 95 ++++++++++++++-------- lib/route.js | 1 - test/internals/initialConfig.test.js | 10 ++- test/types/instance.test-d.ts | 3 +- test/useSemicolonDelimiter.test.js | 113 +++++++++++++++++++++++++++ types/instance.d.ts | 3 +- 10 files changed, 220 insertions(+), 42 deletions(-) create mode 100644 test/useSemicolonDelimiter.test.js diff --git a/build/build-validation.js b/build/build-validation.js index 0bd2a5b1c6c..5ce1cc51122 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -41,7 +41,8 @@ const defaultInitOptions = { requestIdHeader: 'request-id', requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, // 72 seconds - exposeHeadRoutes: true + exposeHeadRoutes: true, + useSemicolonDelimiter: true } const schema = { @@ -101,6 +102,7 @@ const schema = { requestIdLogLabel: { type: 'string', default: defaultInitOptions.requestIdLogLabel }, http2SessionTimeout: { type: 'integer', default: defaultInitOptions.http2SessionTimeout }, exposeHeadRoutes: { type: 'boolean', default: defaultInitOptions.exposeHeadRoutes }, + useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.useSemicolonDelimiter }, // deprecated style of passing the versioning constraint versioning: { type: 'object', diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 2f48430b85b..15e75d4a58c 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -44,6 +44,7 @@ describes the properties available in that options object. - [`frameworkErrors`](#frameworkerrors) - [`clientErrorHandler`](#clienterrorhandler) - [`rewriteUrl`](#rewriteurl) + - [`useSemicolonDelimiter`](#usesemicolondelimiter) - [Instance](#instance) - [Server Methods](#server-methods) - [server](#server) @@ -860,6 +861,31 @@ function rewriteUrl (req) { } ``` +### `useSemicolonDelimiter` + + ++ Default `true` + +Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which supports, +separating the path and query string with a `;` character (code 59), e.g. `/dev;foo=bar`. +This decision originated from [delvedor/find-my-way#76] +(https://github.com/delvedor/find-my-way/issues/76). Thus, this option will support +backwards compatiblilty for the need to split on `;`. To disable support for splitting +on `;` set `useSemicolonDelimiter` to `false`. + +```js +const fastify = require('fastify')({ + useSemicolonDelimiter: true +}) + +fastify.get('/dev', async (request, reply) => { + // An example request such as `/dev;foo=bar` + // Will produce the following query params result `{ foo = 'bar' }` + return request.query +}) +``` + + ## Instance ### Server Methods @@ -1943,6 +1969,7 @@ The properties that can currently be exposed are: - requestIdHeader - requestIdLogLabel - http2SessionTimeout +- useSemicolonDelimiter ```js const { readFileSync } = require('node:fs') diff --git a/fastify.d.ts b/fastify.d.ts index 65c36d78cf8..4554ad5d24f 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -109,6 +109,7 @@ declare namespace fastify { allowUnsafeRegex?: boolean, requestIdHeader?: string | false, requestIdLogLabel?: string; + useSemicolonDelimiter?: boolean, jsonShorthand?: boolean; genReqId?: (req: RawRequestDefaultExpression) => string, trustProxy?: boolean | string | string[] | number | TrustProxyFunction, @@ -238,4 +239,4 @@ declare function fastify< // CJS export // const fastify = require('fastify') -export = fastify \ No newline at end of file +export = fastify diff --git a/fastify.js b/fastify.js index 71be68ba562..123759c5f66 100644 --- a/fastify.js +++ b/fastify.js @@ -181,7 +181,8 @@ function fastify (options) { caseSensitive: options.caseSensitive, allowUnsafeRegex: options.allowUnsafeRegex || defaultInitOptions.allowUnsafeRegex, buildPrettyMeta: defaultBuildPrettyMeta, - querystringParser: options.querystringParser + querystringParser: options.querystringParser, + useSemicolonDelimiter: options.useSemicolonDelimiter ?? defaultInitOptions.useSemicolonDelimiter } }) diff --git a/lib/configValidator.js b/lib/configValidator.js index f7ded6173c4..932e1a9fbb9 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -3,7 +3,7 @@ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -69,6 +69,9 @@ data.http2SessionTimeout = 72000; if(data.exposeHeadRoutes === undefined){ data.exposeHeadRoutes = true; } +if(data.useSemicolonDelimiter === undefined){ +data.useSemicolonDelimiter = true; +} const _errs1 = errors; for(const key0 in data){ if(!(func2.call(schema11.properties, key0))){ @@ -985,13 +988,38 @@ data["exposeHeadRoutes"] = coerced24; } var valid0 = _errs66 === errors; if(valid0){ -if(data.versioning !== undefined){ -let data23 = data.versioning; +let data23 = data.useSemicolonDelimiter; const _errs68 = errors; -if(errors === _errs68){ -if(data23 && typeof data23 == "object" && !Array.isArray(data23)){ +if(typeof data23 !== "boolean"){ +let coerced25 = undefined; +if(!(coerced25 !== undefined)){ +if(data23 === "false" || data23 === 0 || data23 === null){ +coerced25 = false; +} +else if(data23 === "true" || data23 === 1){ +coerced25 = true; +} +else { +validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +return false; +} +} +if(coerced25 !== undefined){ +data23 = coerced25; +if(data !== undefined){ +data["useSemicolonDelimiter"] = coerced25; +} +} +} +var valid0 = _errs68 === errors; +if(valid0){ +if(data.versioning !== undefined){ +let data24 = data.versioning; +const _errs70 = errors; +if(errors === _errs70){ +if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ let missing1; -if(((data23.storage === undefined) && (missing1 = "storage")) || ((data23.deriveVersion === undefined) && (missing1 = "deriveVersion"))){ +if(((data24.storage === undefined) && (missing1 = "storage")) || ((data24.deriveVersion === undefined) && (missing1 = "deriveVersion"))){ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; return false; } @@ -1001,49 +1029,49 @@ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/prop return false; } } -var valid0 = _errs68 === errors; +var valid0 = _errs70 === errors; } else { var valid0 = true; } if(valid0){ if(data.constraints !== undefined){ -let data24 = data.constraints; -const _errs71 = errors; -if(errors === _errs71){ -if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ -for(const key2 in data24){ -let data25 = data24[key2]; -const _errs74 = errors; -if(errors === _errs74){ +let data25 = data.constraints; +const _errs73 = errors; +if(errors === _errs73){ if(data25 && typeof data25 == "object" && !Array.isArray(data25)){ +for(const key2 in data25){ +let data26 = data25[key2]; +const _errs76 = errors; +if(errors === _errs76){ +if(data26 && typeof data26 == "object" && !Array.isArray(data26)){ let missing2; -if(((((data25.name === undefined) && (missing2 = "name")) || ((data25.storage === undefined) && (missing2 = "storage"))) || ((data25.validate === undefined) && (missing2 = "validate"))) || ((data25.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){ +if(((((data26.name === undefined) && (missing2 = "name")) || ((data26.storage === undefined) && (missing2 = "storage"))) || ((data26.validate === undefined) && (missing2 = "validate"))) || ((data26.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing2},message:"must have required property '"+missing2+"'"}]; return false; } else { -if(data25.name !== undefined){ -let data26 = data25.name; -if(typeof data26 !== "string"){ -let dataType25 = typeof data26; -let coerced25 = undefined; -if(!(coerced25 !== undefined)){ -if(dataType25 == "number" || dataType25 == "boolean"){ -coerced25 = "" + data26; +if(data26.name !== undefined){ +let data27 = data26.name; +if(typeof data27 !== "string"){ +let dataType26 = typeof data27; +let coerced26 = undefined; +if(!(coerced26 !== undefined)){ +if(dataType26 == "number" || dataType26 == "boolean"){ +coerced26 = "" + data27; } -else if(data26 === null){ -coerced25 = ""; +else if(data27 === null){ +coerced26 = ""; } else { validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } -if(coerced25 !== undefined){ -data26 = coerced25; -if(data25 !== undefined){ -data25["name"] = coerced25; +if(coerced26 !== undefined){ +data27 = coerced26; +if(data26 !== undefined){ +data26["name"] = coerced26; } } } @@ -1055,7 +1083,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/ return false; } } -var valid7 = _errs74 === errors; +var valid7 = _errs76 === errors; if(!valid7){ break; } @@ -1066,7 +1094,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs71 === errors; +var valid0 = _errs73 === errors; } else { var valid0 = true; @@ -1096,6 +1124,7 @@ var valid0 = true; } } } +} else { validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; @@ -1106,4 +1135,4 @@ return errors === 0; } -module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true} +module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":true} diff --git a/lib/route.js b/lib/route.js index 3004155860b..b2e3a1db678 100644 --- a/lib/route.js +++ b/lib/route.js @@ -58,7 +58,6 @@ const { buildErrorHandler } = require('./error-handler') const { createChildLogger } = require('./logger') function buildRouting (options) { - options.config.useSemicolonDelimiter = true // To avoid a breaking change const router = FindMyWay(options.config) let avvio diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index 37380d2eee7..850b82e83dd 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -48,14 +48,15 @@ test('without options passed to Fastify, initialConfig should expose default val requestIdHeader: 'request-id', requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, - exposeHeadRoutes: true + exposeHeadRoutes: true, + useSemicolonDelimiter: true } t.same(Fastify().initialConfig, fastifyDefaultOptions) }) test('Fastify.initialConfig should expose all options', t => { - t.plan(21) + t.plan(22) const serverFactory = (handler, opts) => { const server = http.createServer((req, res) => { @@ -99,6 +100,7 @@ test('Fastify.initialConfig should expose all options', t => { allowUnsafeRegex: false, requestIdHeader: 'request-id-alt', pluginTimeout: 20000, + useSemicolonDelimiter: false, querystringParser: str => str, genReqId: function (req) { return reqId++ @@ -123,6 +125,7 @@ test('Fastify.initialConfig should expose all options', t => { t.equal(fastify.initialConfig.bodyLimit, 1049600) t.equal(fastify.initialConfig.onProtoPoisoning, 'remove') t.equal(fastify.initialConfig.caseSensitive, true) + t.equal(fastify.initialConfig.useSemicolonDelimiter, false) t.equal(fastify.initialConfig.allowUnsafeRegex, false) t.equal(fastify.initialConfig.requestIdHeader, 'request-id-alt') t.equal(fastify.initialConfig.pluginTimeout, 20000) @@ -285,7 +288,8 @@ test('Should not have issues when passing stream options to Pino.js', t => { requestIdHeader: 'request-id', requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, - exposeHeadRoutes: true + exposeHeadRoutes: true, + useSemicolonDelimiter: true }) } catch (error) { t.fail() diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 7a1196430e7..ba4d5ce9279 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -330,7 +330,8 @@ type InitialConfig = Readonly<{ pluginTimeout?: number, requestIdHeader?: string | false, requestIdLogLabel?: string, - http2SessionTimeout?: number + http2SessionTimeout?: number, + useSemicolonDelimiter?: boolean }> expectType(fastify().initialConfig) diff --git a/test/useSemicolonDelimiter.test.js b/test/useSemicolonDelimiter.test.js new file mode 100644 index 00000000000..3a178157024 --- /dev/null +++ b/test/useSemicolonDelimiter.test.js @@ -0,0 +1,113 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') +const sget = require('simple-get').concat + +test('use semicolon delimiter default true', t => { + t.plan(4) + + const fastify = Fastify({}) + + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/1234', (req, reply) => { + reply.send(req.query) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + console.log('http://localhost:' + fastify.server.address().port + '/1234;foo=bar') + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { + foo: 'bar' + }) + }) + }) +}) + +test('use semicolon delimiter set to true', t => { + t.plan(4) + + const fastify = Fastify({ + useSemicolonDelimiter: true + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/1234', (req, reply) => { + reply.send(req.query) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { + foo: 'bar' + }) + }) + }) +}) + +test('use semicolon delimiter set to false', t => { + t.plan(4) + + const fastify = Fastify({ + useSemicolonDelimiter: false + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/1234;foo=bar', (req, reply) => { + reply.send(req.query) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), {}) + }) + }) +}) + +test('use semicolon delimiter set to false return 404', t => { + t.plan(3) + + const fastify = Fastify({ + useSemicolonDelimiter: false + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/1234', (req, reply) => { + reply.send(req.query) + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 404) + }) + }) +}) diff --git a/types/instance.d.ts b/types/instance.d.ts index d4eda14d555..da3971e933a 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -596,6 +596,7 @@ export interface FastifyInstance< pluginTimeout?: number, requestIdHeader?: string | false, requestIdLogLabel?: string, - http2SessionTimeout?: number + http2SessionTimeout?: number, + useSemicolonDelimiter?: boolean, }> } From 4e2978365423cdd5982e8faff1b8c30237b93d81 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 28 Dec 2023 18:19:25 +0100 Subject: [PATCH 0553/1295] chore: add autocannon and concurrently as devdependencies (#5240) --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7b5cd46fb58..46370b2f3a6 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "types": "fastify.d.ts", "scripts": { "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"", - "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"", - "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", + "benchmark": "concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"autocannon -c 100 -d 30 -p 10 localhost:3000/\"", + "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "coverage": "npm run unit -- --coverage-report=html", "coverage:ci": "c8 --reporter=lcov tap --coverage-report=html --no-browser --no-check-coverage", @@ -154,8 +154,10 @@ "ajv-formats": "^2.1.1", "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", + "autocannon": "^7.14.0", "branch-comparer": "^1.1.0", "c8": "^8.0.1", + "concurrently": "^8.2.2", "cross-env": "^7.0.3", "eslint": "^8.51.0", "eslint-config-standard": "^17.1.0", From 8c0140a43de3c217af358e9020e74654e4a2e3f5 Mon Sep 17 00:00:00 2001 From: Douglas Moura Date: Thu, 28 Dec 2023 15:55:34 -0300 Subject: [PATCH 0554/1295] fix: return the correct serializer function when no content-type is defined (#5229) * fix: `npm run test:watch` (#5221) * fix: set default content type to get a serializer from * feat: add tests * feat: add tests * fix: schema for the test * feat: translate word to English * fix: change the name of the test * feat: make it return false when not a function * chore: undo last changes * feat: make the content-type definition cleaner * fix: nullish coalescing assignment is not supported in node 14 Co-authored-by: Aras Abbasi Signed-off-by: Douglas Moura * fix: rename test to kebab-case * feat: improve tests * feat: remove t.end in favor of t.plan --------- Signed-off-by: Douglas Moura Co-authored-by: Adam Jones Co-authored-by: Aras Abbasi --- lib/error-handler.js | 2 +- test/serialize-response.test.js | 187 ++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 test/serialize-response.test.js diff --git a/lib/error-handler.js b/lib/error-handler.js index 32cee751072..53f52631bba 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -95,6 +95,7 @@ function defaultErrorHandler (error, request, reply) { function fallbackErrorHandler (error, reply, cb) { const res = reply.raw const statusCode = reply.statusCode + reply[kReplyHeaders]['content-type'] = reply[kReplyHeaders]['content-type'] ?? 'application/json; charset=utf-8' let payload try { const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type']) @@ -121,7 +122,6 @@ function fallbackErrorHandler (error, reply, cb) { payload = serializeError(new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)) } - reply[kReplyHeaders]['content-type'] = 'application/json; charset=utf-8' reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload) cb(reply, payload) diff --git a/test/serialize-response.test.js b/test/serialize-response.test.js new file mode 100644 index 00000000000..3e066d3722a --- /dev/null +++ b/test/serialize-response.test.js @@ -0,0 +1,187 @@ +'use strict' + +const t = require('tap') +const test = t.test +const { S } = require('fluent-json-schema') +const Fastify = require('../fastify') +const sjson = require('secure-json-parse') + +const BadRequestSchema = S.object() + .prop('statusCode', S.number()) + .prop('error', S.string()) + .prop('message', S.string()) + +const InternalServerErrorSchema = S.object() + .prop('statusCode', S.number()) + .prop('error', S.string()) + .prop('message', S.string()) + +const NotFoundSchema = S.object() + .prop('statusCode', S.number()) + .prop('error', S.string()) + .prop('message', S.string()) + +const options = { + schema: { + body: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + 400: { + description: 'Bad Request', + content: { + 'application/json': { + schema: BadRequestSchema.valueOf() + } + } + }, + 404: { + description: 'Resource not found', + content: { + 'application/json': { + schema: NotFoundSchema.valueOf(), + example: { + statusCode: 404, + error: 'Not Found', + message: 'Not Found' + } + } + } + }, + 500: { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: InternalServerErrorSchema.valueOf(), + example: { + message: 'Internal Server Error' + } + } + } + } + } + } +} + +const handler = (request, reply) => { + if (request.body.id === '400') { + return reply.status(400).send({ + statusCode: 400, + error: 'Bad Request', + message: 'Custom message', + extra: 'This should not be in the response' + }) + } + + if (request.body.id === '404') { + return reply.status(404).send({ + statusCode: 404, + error: 'Not Found', + message: 'Custom Not Found', + extra: 'This should not be in the response' + }) + } + + if (request.body.id === '500') { + reply.status(500).send({ + statusCode: 500, + error: 'Internal Server Error', + message: 'Custom Internal Server Error', + extra: 'This should not be in the response' + }) + } + + reply.send({ + id: request.body.id, + extra: 'This should not be in the response' + }) +} + +test('serialize the response for a Bad Request error, as defined on the schema', async t => { + t.plan(2) + + const fastify = Fastify({}) + + fastify.post('/', options, handler) + const response = await fastify.inject({ + method: 'POST', + url: '/' + }) + + t.equal(response.statusCode, 400) + t.same(sjson(response.body), { + statusCode: 400, + error: 'Bad Request', + message: 'body must be object' + }) +}) + +test('serialize the response for a Not Found error, as defined on the schema', async t => { + t.plan(2) + + const fastify = Fastify({}) + + fastify.post('/', options, handler) + + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: { id: '404' } + }) + + t.equal(response.statusCode, 404) + t.same(sjson(response.body), { + statusCode: 404, + error: 'Not Found', + message: 'Custom Not Found' + }) +}) + +test('serialize the response for a Internal Server Error error, as defined on the schema', async t => { + t.plan(2) + + const fastify = Fastify({}) + + fastify.post('/', options, handler) + + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: { id: '500' } + }) + + t.equal(response.statusCode, 500) + t.same(sjson(response.body), { + statusCode: 500, + error: 'Internal Server Error', + message: 'Custom Internal Server Error' + }) +}) + +test('serialize the success response, as defined on the schema', async t => { + t.plan(2) + + const fastify = Fastify({}) + + fastify.post('/', options, handler) + + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: { id: 'test' } + }) + + t.equal(response.statusCode, 200) + t.same(sjson(response.body), { + id: 'test' + }) +}) From 37e69fbee6963072474e6eea387299aa9552b7d1 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 31 Dec 2023 00:28:25 +0100 Subject: [PATCH 0555/1295] docs: add open-collective (#5216) --- CONTRIBUTING.md | 7 +++- EXPENSE_POLICY.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++ GOVERNANCE.md | 2 + README.md | 22 ++++++---- SPONSORS.md | 19 +++++++++ package.json | 4 ++ 6 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 EXPENSE_POLICY.md create mode 100644 SPONSORS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db231afbd28..e246e21b67c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,12 +101,14 @@ the following tasks: 4. Open a pull request to [`fastify/website:HEAD`](https://github.com/fastify/website/pulls) adding yourself to the - [team.yml](https://github.com/fastify/website/blob/HEAD/src/website/data/team.yml) + [team.yml](https://github.com/fastify/website/blob/HEAD/static/data/team.yml) file. This list is also sorted alphabetically so make sure to add your name in the proper order. Use your GitHub profile icon for the `picture:` field. 5. The person that does the onboarding must add you to the [npm org](https://www.npmjs.com/org/fastify), so that you can help maintaining the official plugins. +6. Optionally, the person can be added as an Open Collective member + by the lead team. ### Offboarding Collaborators @@ -121,7 +123,7 @@ person that did the onboarding must: 2. Open a pull request to [`fastify/website:HEAD`](https://github.com/fastify/website/pulls) and move themselves to the *Past Collaborators* section in the - [team.yml](https://github.com/fastify/website/blob/HEAD/src/website/data/team.yml) + [team.yml](https://github.com/fastify/website/blob/HEAD/static/data/team.yml) file. The person that did the onboarding must: @@ -131,6 +133,7 @@ The person that did the onboarding must: 3. Remove the collaborator from the [npm org](https://www.npmjs.com/org/fastify). 4. Remove the collaborator from the Azure team. +5. Remove the collaborator from the Open Collective members. ----------------------------------------- diff --git a/EXPENSE_POLICY.md b/EXPENSE_POLICY.md new file mode 100644 index 00000000000..cf5844ba7ec --- /dev/null +++ b/EXPENSE_POLICY.md @@ -0,0 +1,104 @@ +# Expense Policy + +Fastify collaborators accept donations through the [Open Collective](https://opencollective.com/fastify/) +platform to enhance the project and support the community. + +This Collective is run by and for the benefit of the independent contributors to +the Fastify open source software project. +This Collective is not endorsed or administered by OpenJS Foundation, Inc. +(the “OpenJS Foundation”). The OpenJS Foundation does not receive or have +control over any funds contributed. The OpenJS Foundation does not direct or +otherwise supervise the actions of any contributor to the Fastify project, +and all donations made will be expended for the private benefit of or otherwise +to reimburse individuals that do not have an employer/employee, contractor, or +other agency relationship with the OpenJS Foundation. +The Fastify marks used herein are used under license from the OpenJS Foundation +for the benefit of the open source software community. + +The admins of the Fastify Collective are the [lead maintainers](./GOVERNANCE.md) +of the project. + +This document outlines the process for requesting reimbursement or an invoice +for expenses. + +## Reimbursement + +Reimbursement is applicable for expenses already paid, such as: + +- Stickers +- Gadgets +- Hosting + +**Before making any purchases**, initiate a [new discussion](https://github.com/orgs/fastify/discussions) +in the `fastify` organization with the following information: + +- What is needed +- Why it is needed +- Cost +- Deadline + +Once the discussion is approved by a lead maintainer and with no unresolved objections, +the purchase can proceed, and an expense can be submitted to the [Open Collective][submit]. +This process takes a minimum of 3 business days from the request to allow time for +discussion approval. + +The discussion helps prevent misunderstandings and ensures the expense is not rejected. +As a project under the OpenJS Foundation, Fastify benefits from the Foundation's +resources, including servers, domains, and [travel funds](https://github.com/openjs-foundation/community-fund/tree/main/programs/travel-fund). + +Always seek approval first. + +## Invoice + +Invoices are for services provided to the Fastify project, such as PR reviews, +documentation, etc. +A VAT number is not required to submit an invoice. +Refer to the [Open Collective documentation][openc_docs] for details. + +### Adding a bounty to an issue + +Issues become eligible for a bounty when the core team adds the `bounty` label, +with the amount determined by the core team based on `estimated hours * rate` +(suggested $50 per hour). + +> Example: If the estimated time to fix the issue is 2 hours, +> the bounty will be $100. + +To add a bounty: + +- Apply the `bounty` label to the issue +- Comment on the issue with the bounty amount +- Edit the first comment of the issue using this template: + +``` +## 💰 Bounty + +This issue has a bounty of [$AMOUNT](LINK TO THE BOUNTY COMMENT). +_Read more about [the bounty program](./EXPENSE_POLICY.md)_ +``` + +For discussions on bounties or determining amounts, open a [new discussion](https://github.com/orgs/fastify/discussions/new?category=bounty). + +### Outstanding contributions + +The lead team can decide to add a bounty to an issue or PR not labeled as `bounty` +if the contribution is outstanding. + +### Claiming a bounty + +To claim a bounty: + +- Submit a PR that fixes the issue +- If multiple submissions exist, a core member will choose the best solution +- Once merged, the PR author can claim the bounty by: + - Submitting an expense to the [Open Collective][submit] with the PR link + - Adding a comment on the PR with a link to their Open Collective expense to + ensure the claimant is the issue resolver +- The expense will be validated by a lead maintainer and then the payment will be + processed by Open Collective + +If the Open Collective budget is insufficient, the expense will be rejected. +Unclaimed bounties are available for other issues. + +[submit]: https://opencollective.com/fastify/expenses/new +[openc_docs]: https://docs.oscollective.org/how-it-works/basics/invoice-and-reimbursement-examples diff --git a/GOVERNANCE.md b/GOVERNANCE.md index a4e63562227..a8950ce53b9 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -18,6 +18,8 @@ Fastify Lead Maintainers are the organization owners. They are the only members of the `@fastify/leads` team. The Lead Maintainers are the curator of the Fastify project and their key responsibility is to issue releases of Fastify and its dependencies. +They manage the [Open Collective](./EXPENSE_POLICY.md) funds and are responsible +for approving expenses and invoices. ## Collaborators diff --git a/README.md b/README.md index f7021eeab15..b163b5b64d0 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ downloads](https://img.shields.io/npm/dm/fastify.svg?style=flat)](https://www.np Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yellow.svg)](https://github.com/fastify/fastify/blob/main/SECURITY.md) [![Discord](https://img.shields.io/discord/725613461949906985)](https://discord.gg/fastify) [![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=blue)](https://gitpod.io/#https://github.com/fastify/fastify) +![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/fastify)
@@ -171,15 +172,6 @@ fastify.listen({ port: 3000 }, (err, address) => { Do you want to know more? Head to the Getting Started. - -### Fastify v1.x and v2.x - -Code for Fastify's **v1.x** is in [**`branch -1.x`**](https://github.com/fastify/fastify/tree/1.x), so all Fastify 1.x related -changes should be based on **`branch 1.x`**. In a similar way, all Fastify -**v2.x** related changes should be based on [**`branch -2.x`**](https://github.com/fastify/fastify/tree/2.x). - > ## Note > `.listen` binds to the local host, `localhost`, interface by default > (`127.0.0.1` or `::1`, depending on the operating system configuration). If @@ -275,6 +267,12 @@ application, you should __always__ benchmark if performance matters to you. Please visit [Fastify help](https://github.com/fastify/help) to view prior support issues and to ask new support questions. +## Contributing + +Whether reporting bugs, discussing improvements and new ideas or writing code, +we welcome contributions from anyone and everyone. Please read the [CONTRIBUTING](./CONTRIBUTING.md) +guidelines before submitting pull requests. + ## Team _Fastify_ is the result of the work of a great community. Team members are @@ -383,6 +381,12 @@ We are a [At-Large Project](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/PROJECT_PROGRESSION.md#at-large-projects) in the [OpenJS Foundation](https://openjsf.org/). +## Sponsors + +Support this project by becoming a [SPONSOR](./SPONSORS.md)! +Fastify has an [Open Collective](https://opencollective.com/fastify) +page where we accept and manage financial contributions. + ## Acknowledgements This project is kindly sponsored by: diff --git a/SPONSORS.md b/SPONSORS.md new file mode 100644 index 00000000000..68d3bf07bd2 --- /dev/null +++ b/SPONSORS.md @@ -0,0 +1,19 @@ +# Sponsors + +All active sponsors of Fastify are listed here, in order of contribution! +Our sponsors are the reason why we can work on some issues or features +that otherwise would be impossible to do. + +If you want to become a sponsor, please check out our [Open Collective page](https://opencollective.com/fastify). + +## Tier 4 + +_Be the first!_ + +## Tier 3 + +_Be the first!_ + +## Tier 2 + +_Be the first!_ diff --git a/package.json b/package.json index 46370b2f3a6..14487ea69a5 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,10 @@ "url": "https://github.com/fastify/fastify/issues" }, "homepage": "https://www.fastify.dev/", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + }, "devDependencies": { "@fastify/pre-commit": "^2.0.2", "@sinclair/typebox": "^0.31.17", From 6d29977460205de2888ea5ffb060f92752df93c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 14:16:10 +0000 Subject: [PATCH 0556/1295] chore: Bump actions/upload-artifact from 3 to 4 (#5249) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 1bbf852e8f9..255131a7f7c 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -29,7 +29,7 @@ jobs: run: | npm run coverage:ci - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: ${{ success() }} with: name: coverage-report-nix diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 903e27e8e33..98b3af4f6a5 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -29,7 +29,7 @@ jobs: run: | npm run coverage:ci - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: ${{ success() }} with: name: coverage-report-win From dd4cfedd3f699df5e573552d98233c0df4cedcc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 14:18:54 +0000 Subject: [PATCH 0557/1295] chore: Bump actions/labeler from 4 to 5 (#5248) Bumps [actions/labeler](https://github.com/actions/labeler) from 4 to 5. - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/labeler dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 903bb139647..e9f3f9d4070 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -9,7 +9,7 @@ jobs: label: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: # Workaround only valid until the next major version bump (v5) sync-labels: '' From 2a6ee491a855117f1d48889232ab8d72607bbb3f Mon Sep 17 00:00:00 2001 From: Shane Date: Tue, 2 Jan 2024 04:48:21 -0500 Subject: [PATCH 0558/1295] docs(ecosystem): update fastify-rabbitmq // add fastify-hl7 to ecosystem.md (#5245) --- docs/Guides/Ecosystem.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 28ac0166615..649639c8986 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -398,6 +398,11 @@ section. - [`fastify-hemera`](https://github.com/hemerajs/fastify-hemera) Fastify Hemera plugin, for writing reliable & fault-tolerant microservices with [nats.io](https://nats.io/). +- [`fastify-hl7`](https://github.com/Bugs5382/fastify-hl7) A Fastify Plugin to + create a server, build, and send HL7 formatted Hl7 messages. Using + [node-hl7-client](https://github.com/Bugs5382/node-hl7-client) and + [node-hl7-server](https://github.com/Bugs5382/node-hl7-server) as the + underlining technology to do this. - [`fastify-http-client`](https://github.com/kenuyx/fastify-http-client) Plugin to send HTTP(s) requests. Built upon [urllib](https://github.com/node-modules/urllib). - [`fastify-http-context`](https://github.com/thorough-developer/fastify-http-context) @@ -551,7 +556,7 @@ middlewares into Fastify plugins [qs](https://github.com/ljharb/qs). - [`fastify-rabbitmq`](https://github.com/Bugs5382/fastify-rabbitmq) Fastify RabbitMQ plugin that uses - [node-amqp-connection-manager](https://github.com/jwalton/node-amqp-connection-manager) + [node-rabbitmq-client](https://github.com/cody-greene/node-rabbitmq-client) plugin as a wrapper. - [`fastify-racing`](https://github.com/metcoder95/fastify-racing) Fastify's plugin that adds support to handle an aborted request asynchronous. From 4518fccee62c7a1abca7d1094ce62f4fafb7fb52 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:17:56 +0800 Subject: [PATCH 0559/1295] chore: update actions/labeler@5 (#5254) --- .github/workflows/labeler.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index e9f3f9d4070..93c98bee0c0 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -11,6 +11,4 @@ jobs: steps: - uses: actions/labeler@v5 with: - # Workaround only valid until the next major version bump (v5) - sync-labels: '' repo-token: "${{ secrets.GITHUB_TOKEN }}" From 70a1d3f6f79969ec0882c14308faf4d9c978f33c Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:42:27 +0800 Subject: [PATCH 0560/1295] refactor: restrict findRoute exposed property (#5253) --- lib/route.js | 14 +++++++++++++- test/findRoute.test.js | 21 +++++++++++++++++++-- test/types/route.test-d.ts | 13 +++++++++---- types/instance.d.ts | 14 +++++++------- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/lib/route.js b/lib/route.js index b2e3a1db678..56caade26b1 100644 --- a/lib/route.js +++ b/lib/route.js @@ -173,11 +173,23 @@ function buildRouting (options) { } function findRoute (options) { - return router.find( + const route = router.find( options.method, options.url || '', options.constraints ) + if (route) { + // we must reduce the expose surface, otherwise + // we provide the ability for the user to modify + // all the route and server information in runtime + return { + handler: route.handler, + params: route.params, + searchParams: route.searchParams + } + } else { + return null + } } /** diff --git a/test/findRoute.test.js b/test/findRoute.test.js index a35093e2101..1c75a5c649f 100644 --- a/test/findRoute.test.js +++ b/test/findRoute.test.js @@ -67,7 +67,7 @@ test('findRoute should return null when route cannot be found due to a different }) test('findRoute should return the route when found', t => { - t.plan(2) + t.plan(1) const fastify = Fastify() const handler = (req, reply) => reply.send(typeof req.params.artistId) @@ -85,7 +85,6 @@ test('findRoute should return the route when found', t => { }) t.same(route.params, { artistId: ':artistId' }) - t.same(route.store.schema, { params: { artistId: { type: 'integer' } } }) }) test('findRoute should work correctly when used within plugins', t => { @@ -116,3 +115,21 @@ test('findRoute should work correctly when used within plugins', t => { t.equal(fastify.validateRoutes.validateParams(), true) }) }) + +test('findRoute should not expose store', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + const route = fastify.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) + t.equal(route.store, undefined) +}) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 4ab86206e8d..3617cfd4b88 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -3,9 +3,8 @@ import * as http from 'http' import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from '../../fastify' import { RequestPayload } from '../../types/hooks' -import { HTTPMethods, RawServerBase, RawServerDefault } from '../../types/utils' -import { FindResult, HTTPVersion } from 'find-my-way' -import { FindMyWayFindResult, FindMyWayVersion } from '../../types/instance' +import { FindMyWayFindResult } from '../../types/instance' +import { HTTPMethods, RawServerDefault } from '../../types/utils' /* * Testing Fastify HTTP Routes and Route Shorthands. @@ -455,13 +454,19 @@ expectType(fastify().hasRoute({ } })) -expectType>( +expectType, 'store'>>( fastify().findRoute({ url: '/', method: 'get' }) ) +// we should not expose store +expectError(fastify().findRoute({ + url: '/', + method: 'get' +}).store) + expectType(fastify().route({ url: '/', method: 'get', diff --git a/types/instance.d.ts b/types/instance.d.ts index da3971e933a..bf63192fe57 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -1,14 +1,15 @@ import { FastifyError } from '@fastify/error' import { ConstraintStrategy, FindResult, HTTPVersion } from 'find-my-way' import * as http from 'http' -import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' -import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' -import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, preCloseHookHandler, preCloseAsyncHookHandler, LifecycleHook, ApplicationHook, HookAsyncLookup, HookLookup } from './hooks' +import { InjectOptions, CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request' +import { AddressInfo } from 'net' +import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, ProtoAction, getDefaultJsonParser, hasContentTypeParser, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' +import { ApplicationHook, HookAsyncLookup, HookLookup, LifecycleHook, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onListenAsyncHookHandler, onListenHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' import { FastifyBaseLogger, FastifyChildLoggerFactory } from './logger' import { FastifyRegister } from './register' import { FastifyReply } from './reply' import { FastifyRequest } from './request' -import { DefaultRoute, RouteGenericInterface, RouteOptions, RouteShorthandMethod, RouteHandlerMethod } from './route' +import { DefaultRoute, RouteGenericInterface, RouteHandlerMethod, RouteOptions, RouteShorthandMethod } from './route' import { FastifySchema, FastifySchemaCompiler, @@ -20,8 +21,7 @@ import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' -import { HTTPMethods, ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' -import { AddressInfo } from 'net' +import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' export interface PrintRoutesOptions { method?: HTTPMethods; @@ -224,7 +224,7 @@ export interface FastifyInstance< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, - >(opts: Pick, 'method' | 'url' | 'constraints'>): FindMyWayFindResult; + >(opts: Pick, 'method' | 'url' | 'constraints'>): Omit, 'store'>; // addHook: overloads From 6c8e0955096300dd4fcae7a043a9a735d08f9369 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 5 Jan 2024 13:47:56 +0100 Subject: [PATCH 0561/1295] fix(test): flaky on-listen hook test (#5256) * fix(test): flaky on-listen hook test * Apply suggestions from code review Signed-off-by: Aras Abbasi * simplify * use t.equal instead t.ok --------- Signed-off-by: Aras Abbasi --- test/hooks.on-listen.test.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/hooks.on-listen.test.js b/test/hooks.on-listen.test.js index fd671593036..24718ef03a2 100644 --- a/test/hooks.on-listen.test.js +++ b/test/hooks.on-listen.test.js @@ -5,9 +5,7 @@ const Fastify = require('../fastify') const fp = require('fastify-plugin') const split = require('split2') const helper = require('./helper') - -// fix citgm @aix72-ppc64 -const LISTEN_READYNESS = process.env.CITGM ? 250 : 50 +const { kState } = require('../lib/symbols') let localhost before(async function () { @@ -1056,36 +1054,35 @@ test('async onListen does not need to be awaited', t => { test('onListen hooks do not block /1', t => { t.plan(2) + const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) fastify.addHook('onListen', function (done) { - setTimeout(done, 500) + t.equal(fastify[kState].listening, true) + done() }) - const startDate = new Date() fastify.listen({ host: 'localhost', port: 0 }, err => { t.error(err) - t.ok(new Date() - startDate < LISTEN_READYNESS) }) }) test('onListen hooks do not block /2', async t => { t.plan(1) + const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) fastify.addHook('onListen', async function () { - await new Promise(resolve => setTimeout(resolve, 500)) + t.equal(fastify[kState].listening, true) }) - const startDate = new Date() await fastify.listen({ host: 'localhost', port: 0 }) - t.ok(new Date() - startDate < LISTEN_READYNESS) }) From 8c19f661b4d3ec3323dfb413c921179e8df6d9d7 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 9 Jan 2024 07:14:49 +0100 Subject: [PATCH 0562/1295] fix: remove unused promise warning in setNotFoundHandler with preHandler (#5258) --- types/instance.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/instance.d.ts b/types/instance.d.ts index bf63192fe57..b64752ee94a 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -465,8 +465,8 @@ export interface FastifyInstance< setNotFoundHandler ( opts: { - preValidation?: preValidationHookHandler | preValidationHookHandler[]; - preHandler?: preHandlerHookHandler | preHandlerHookHandler[]; + preValidation?: preValidationHookHandler | preValidationAsyncHookHandler | preValidationHookHandler[] | preValidationAsyncHookHandler[]; + preHandler?: preHandlerHookHandler | preHandlerAsyncHookHandler | preHandlerHookHandler[] | preHandlerAsyncHookHandler[]; }, handler: RouteHandlerMethod ): FastifyInstance From 458bb927711f876c1097e23096faf596ef3ccfd2 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 9 Jan 2024 12:13:05 +0100 Subject: [PATCH 0563/1295] fix: Always call resource.emitDestroy() (#5228) * Always call resource.emitDestroy() Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina --- lib/contentTypeParser.js | 11 +++++-- test/async_hooks.test.js | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 test/async_hooks.test.js diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index dcbcedbc94e..63c33f31b1c 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -157,7 +157,6 @@ ContentTypeParser.prototype.remove = function (contentType) { ContentTypeParser.prototype.run = function (contentType, handler, request, reply) { const parser = this.getParser(contentType) - const resource = new AsyncResource('content-type-parser:run', request) if (parser === undefined) { if (request.is404) { @@ -165,7 +164,14 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply } else { reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined)) } - } else if (parser.asString === true || parser.asBuffer === true) { + + // Early return to avoid allocating an AsyncResource if it's not needed + return + } + + const resource = new AsyncResource('content-type-parser:run', request) + + if (parser.asString === true || parser.asBuffer === true) { rawBody( request, reply, @@ -184,6 +190,7 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply function done (error, body) { // We cannot use resource.bind() because it is broken in node v12 and v14 resource.runInAsyncScope(() => { + resource.emitDestroy() if (error) { reply[kReplyIsError] = true reply.send(error) diff --git a/test/async_hooks.test.js b/test/async_hooks.test.js new file mode 100644 index 00000000000..992f21f023b --- /dev/null +++ b/test/async_hooks.test.js @@ -0,0 +1,69 @@ +'use strict' + +const { createHook } = require('node:async_hooks') +const t = require('tap') +const Fastify = require('..') +const sget = require('simple-get').concat + +const remainingIds = new Set() + +createHook({ + init (asyncId, type, triggerAsyncId, resource) { + if (type === 'content-type-parser:run') { + remainingIds.add(asyncId) + } + }, + destroy (asyncId) { + remainingIds.delete(asyncId) + } +}) + +const app = Fastify({ logger: false }) + +app.get('/', function (request, reply) { + reply.send({ id: 42 }) +}) + +app.post('/', function (request, reply) { + reply.send({ id: 42 }) +}) + +app.listen({ port: 0 }, function (err, address) { + t.error(err) + + sget({ + method: 'POST', + url: 'http://localhost:' + app.server.address().port, + body: { + hello: 'world' + }, + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + + sget({ + method: 'POST', + url: 'http://localhost:' + app.server.address().port, + body: { + hello: 'world' + }, + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + + sget({ + method: 'GET', + url: 'http://localhost:' + app.server.address().port, + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + app.close() + t.equal(remainingIds.size, 0) + t.end() + }) + }) + }) +}) From c7ad9af449cf339f26e18c7e47037626003500cd Mon Sep 17 00:00:00 2001 From: Matthias Keckl <53833818+matthyk@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:36:43 +0100 Subject: [PATCH 0564/1295] Add missing punctuation in Ecosystem (#5261) Signed-off-by: Matthias Keckl <53833818+matthyk@users.noreply.github.com> --- docs/Guides/Ecosystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 649639c8986..d649083008d 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -13,7 +13,7 @@ section. - [`@fastify/accepts-serializer`](https://github.com/fastify/fastify-accepts-serializer) to serialize to output according to the `Accept` header. - [`@fastify/any-schema`](https://github.com/fastify/any-schema-you-like) Save - multiple schemas and decide which one to use to serialize the payload + multiple schemas and decide which one to use to serialize the payload. - [`@fastify/auth`](https://github.com/fastify/fastify-auth) Run multiple auth functions in Fastify. - [`@fastify/autoload`](https://github.com/fastify/fastify-autoload) Require all From 3947af58c0a62909007e4c0de9444ae71b685121 Mon Sep 17 00:00:00 2001 From: Rafael Lawisch Date: Tue, 9 Jan 2024 11:52:36 -0300 Subject: [PATCH 0565/1295] Remove word repetition (#5260) This removes a repetition on `route` word (first as plain text, second time as a link to the routes documentation). Signed-off-by: Rafael Lawisch --- docs/Reference/Decorators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index fe413c139b1..f3c50ceb203 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -101,7 +101,7 @@ console.log(fastify.conf.db) ``` The decorated [Fastify server](./Server.md) is bound to `this` in -route [route](./Routes.md) handlers: +[route](./Routes.md) handlers: ```js fastify.decorate('db', new DbConnection()) From 0be27ca0cedf6e74a499f18e73d55aa0ef353b21 Mon Sep 17 00:00:00 2001 From: codershiba <155646804+codershiba@users.noreply.github.com> Date: Thu, 11 Jan 2024 19:40:32 +0530 Subject: [PATCH 0566/1295] chore(types): Remove unused imports from fastify.d.ts (#5264) Signed-off-by: codershiba <155646804+codershiba@users.noreply.github.com> --- fastify.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastify.d.ts b/fastify.d.ts index 4554ad5d24f..88b469db26d 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -14,7 +14,7 @@ import { FastifyRequestContext, FastifyContextConfig, FastifyReplyContext } from import { FastifyErrorCodes } from './types/errors' import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler } from './types/hooks' import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' -import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel, Bindings, ChildLoggerOptions } from './types/logger' +import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel } from './types/logger' import { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './types/register' import { FastifyReply } from './types/reply' From f176719ceb97718d1ce371657c1aa8abadf3c27b Mon Sep 17 00:00:00 2001 From: codershiba <155646804+codershiba@users.noreply.github.com> Date: Sat, 13 Jan 2024 12:46:48 +0530 Subject: [PATCH 0567/1295] chore(license): Update licensing year (#5266) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index bd8d19c6904..6f7efa05146 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2023 The Fastify Team +Copyright (c) 2016-2024 The Fastify Team The Fastify team members are listed at https://github.com/fastify/fastify#team and in the README file. From eca6312d7d9f584bfc6943998f13d3724c8a3f5b Mon Sep 17 00:00:00 2001 From: codershiba <155646804+codershiba@users.noreply.github.com> Date: Sat, 13 Jan 2024 15:14:32 +0530 Subject: [PATCH 0568/1295] chore(docs): Add clarification about `fastify.setErrorHandler()` (#5265) --- docs/Reference/Errors.md | 67 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 9590d39fb53..ecbf5d48f49 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -169,6 +169,71 @@ Some things to consider in your custom error handler: internally monitors the error invocation to avoid infinite loops for errors thrown in the reply phases of the lifecycle. (those after the route handler) +**Important**: When utilizing Fastify's custom error handling through +[`setErrorHandler`](./Server.md#seterrorhandler), +it's crucial to be aware of how errors +are propagated between custom and default error handlers. + +For example: +```js +const Fastify = require('fastify') + +// Instantiate the framework +const fastify = Fastify({ + logger: true +}) + +// Register parent error handler +fastify.setErrorHandler((error, request, reply) => { + reply.status(500).send({ ok: false }) +}) + +fastify.register((app, options, next) => { + // Register child error handler + fastify.setErrorHandler((error, request, reply) => { + throw error + }) + + fastify.get('/bad', async () => { + // Throws a non-Error type, 'bar' + throw 'foo' + }) + + fastify.get('/good', async () => { + // Throws an Error instance, 'bar' + throw new Error('bar') + }) + + next() +}) + +// Run the server +fastify.listen({ port: 3000 }, function (err, address) { + if (err) { + fastify.log.error(err) + process.exit(1) + } + // Server is listening at ${address} +}) +``` + +If a plugin's error handler re-throws +an error, and the error is not an instance of +[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) +(as seen in the `/bad` route), it +won't propagate to the parent context error handler. +Instead, it will be caught by the default error handler. + +To ensure consistent error handling, +it's recommended to throw instances of Error. +For instance, replacing `throw 'foo'` +with `throw new Error('foo')` in the +`/bad` route would ensure that errors +propagate through the custom error handling chain as intended. +This practice helps to avoid potential +pitfalls when working with custom +error handling in Fastify. + ### Fastify Error Codes @@ -183,7 +248,7 @@ const errorCodes = require('fastify').errorCodes For example: ```js -const Fastify = require('./fastify') +const Fastify = require('fastify') // Instantiate the framework const fastify = Fastify({ From 9c47812e6b19e5f28dc886ad4463fdec5f89e030 Mon Sep 17 00:00:00 2001 From: codershiba <155646804+codershiba@users.noreply.github.com> Date: Mon, 15 Jan 2024 00:51:34 +0530 Subject: [PATCH 0569/1295] refactor: deprecate `Reply#getResponseTime()` in favour of `Reply#elapsedTime` (#5263) --- docs/Reference/Reply.md | 23 +++++++++- docs/Reference/Warnings.md | 2 + lib/reply.js | 21 +++++---- lib/warnings.js | 6 +++ test/internals/reply.test.js | 85 +++++++++++++++++++++++++++++++++--- test/types/reply.test-d.ts | 6 ++- types/reply.d.ts | 4 ++ 7 files changed, 131 insertions(+), 16 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index b3f579ce4f0..2307c299825 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -4,6 +4,7 @@ - [Reply](#reply) - [Introduction](#introduction) - [.code(statusCode)](#codestatuscode) + - [.elapsedTime](#elapsedtime) - [.statusCode](#statuscode) - [.server](#server) - [.header(key, value)](#headerkey-value) @@ -46,6 +47,8 @@ object that exposes the following functions and properties: - `.code(statusCode)` - Sets the status code. - `.status(statusCode)` - An alias for `.code(statusCode)`. - `.statusCode` - Read and set the HTTP status code. +- `.elapsedTime` - Returns the amount of time passed +since the request was received by Fastify. - `.server` - A reference to the fastify instance object. - `.header(name, value)` - Sets a response header. - `.headers(object)` - Sets all the keys of the object as response headers. @@ -85,6 +88,8 @@ object that exposes the following functions and properties: from Node core. - `.log` - The logger instance of the incoming request. - `.request` - The incoming request. +- `.getResponseTime()` - Deprecated, returns the amount of time passed +since the request was received by Fastify. - `.context` - Deprecated, access the [Request's context](./Request.md) property. ```js @@ -110,6 +115,19 @@ fastify.get('/', {config: {foo: 'bar'}}, function (request, reply) { If not set via `reply.code`, the resulting `statusCode` will be `200`. +### .elapsedTime + + +Invokes the custom response time getter to calculate the amount of time passed +since the request was received by Fastify. + +Note that unless this function is called in the [`onResponse` +hook](./Hooks.md#onresponse) it will always return `0`. + +```js +const milliseconds = reply.elapsedTime +``` + ### .statusCode @@ -327,7 +345,7 @@ reply.callNotFound() Invokes the custom response time getter to calculate the amount of time passed -since the request was started. +since the request was received by Fastify. Note that unless this function is called in the [`onResponse` hook](./Hooks.md#onresponse) it will always return `0`. @@ -336,6 +354,9 @@ hook](./Hooks.md#onresponse) it will always return `0`. const milliseconds = reply.getResponseTime() ``` +*Note: This method is deprecated and will be removed in `fastify@5`. +Use the [.elapsedTime](#elapsedtime) property instead.* + ### .type(contentType) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 35ef423510c..94c126a0e31 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -23,6 +23,7 @@ - [FSTDEP017](#FSTDEP017) - [FSTDEP018](#FSTDEP018) - [FSTDEP019](#FSTDEP019) + - [FSTDEP020](#FSTDEP020) ## Warnings @@ -75,3 +76,4 @@ Deprecation codes are further supported by the Node.js CLI options: | FSTDEP017 | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) | | FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | | FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | +| FSTDEP020 | You are using the deprecated `reply.getReponseTime()` method. | Use the `reply.elapsedTime` property instead. | [#5263](https://github.com/fastify/fastify/pull/5263) | diff --git a/lib/reply.js b/lib/reply.js index 37fcfb24472..e4de3430b3d 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -53,7 +53,7 @@ const { FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') -const { FSTDEP010, FSTDEP013, FSTDEP019 } = require('./warnings') +const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020 } = require('./warnings') function Reply (res, request, log) { this.raw = res @@ -84,6 +84,14 @@ Object.defineProperties(Reply.prototype, { return this.request[kRouteContext] } }, + elapsedTime: { + get () { + if (this[kReplyStartTime] === undefined) { + return 0 + } + return (this[kReplyEndTime] || now()) - this[kReplyStartTime] + } + }, server: { get () { return this.request[kRouteContext].server @@ -452,14 +460,11 @@ Reply.prototype.callNotFound = function () { return this } +// TODO: should be removed in fastify@5 Reply.prototype.getResponseTime = function () { - let responseTime = 0 - - if (this[kReplyStartTime] !== undefined) { - responseTime = (this[kReplyEndTime] || now()) - this[kReplyStartTime] - } + FSTDEP020() - return responseTime + return this.elapsedTime } // Make reply a thenable, so it could be used with async/await. @@ -813,7 +818,7 @@ function onResponseCallback (err, request, reply) { return } - const responseTime = reply.getResponseTime() + const responseTime = reply.elapsedTime if (err != null) { reply.log.error({ diff --git a/lib/warnings.js b/lib/warnings.js index 609f73b5d71..0b446990264 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -77,6 +77,11 @@ const FSTDEP019 = createDeprecation({ message: 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.' }) +const FSTDEP020 = createDeprecation({ + code: 'FSTDEP020', + message: 'You are using the deprecated "reply.getResponseTime()"" method. Use the "request.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.' +}) + const FSTWRN001 = createWarning({ name: 'FastifyWarning', code: 'FSTWRN001', @@ -107,6 +112,7 @@ module.exports = { FSTDEP017, FSTDEP018, FSTDEP019, + FSTDEP020, FSTWRN001, FSTWRN002 } diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index cfd40c015ed..3bb5e561e55 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -19,7 +19,7 @@ const { } = require('../../lib/symbols') const fs = require('node:fs') const path = require('node:path') -const { FSTDEP019, FSTDEP010 } = require('../../lib/warnings') +const { FSTDEP010, FSTDEP019, FSTDEP020 } = require('../../lib/warnings') const agent = new http.Agent({ keepAlive: false }) @@ -1598,7 +1598,40 @@ test('reply.getResponseTime() should return a number greater than 0 after the ti fastify.inject({ method: 'GET', url: '/' }) }) -test('reply.getResponseTime() should return the time since a request started while inflight', t => { +test('should emit deprecation warning when trying to use reply.getResponseTime() and should return the time since a request started while inflight', t => { + t.plan(5) + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send('hello world') + } + }) + + process.removeAllListeners('warning') + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'DeprecationWarning') + t.equal(warning.code, FSTDEP020.code) + } + + fastify.addHook('preValidation', (req, reply, done) => { + t.equal(reply.getResponseTime(), reply.getResponseTime()) + done() + }) + + fastify.inject({ method: 'GET', url: '/' }, (err, res) => { + t.error(err) + t.pass() + + process.removeListener('warning', onWarning) + }) + + FSTDEP020.emitted = false +}) + +test('reply.getResponseTime() should return the same value after a request is finished', t => { t.plan(1) const fastify = Fastify() fastify.route({ @@ -1609,19 +1642,61 @@ test('reply.getResponseTime() should return the time since a request started whi } }) + fastify.addHook('onResponse', (req, reply) => { + t.equal(reply.getResponseTime(), reply.getResponseTime()) + t.end() + }) + + fastify.inject({ method: 'GET', url: '/' }) +}) + +test('reply.elapsedTime should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', t => { + t.plan(1) + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send('hello world') + } + }) + + fastify.addHook('onResponse', (req, reply) => { + t.ok(reply.elapsedTime > 0) + t.end() + }) + + fastify.inject({ method: 'GET', url: '/' }) +}) + +test('reply.elapsedTime should return the time since a request started while inflight', t => { + t.plan(1) + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send('hello world') + } + }) + + let preValidationElapsedTime + fastify.addHook('preValidation', (req, reply, done) => { - t.not(reply.getResponseTime(), reply.getResponseTime()) + preValidationElapsedTime = reply.elapsedTime + done() }) fastify.addHook('onResponse', (req, reply) => { + t.ok(reply.elapsedTime > preValidationElapsedTime) t.end() }) fastify.inject({ method: 'GET', url: '/' }) }) -test('reply.getResponseTime() should return the same value after a request is finished', t => { +test('reply.elapsedTime should return the same value after a request is finished', t => { t.plan(1) const fastify = Fastify() fastify.route({ @@ -1633,7 +1708,7 @@ test('reply.getResponseTime() should return the same value after a request is fi }) fastify.addHook('onResponse', (req, reply) => { - t.equal(reply.getResponseTime(), reply.getResponseTime()) + t.equal(reply.elapsedTime, reply.elapsedTime) t.end() }) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 74f17e3a5ac..61466db3b6d 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,5 +1,5 @@ import { Buffer } from 'buffer' -import { expectAssignable, expectError, expectType } from 'tsd' +import { expectAssignable, expectDeprecated, expectError, expectType } from 'tsd' import fastify, { FastifyReplyContext, FastifyReply, FastifyRequest, FastifySchema, FastifySchemaCompiler, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' import { FastifyInstance } from '../../types/instance' import { FastifyLoggerInstance } from '../../types/logger' @@ -19,6 +19,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.status) expectType<(payload?: unknown) => FastifyReply>(reply.code(100 as number).send) + expectType(reply.elapsedTime) expectType(reply.statusCode) expectType(reply.sent) expectType<((payload?: unknown) => FastifyReply)>(reply.send) @@ -31,7 +32,8 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<{(statusCode: number, url: string): FastifyReply; (url: string): FastifyReply }>(reply.redirect) expectType<() => FastifyReply>(reply.hijack) expectType<() => void>(reply.callNotFound) - expectType<() => number>(reply.getResponseTime) + // Test reply.getResponseTime() deprecation + expectDeprecated(reply.getResponseTime) expectType<(contentType: string) => FastifyReply>(reply.type) expectType<(fn: (payload: any) => string) => FastifyReply>(reply.serializer) expectType<(payload: any) => string | ArrayBuffer | Buffer>(reply.serialize) diff --git a/types/reply.d.ts b/types/reply.d.ts index ee9091e475e..dbb8e13ca95 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -40,6 +40,7 @@ export interface FastifyReply< > { raw: RawReply; context: FastifyReplyContext; + elapsedTime: number; log: FastifyBaseLogger; request: FastifyRequest; server: FastifyInstance; @@ -59,6 +60,9 @@ export interface FastifyReply< redirect(url: string): FastifyReply; hijack(): FastifyReply; callNotFound(): void; + /** + * @deprecated Use the Reply#elapsedTime property instead + */ getResponseTime(): number; type(contentType: string): FastifyReply; serializer(fn: (payload: any) => string): FastifyReply; From 305c89de2b99f51ae322500559652197b62f75d2 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 15 Jan 2024 06:40:15 +0000 Subject: [PATCH 0570/1295] chore: remove `www.` from `fastify.dev` urls (#5270) --- docs/Guides/Ecosystem.md | 8 ++++---- docs/Guides/Getting-Started.md | 6 +++--- docs/Guides/Style-Guide.md | 14 +++++++------- package.json | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index d649083008d..e8286cd3053 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -137,11 +137,11 @@ section. throttling the download speed of a request. - [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) Fastify - [type provider](https://www.fastify.dev/docs/latest/Reference/Type-Providers/) + [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/) for [json-schema-to-ts](https://github.com/ThomasAribart/json-schema-to-ts). - [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox) Fastify - [type provider](https://www.fastify.dev/docs/latest/Reference/Type-Providers/) + [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/) for [Typebox](https://github.com/sinclairzx81/typebox). - [`@fastify/under-pressure`](https://github.com/fastify/under-pressure) Measure process load with automatic handling of _"Service Unavailable"_ plugin for @@ -644,11 +644,11 @@ middlewares into Fastify plugins Useful functions for Twitch Extension Backend Services (EBS). - [`fastify-type-provider-effect-schema`](https://github.com/daotl/fastify-type-provider-effect-schema) Fastify - [type provider](https://www.fastify.dev/docs/latest/Reference/Type-Providers/) + [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/) for [@effect/schema](https://github.com/effect-ts/schema). - [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod) Fastify - [type provider](https://www.fastify.dev/docs/latest/Reference/Type-Providers/) + [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/) for [zod](https://github.com/colinhacks/zod). - [`fastify-typeorm-plugin`](https://github.com/inthepocket/fastify-typeorm-plugin) Fastify plugin to work with TypeORM. diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index 4b45188df70..547ef7196c6 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -178,7 +178,7 @@ fastify.listen({ port: 3000 }, function (err, address) { /** * Encapsulates the routes * @param {FastifyInstance} fastify Encapsulated Fastify Instance - * @param {Object} options plugin options, refer to https://www.fastify.dev/docs/latest/Reference/Plugins/#plugin-options + * @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options */ async function routes (fastify, options) { fastify.get('/', async (request, reply) => { @@ -293,7 +293,7 @@ const fastifyPlugin = require('fastify-plugin') /** * Connects to a MongoDB database * @param {FastifyInstance} fastify Encapsulated Fastify Instance - * @param {Object} options plugin options, refer to https://www.fastify.dev/docs/latest/Reference/Plugins/#plugin-options + * @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options */ async function dbConnector (fastify, options) { fastify.register(require('@fastify/mongodb'), { @@ -312,7 +312,7 @@ module.exports = fastifyPlugin(dbConnector) /** * A plugin that provide encapsulated routes * @param {FastifyInstance} fastify encapsulated fastify instance - * @param {Object} options plugin options, refer to https://www.fastify.dev/docs/latest/Reference/Plugins/#plugin-options + * @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options */ async function routes (fastify, options) { const collection = fastify.mongo.db.collection('test_collection') diff --git a/docs/Guides/Style-Guide.md b/docs/Guides/Style-Guide.md index 97ca8c8bd5f..2b811624ddb 100644 --- a/docs/Guides/Style-Guide.md +++ b/docs/Guides/Style-Guide.md @@ -13,7 +13,7 @@ This guide is for anyone who loves to build with Fastify or wants to contribute to our documentation. You do not need to be an expert in writing technical documentation. This guide is here to help you. -Visit the [contribute](https://www.fastify.dev/contribute) page on our website or +Visit the [contribute](https://fastify.dev/contribute) page on our website or read the [CONTRIBUTING.md](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md) file on GitHub to join our Open Source folks. @@ -70,12 +70,12 @@ markdown. **Example** ``` -To learn more about hooks, see [Fastify hooks](https://www.fastify.dev/docs/latest/Reference/Hooks/). +To learn more about hooks, see [Fastify hooks](https://fastify.dev/docs/latest/Reference/Hooks/). ``` Result: >To learn more about hooks, see [Fastify ->hooks](https://www.fastify.dev/docs/latest/Reference/Hooks/). +>hooks](https://fastify.dev/docs/latest/Reference/Hooks/). @@ -224,18 +224,18 @@ hyperlink should look: // Add clear & brief description -[Fastify Plugins] (https://www.fastify.dev/docs/latest/Plugins/) +[Fastify Plugins] (https://fastify.dev/docs/latest/Plugins/) // incomplete description -[Fastify] (https://www.fastify.dev/docs/latest/Plugins/) +[Fastify] (https://fastify.dev/docs/latest/Plugins/) // Adding title in link brackets -[](https://www.fastify.dev/docs/latest/Plugins/ "fastify plugin") +[](https://fastify.dev/docs/latest/Plugins/ "fastify plugin") // Empty title -[](https://www.fastify.dev/docs/latest/Plugins/) +[](https://fastify.dev/docs/latest/Plugins/) // Adding links localhost URLs instead of using code strings (``) [http://localhost:3000/](http://localhost:3000/) diff --git a/package.json b/package.json index 14487ea69a5..3bfe826a8c8 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "bugs": { "url": "https://github.com/fastify/fastify/issues" }, - "homepage": "https://www.fastify.dev/", + "homepage": "https://fastify.dev/", "funding": { "type": "opencollective", "url": "https://opencollective.com/fastify" From 7dc69db818c59460f5578bd5d2fe01e5eba1f788 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Mon, 15 Jan 2024 02:01:36 -0500 Subject: [PATCH 0571/1295] feat: expose method for setGenReqId on FastifyInstance (#5259) --- docs/Reference/Server.md | 40 ++++ fastify.js | 24 ++- lib/fourOhFour.js | 7 +- lib/reqIdGenFactory.js | 5 + lib/route.js | 5 +- lib/symbols.js | 1 + test/genReqId.test.js | 392 ++++++++++++++++++++++++++++++++++ test/types/instance.test-d.ts | 12 ++ types/instance.d.ts | 5 + 9 files changed, 480 insertions(+), 11 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 15e75d4a58c..f23081c9203 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -84,6 +84,7 @@ describes the properties available in that options object. - [setNotFoundHandler](#setnotfoundhandler) - [setErrorHandler](#seterrorhandler) - [setChildLoggerFactory](#setchildloggerfactory) + - [setGenReqId](#setGenReqId) - [addConstraintStrategy](#addconstraintstrategy) - [hasConstraintStrategy](#hasconstraintstrategy) - [printRoutes](#printroutes) @@ -1640,6 +1641,45 @@ const fastify = require('fastify')({ The handler is bound to the Fastify instance and is fully encapsulated, so different plugins can set different logger factories. +#### setGenReqId + + +`fastify.setGenReqId(function (rawReq))` Synchronous function for setting the request-id +for additional Fastify instances. It will receive the _raw_ incoming request as a +parameter. The provided function should not throw an Error in any case. + +Especially in distributed systems, you may want to override the default ID +generation behavior to handle custom ways of generating different IDs in +order to handle different use cases. Such as observability or webhooks plugins. + +For example: +```js +const fastify = require('fastify')({ + genReqId: (req) => { + return 'base' + } +}) + +fastify.register((instance, opts, done) => { + instance.setGenReqId((req) => { + // custom request ID for `/webhooks` + return 'webhooks-id' + }) + done() +}, { prefix: '/webhooks' }) + +fastify.register((instance, opts, done) => { + instance.setGenReqId((req) => { + // custom request ID for `/observability` + return 'observability-id' + }) + done() +}, { prefix: '/observability' }) +``` + +The handler is bound to the Fastify instance and is fully encapsulated, so +different plugins can set a different request ID. + #### addConstraintStrategy diff --git a/fastify.js b/fastify.js index 123759c5f66..8092d72686c 100644 --- a/fastify.js +++ b/fastify.js @@ -28,7 +28,8 @@ const { kSchemaErrorFormatter, kErrorHandler, kKeepAliveConnections, - kChildLoggerFactory + kChildLoggerFactory, + kGenReqId } = require('./lib/symbols.js') const { createServer, compileValidateHTTPVersion } = require('./lib/server') @@ -42,7 +43,7 @@ const SchemaController = require('./lib/schema-controller') const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks') const { createLogger, createChildLogger, defaultChildLoggerFactory } = require('./lib/logger') const pluginUtils = require('./lib/pluginUtils') -const { reqIdGenFactory } = require('./lib/reqIdGenFactory') +const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory') const { buildRouting, validateBodyLimitOption } = require('./lib/route') const build404 = require('./lib/fourOhFour') const getSecuredInitialConfig = require('./lib/initialConfigValidation') @@ -138,7 +139,6 @@ function fastify (options) { options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout options.logger = logger - options.genReqId = genReqId options.requestIdHeader = requestIdHeader options.requestIdLogLabel = requestIdLogLabel options.disableRequestLogging = disableRequestLogging @@ -248,6 +248,7 @@ function fastify (options) { [pluginUtils.kRegisteredPlugins]: [], [kPluginNameChain]: ['fastify'], [kAvvioBoot]: null, + [kGenReqId]: genReqId, // routing method routing: httpHandler, getDefaultRoute: router.getDefaultRoute.bind(router), @@ -304,6 +305,8 @@ function fastify (options) { setSchemaController, setReplySerializer, setSchemaErrorFormatter, + // set generated request id + setGenReqId, // custom parsers addContentTypeParser: ContentTypeParser.helpers.addContentTypeParser, hasContentTypeParser: ContentTypeParser.helpers.hasContentTypeParser, @@ -400,6 +403,10 @@ function fastify (options) { get () { return this[kErrorHandler].func } + }, + genReqId: { + configurable: true, + get () { return this[kGenReqId] } } }) @@ -743,7 +750,7 @@ function fastify (options) { function onBadUrl (path, req, res) { if (frameworkErrors) { - const id = genReqId(req) + const id = getGenReqId(onBadUrlContext.server, req) const childLogger = createChildLogger(onBadUrlContext, logger, req, id) const request = new Request(id, null, req, null, childLogger, onBadUrlContext) @@ -768,7 +775,7 @@ function fastify (options) { return function onAsyncConstraintError (err) { if (err) { if (frameworkErrors) { - const id = genReqId(req) + const id = getGenReqId(onBadUrlContext.server, req) const childLogger = createChildLogger(onBadUrlContext, logger, req, id) const request = new Request(id, null, req, null, childLogger, onBadUrlContext) @@ -872,6 +879,13 @@ function fastify (options) { router.routing(req, res, buildAsyncConstraintCallback(isAsync, req, res)) } } + + function setGenReqId (func) { + throwIfAlreadyStarted('Cannot call "setGenReqId"!') + + this[kGenReqId] = reqIdGenFactory(this[kOptions].requestIdHeader, func) + return this + } } fastify.errorCodes = errorCodes diff --git a/lib/fourOhFour.js b/lib/fourOhFour.js index 1b3eedbf1b7..b5f71432c28 100644 --- a/lib/fourOhFour.js +++ b/lib/fourOhFour.js @@ -19,6 +19,7 @@ const { FST_ERR_NOT_FOUND } = require('./errors') const { createChildLogger } = require('./logger') +const { getGenReqId } = require('./reqIdGenFactory.js') /** * Each fastify instance have a: @@ -28,7 +29,7 @@ const { createChildLogger } = require('./logger') * kFourOhFourContext: the context in the reply object where the handler will be executed */ function fourOhFour (options) { - const { logger, genReqId } = options + const { logger } = options // 404 router, used for handling encapsulated 404 handlers const router = FindMyWay({ onBadUrl: createOnBadUrl(), defaultRoute: fourOhFourFallBack }) @@ -58,8 +59,8 @@ function fourOhFour (options) { function createOnBadUrl () { return function onBadUrl (path, req, res) { - const id = genReqId(req) const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext] + const id = getGenReqId(fourOhFourContext.server, req) const childLogger = createChildLogger(fourOhFourContext, logger, req, id) const request = new Request(id, null, req, null, childLogger, fourOhFourContext) const reply = new Reply(res, request, childLogger) @@ -166,8 +167,8 @@ function fourOhFour (options) { // we might want to do some hard debugging // here, let's print out as much info as // we can - const id = genReqId(req) const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext] + const id = getGenReqId(fourOhFourContext.server, req) const childLogger = createChildLogger(fourOhFourContext, logger, req, id) childLogger.info({ req }, 'incoming request') diff --git a/lib/reqIdGenFactory.js b/lib/reqIdGenFactory.js index 8fc5d3e2a50..23b96211bc8 100644 --- a/lib/reqIdGenFactory.js +++ b/lib/reqIdGenFactory.js @@ -21,6 +21,10 @@ function reqIdGenFactory (requestIdHeader, optGenReqId) { return genReqId } +function getGenReqId (contextServer, req) { + return contextServer.genReqId(req) +} + function buildDefaultGenReqId () { // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8). // With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days. @@ -43,5 +47,6 @@ function buildOptionalHeaderReqId (requestIdHeader, genReqId) { } module.exports = { + getGenReqId, reqIdGenFactory } diff --git a/lib/route.js b/lib/route.js index 56caade26b1..88ea0739985 100644 --- a/lib/route.js +++ b/lib/route.js @@ -56,6 +56,7 @@ const { } = require('./symbols.js') const { buildErrorHandler } = require('./error-handler') const { createChildLogger } = require('./logger') +const { getGenReqId } = require('./reqIdGenFactory.js') function buildRouting (options) { const router = FindMyWay(options.config) @@ -66,7 +67,6 @@ function buildRouting (options) { let hasLogger let setupResponseListeners let throwIfAlreadyStarted - let genReqId let disableRequestLogging let ignoreTrailingSlash let ignoreDuplicateSlashes @@ -92,7 +92,6 @@ function buildRouting (options) { validateHTTPVersion = fastifyArgs.validateHTTPVersion globalExposeHeadRoutes = options.exposeHeadRoutes - genReqId = options.genReqId disableRequestLogging = options.disableRequestLogging ignoreTrailingSlash = options.ignoreTrailingSlash ignoreDuplicateSlashes = options.ignoreDuplicateSlashes @@ -445,7 +444,7 @@ function buildRouting (options) { // HTTP request entry point, the routing has already been executed function routeHandler (req, res, params, context, query) { - const id = genReqId(req) + const id = getGenReqId(context.server, req) const loggerOpts = { level: context.logLevel diff --git a/lib/symbols.js b/lib/symbols.js index 9a27f86f861..e31a66552b7 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -16,6 +16,7 @@ const keys = { kPluginNameChain: Symbol('fastify.pluginNameChain'), kRouteContext: Symbol('fastify.context'), kPublicRouteContext: Symbol('fastify.routeOptions'), + kGenReqId: Symbol('fastify.genReqId'), // Schema kSchemaController: Symbol('fastify.schemaController'), kSchemaHeaders: Symbol('headers-schema'), diff --git a/test/genReqId.test.js b/test/genReqId.test.js index 102aaa72cd6..1be905ef7d9 100644 --- a/test/genReqId.test.js +++ b/test/genReqId.test.js @@ -2,6 +2,7 @@ const { Readable } = require('node:stream') const { test } = require('tap') +const fp = require('fastify-plugin') const Fastify = require('..') test('Should accept a custom genReqId function', t => { @@ -72,3 +73,394 @@ test('Custom genReqId function gets raw request as argument', t => { }) }) }) + +test('Should accept option to set genReqId with setGenReqId option', t => { + t.plan(9) + + const fastify = Fastify({ + genReqId: function (req) { + return 'base' + } + }) + + fastify.register(function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'foo' + }) + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + }, { prefix: 'foo' }) + + fastify.register(function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'bar' + }) + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + }, { prefix: 'bar' }) + + fastify.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'base') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'foo') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/bar' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'bar') + fastify.close() + }) +}) + +test('Should encapsulate setGenReqId', t => { + t.plan(12) + + const fastify = Fastify({ + genReqId: function (req) { + return 'base' + } + }) + + const bazInstance = function (instance, opts, next) { + instance.register(barInstance, { prefix: 'baz' }) + + instance.setGenReqId(function (req) { + return 'baz' + }) + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + const barInstance = function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'bar' + }) + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + const fooInstance = function (instance, opts, next) { + instance.register(bazInstance, { prefix: 'baz' }) + instance.register(barInstance, { prefix: 'bar' }) + + instance.setGenReqId(function (req) { + return 'foo' + }) + + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + fastify.register(fooInstance, { prefix: 'foo' }) + + fastify.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'base') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'foo') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo/bar' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'bar') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo/baz' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'baz') + fastify.close() + }) +}) + +test('Should encapsulate setGenReqId', t => { + t.plan(12) + + const fastify = Fastify({ + genReqId: function (req) { + return 'base' + } + }) + + const bazInstance = function (instance, opts, next) { + instance.register(barInstance, { prefix: 'baz' }) + + instance.setGenReqId(function (req) { + return 'baz' + }) + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + const barInstance = function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'bar' + }) + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + const fooInstance = function (instance, opts, next) { + instance.register(bazInstance, { prefix: 'baz' }) + instance.register(barInstance, { prefix: 'bar' }) + + instance.setGenReqId(function (req) { + return 'foo' + }) + + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + fastify.register(fooInstance, { prefix: 'foo' }) + + fastify.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'base') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'foo') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo/bar' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'bar') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo/baz' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'baz') + fastify.close() + }) +}) + +test('Should not alter parent of genReqId', t => { + t.plan(6) + + const fastify = Fastify() + + const fooInstance = function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'foo' + }) + + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + fastify.register(fooInstance, { prefix: 'foo' }) + + fastify.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'req-1') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'foo') + fastify.close() + }) +}) + +test('Should have child instance user parent genReqId', t => { + t.plan(6) + + const fastify = Fastify({ + genReqId: function (req) { + return 'foo' + } + }) + + const fooInstance = function (instance, opts, next) { + instance.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + fastify.register(fooInstance, { prefix: 'foo' }) + + fastify.get('/', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'foo') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'foo') + fastify.close() + }) +}) + +test('genReqId set on root scope when using fastify-plugin', t => { + t.plan(6) + + const fastify = Fastify() + + fastify.register(fp(function (fastify, options, done) { + fastify.setGenReqId(function (req) { + return 'not-encapsulated' + }) + fastify.get('/not-encapsulated-1', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + done() + })) + + fastify.get('/not-encapsulated-2', (req, reply) => { + t.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.inject({ + method: 'GET', + url: '/not-encapsulated-1' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'not-encapsulated') + fastify.close() + }) + + fastify.inject({ + method: 'GET', + url: '/not-encapsulated-2' + }, (err, res) => { + t.error(err) + const payload = JSON.parse(res.payload) + t.equal(payload.id, 'not-encapsulated') + fastify.close() + }) +}) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index ba4d5ce9279..2eaac8be13c 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -51,6 +51,18 @@ expectAssignable( }) ) +expectAssignable( + server.setGenReqId(function (req) { + expectType(req) + return 'foo' + }) +) + +function fastifySetGenReqId (req: RawRequestDefaultExpression) { + return 'foo' +} +server.setGenReqId(fastifySetGenReqId) + function fastifyErrorHandler (this: FastifyInstance, error: FastifyError) {} server.setErrorHandler(fastifyErrorHandler) diff --git a/types/instance.d.ts b/types/instance.d.ts index b64752ee94a..cc770da130e 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -483,6 +483,11 @@ export interface FastifyInstance< handler: (this: FastifyInstance, error: TError, request: FastifyRequest, reply: FastifyReply) => any | Promise ): FastifyInstance; + /** + * Set a function that will generate a request-ids + */ + setGenReqId(fn: (req: RawRequestDefaultExpression) => string): FastifyInstance; + /** * Hook function that is called when creating a child logger instance for each request * which allows for modifying or adding child logger bindings and logger options, or From c30992b54ef2751089d0ff71b8a6a52e40b13890 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Mon, 15 Jan 2024 02:03:41 -0500 Subject: [PATCH 0572/1295] feat: align fastify.hasRoute to fmw.hasRoute (#5102) --- lib/route.js | 6 +++++- test/findRoute.test.js | 16 ++++++++++++++++ test/has-route.test.js | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/route.js b/lib/route.js index 271dc87f270..6492e94a5ea 100644 --- a/lib/route.js +++ b/lib/route.js @@ -153,7 +153,11 @@ function buildRouting (options) { } function hasRoute ({ options }) { - return findRoute(options) !== null + return router.hasRoute( + options.method, + options.url || '', + options.constraints + ) } function findRoute (options) { diff --git a/test/findRoute.test.js b/test/findRoute.test.js index a35093e2101..85711a567f2 100644 --- a/test/findRoute.test.js +++ b/test/findRoute.test.js @@ -49,6 +49,22 @@ test('findRoute should return an immutable route to avoid leaking and runtime ro t.same(route.params, { artistId: ':artistId' }) }) +test('findRoute should return null when when url is not passed', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + t.equal(fastify.findRoute({ + method: 'POST' + }), null) +}) + test('findRoute should return null when route cannot be found due to a different path', t => { t.plan(1) const fastify = Fastify() diff --git a/test/has-route.test.js b/test/has-route.test.js index 28e1ca943a7..56cade122f8 100644 --- a/test/has-route.test.js +++ b/test/has-route.test.js @@ -71,7 +71,7 @@ test('hasRoute', t => { t.equal(fastify.hasRoute({ method: 'GET', - url: '/example/12345.png' + url: '/example/:file(^\\d+).png' }), true) }) }) From 79042e60b9fc51f4969183f2d8b9ce977eb320d6 Mon Sep 17 00:00:00 2001 From: AJ Bienz Date: Wed, 17 Jan 2024 04:27:25 -0500 Subject: [PATCH 0573/1295] fix: ensure `onListen` hooks are called when they should be (#5273) * fix: ensure `onListen` hooks is called for nested encapsulation contexts * also ensure `onListen` runs for all peer contexts --- lib/hooks.js | 6 +--- test/hooks.on-listen.test.js | 65 +++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/lib/hooks.js b/lib/hooks.js index 45fdb2b3322..d744b8716da 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -181,10 +181,6 @@ function onListenHookRunner (server) { const hooks = server[kHooks].onListen const hooksLen = hooks.length - if (hooksLen === 0) { - return - } - let i = 0 let c = 0 @@ -196,7 +192,7 @@ function onListenHookRunner (server) { if ( i === hooksLen ) { - if (c < server[kChildren].length) { + while (c < server[kChildren].length) { const child = server[kChildren][c++] onListenHookRunner(child) } diff --git a/test/hooks.on-listen.test.js b/test/hooks.on-listen.test.js index 24718ef03a2..f13159fc123 100644 --- a/test/hooks.on-listen.test.js +++ b/test/hooks.on-listen.test.js @@ -272,8 +272,8 @@ test('localhost Register onListen hook after a plugin inside a plugin should log }) }) -test('localhost onListen encapsulation should be called in order', t => { - t.plan(6) +test('localhost onListen encapsulation should be called in order', async t => { + t.plan(8) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) @@ -285,20 +285,75 @@ test('localhost onListen encapsulation should be called in order', t => { done() }) - fastify.register(async (childOne, o) => { + await fastify.register(async (childOne, o) => { childOne.addHook('onListen', function (done) { t.equal(++order, 2, 'called in childOne') t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') done() }) - childOne.register(async (childTwo, o) => { + + await childOne.register(async (childTwo, o) => { childTwo.addHook('onListen', async function () { t.equal(++order, 3, 'called in childTwo') t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') }) }) + + await childOne.register(async (childTwoPeer, o) => { + childTwoPeer.addHook('onListen', async function () { + t.equal(++order, 4, 'called second in childTwo') + t.equal(this.pluginName, childTwoPeer.pluginName, 'the this binding is the right instance') + }) + }) }) - fastify.listen({ + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen encapsulation with only nested hook', async t => { + t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + await fastify.register(async (child) => { + await child.register(async (child2) => { + child2.addHook('onListen', function (done) { + t.pass() + done() + }) + }) + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen peer encapsulations with only nested hooks', async t => { + t.plan(2) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + await fastify.register(async (child) => { + await child.register(async (child2) => { + child2.addHook('onListen', function (done) { + t.pass() + done() + }) + }) + + await child.register(async (child2) => { + child2.addHook('onListen', function (done) { + t.pass() + done() + }) + }) + }) + + await fastify.listen({ host: 'localhost', port: 0 }) From cf6a87b951351016149a2ab8181fa0ae62fda944 Mon Sep 17 00:00:00 2001 From: codershiba <155646804+codershiba@users.noreply.github.com> Date: Sun, 21 Jan 2024 20:00:53 +0530 Subject: [PATCH 0574/1295] docs: re-word clarification about `setErrorHandler()` (#5269) Signed-off-by: codershiba <155646804+codershiba@users.noreply.github.com> --- docs/Reference/Errors.md | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index ecbf5d48f49..31633a77896 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -169,10 +169,21 @@ Some things to consider in your custom error handler: internally monitors the error invocation to avoid infinite loops for errors thrown in the reply phases of the lifecycle. (those after the route handler) -**Important**: When utilizing Fastify's custom error handling through -[`setErrorHandler`](./Server.md#seterrorhandler), -it's crucial to be aware of how errors -are propagated between custom and default error handlers. +When utilizing Fastify's custom error handling through [`setErrorHandler`](./Server.md#seterrorhandler), +you should be aware of how errors are propagated between custom and default +error handlers. + +If a plugin's error handler re-throws an error, and the error is not an +instance of [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) +(as seen in the `/bad` route in the following example), it will not propagate +to the parent context error handler. Instead, it will be caught by the default +error handler. + +To ensure consistent error handling, it is recommended to throw instances of +`Error`. For instance, in the following example, replacing `throw 'foo'` with +`throw new Error('foo')` in the `/bad` route ensures that errors propagate through +the custom error handling chain as intended. This practice helps avoid potential +pitfalls when working with custom error handling in Fastify. For example: ```js @@ -217,23 +228,6 @@ fastify.listen({ port: 3000 }, function (err, address) { }) ``` -If a plugin's error handler re-throws -an error, and the error is not an instance of -[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) -(as seen in the `/bad` route), it -won't propagate to the parent context error handler. -Instead, it will be caught by the default error handler. - -To ensure consistent error handling, -it's recommended to throw instances of Error. -For instance, replacing `throw 'foo'` -with `throw new Error('foo')` in the -`/bad` route would ensure that errors -propagate through the custom error handling chain as intended. -This practice helps to avoid potential -pitfalls when working with custom -error handling in Fastify. - ### Fastify Error Codes From ac26d7b4cadca3a2673fca5853863be498839c60 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 21 Jan 2024 17:11:15 +0000 Subject: [PATCH 0575/1295] docs(ecosystem): remove unspported package (#5278) --- docs/Guides/Ecosystem.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index e8286cd3053..112ee3c0313 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -220,9 +220,6 @@ section. - [`apitally`](https://github.com/apitally/nodejs-client) Fastify plugin to integrate with [Apitally](https://apitally.io), a simple API monitoring & API key management solution. -- [`apollo-server-fastify`](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify) - Run an [Apollo Server](https://github.com/apollographql/apollo-server) to - serve GraphQL with Fastify. - [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for Kubernetes Liveness and Readiness Probes. - [`cls-rtracer`](https://github.com/puzpuzpuz/cls-rtracer) Fastify middleware From e68e849ce800e4e3485a7cb8766fb34e4067cdef Mon Sep 17 00:00:00 2001 From: Bosco Domingo <46006784+BoscoDomingo@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:43:50 +0100 Subject: [PATCH 0576/1295] docs: Fix Pino docs link (#5284) * chore: Fix Pino docs link and refactor to Semantic Line Breaks More info here: https://sembr.org/ I updated the Pino docs link as the previous one was broken and took advantage to use Semantic Line Breaks for easier updates in the future without changing the formatting Signed-off-by: Bosco Domingo <46006784+BoscoDomingo@users.noreply.github.com> * docs: Undo unnecessary changes from previous commit This reverts commit 099614ecd7c2368ecfd26d24d02f5dae5914cae5. --------- Signed-off-by: Bosco Domingo <46006784+BoscoDomingo@users.noreply.github.com> --- docs/Reference/Logging.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 69fd9286c56..0edeffa39b1 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -61,9 +61,9 @@ the Fastify instance: fastify.log.info('Something important happened!'); ``` -If you want to pass some options to the logger, just pass them to Fastify. You -can find all available options in the [Pino -documentation](https://github.com/pinojs/pino/blob/master/docs/api.md#pinooptions-stream). +If you want to pass some options to the logger, just pass them to Fastify. +You can find all available options in the +[Pino documentation](https://github.com/pinojs/pino/blob/master/docs/api.md#options). If you want to specify a file destination, use: ```js From 4415c2fb040da15d2f6e72b0d263e6459e34d496 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 26 Jan 2024 19:13:30 +0100 Subject: [PATCH 0577/1295] chore: add github sponsor (#5293) * chore: update funding Signed-off-by: Manuel Spigolon * chore: update EXPENSE_POLICY Signed-off-by: Manuel Spigolon * chore: update SPONSORS.md Signed-off-by: Manuel Spigolon * Update SPONSORS.md Co-authored-by: Aras Abbasi Signed-off-by: Manuel Spigolon --------- Signed-off-by: Manuel Spigolon Co-authored-by: Aras Abbasi --- EXPENSE_POLICY.md | 3 ++- SPONSORS.md | 3 ++- package.json | 10 ++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/EXPENSE_POLICY.md b/EXPENSE_POLICY.md index cf5844ba7ec..5e5bb7867fa 100644 --- a/EXPENSE_POLICY.md +++ b/EXPENSE_POLICY.md @@ -1,7 +1,8 @@ # Expense Policy Fastify collaborators accept donations through the [Open Collective](https://opencollective.com/fastify/) -platform to enhance the project and support the community. +platform and [GitHub Sponsors](https://github.com/sponsors/fastify) +to enhance the project and support the community. This Collective is run by and for the benefit of the independent contributors to the Fastify open source software project. diff --git a/SPONSORS.md b/SPONSORS.md index 68d3bf07bd2..6f5d8d5679d 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -4,7 +4,8 @@ All active sponsors of Fastify are listed here, in order of contribution! Our sponsors are the reason why we can work on some issues or features that otherwise would be impossible to do. -If you want to become a sponsor, please check out our [Open Collective page](https://opencollective.com/fastify). +If you want to become a sponsor, please check out our [Open Collective page](https://opencollective.com/fastify) +or [GitHub Sponsors](https://github.com/sponsors/fastify)! ## Tier 4 diff --git a/package.json b/package.json index 3bfe826a8c8..1506b5741ee 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,16 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "devDependencies": { "@fastify/pre-commit": "^2.0.2", "@sinclair/typebox": "^0.31.17", From d2142014c446cfafa4f6eefe427b5f6218705c6d Mon Sep 17 00:00:00 2001 From: Yoav Gal <45267791+yoav0gal@users.noreply.github.com> Date: Sun, 28 Jan 2024 00:46:30 +0200 Subject: [PATCH 0578/1295] docs(ecosystem): adds fastify-sqlite-typed to the community plugins list (#5288) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 112ee3c0313..aaba63fe1f6 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -616,6 +616,8 @@ middlewares into Fastify plugins HTTP part of the request. - [`fastify-sqlite`](https://github.com/Eomm/fastify-sqlite) connects your application to a sqlite3 database. +- [`fastify-sqlite-typed`](https://github.com/yoav0gal/fastify-sqlite-typed) connects + your application to a SQLite database with full Typescript support. - [`fastify-sse`](https://github.com/lolo32/fastify-sse) to provide Server-Sent Events with `reply.sse( … )` to Fastify. - [`fastify-sse-v2`](https://github.com/nodefactoryio/fastify-sse-v2) to provide From 62f564d965949bc123184a27a610f214f23e9a49 Mon Sep 17 00:00:00 2001 From: Atila Guler <128936466+atilagulers@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:24:24 +0300 Subject: [PATCH 0579/1295] docs: add ESM usage example in Getting Started (#5294) --- docs/Guides/Getting-Started.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index 547ef7196c6..ec0ed1978f5 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -55,6 +55,14 @@ fastify.listen({ port: 3000 }, function (err, address) { }) ``` +> If you are using ECMAScript Modules (ESM) in your project, be sure to +> include "type": "module" in your package.json. +>```js +>{ +> "type": "module" +>} +>``` + Do you prefer to use `async/await`? Fastify supports it out-of-the-box. ```js @@ -172,6 +180,7 @@ fastify.listen({ port: 3000 }, function (err, address) { }) ``` + ```js // our-first-route.js @@ -186,6 +195,10 @@ async function routes (fastify, options) { }) } +//ESM +export default routes; + +// CommonJs module.exports = routes ``` In this example, we used the `register` API, which is the core of the Fastify From bdd647d522e0d09320133eac48ddc0bc3706241f Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 29 Jan 2024 09:01:05 +0000 Subject: [PATCH 0580/1295] docs: repoint readers to shared `.github` files (#5268) --- CODE_OF_CONDUCT.md | 178 +-------------------------------------------- GOVERNANCE.md | 107 +-------------------------- SECURITY.md | 159 +--------------------------------------- 3 files changed, 6 insertions(+), 438 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 80320a25a5d..9c322c23b55 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,178 +1,4 @@ # Code of Conduct -Fastify, as member project of the OpenJS Foundation, use [Contributor Covenant -v2.0](https://contributor-covenant.org/version/2/0/code_of_conduct) as their -code of conduct. The full text is included -[below](#contributor-covenant-code-of-conduct) in English, and translations are -available from the Contributor Covenant organisation: - -- [contributor-covenant.org/translations](https://www.contributor-covenant.org/translations) -- [github.com/ContributorCovenant](https://github.com/ContributorCovenant/contributor_covenant/tree/release/content/version/2/0) - -Refer to the sections on reporting and escalation in this document for the -specific emails that can be used to report and escalate issues. - -## Reporting - -### Project Spaces - -For reporting issues in spaces related to Fastify please use the email -`hello@matteocollina.com` or `tommydelved@gmail.com`. Fastify handles CoC issues -related to the spaces that it maintains. Projects maintainers commit to: - -- maintain the confidentiality with regard to the reporter of an incident -- to participate in the path for escalation as outlined in the section on - Escalation when required. - -### Foundation Spaces - -For reporting issues in spaces managed by the OpenJS Foundation, for example, -repositories within the OpenJS organization, use the email -`report@lists.openjsf.org`. The Cross Project Council (CPC) is responsible for -managing these reports and commits to: - -- maintain the confidentiality with regard to the reporter of an incident -- to participate in the path for escalation as outlined in the section on - Escalation when required. - -## Escalation - -The OpenJS Foundation maintains a Code of Conduct Panel (CoCP). This is a -foundation-wide team established to manage escalation when a reporter believes -that a report to a member project or the CPC has not been properly handled. In -order to escalate to the CoCP send an email to -`coc-escalation@lists.openjsf.org`. - -For more information, refer to the full [Code of Conduct governance -document](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/CODE_OF_CONDUCT.md). - ---- - -## Contributor Covenant Code of Conduct v2.0 - -### Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -### Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -### Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -### Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -### Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at the email -addresses listed above in the [Reporting](#reporting) and -[Escalation](#escalation) sections. All complaints will be reviewed and -investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -### Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -#### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -#### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -#### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -#### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -project community. - -### Attribution - -This Code of Conduct is adapted from the [Contributor -Covenant](https://www.contributor-covenant.org), version 2.0, available at -[contributor-covenant.org/version/2/0/code_of_conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct). - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -For answers to common questions about this code of conduct, see the FAQ at -[contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). -Translations are available at -[contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). +Please see Fastify's [organization-wide code of conduct +](https://github.com/fastify/.github/blob/main/CODE_OF_CONDUCT.md). \ No newline at end of file diff --git a/GOVERNANCE.md b/GOVERNANCE.md index a8950ce53b9..c1d8fab6dbe 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,107 +1,4 @@ # Fastify Project Governance - - -* [Lead Maintainers](#lead-maintainers) -* [Collaborators](#collaborators) - * [Collaborator activities](#collaborator-activities) -* [Great Contributors](#great-contributors) -* [Collaborator nominations](#collaborator-maintainers-nominations) -* [Lead Maintainers nominations](#lead-maintainers-nominations) -* [Consensus seeking process](#consensus-seeking-process) - - - -## Lead Maintainers - -Fastify Lead Maintainers are the organization owners. -They are the only members of the `@fastify/leads` team. The Lead -Maintainers are the curator of the Fastify project and their key responsibility -is to issue releases of Fastify and its dependencies. -They manage the [Open Collective](./EXPENSE_POLICY.md) funds and are responsible -for approving expenses and invoices. - -## Collaborators - -Fastify Collaborators maintain the projects of the Fastify organization. - -They are split into the following teams: - -| Team | Responsibility | Repository | -|---|---|---| -| `@fastify/leads` | Fastify Lead Maintainers | GitHub organization owners | -| `@fastify/core` | Fastify Core development | `fastify`, `fast-json-stringify`, `light-my-request`, `fastify-plugin`, `middie` | -| `@fastify/plugins` | Build, maintain and release Fastify plugins | All plugins repositories | -| `@fastify/benchmarks` | Build and maintain our benchmarks suite | `benchmarks` | -| `@fastify/docs-chinese` | Translate the Fastify documentation in Chinese | `docs-chinese` | - -Every member of the org is also part of `@fastify/fastify`. - -Collaborators have: - -* Commit access to the projects repository of the team they belong - * Grant to release new versions of the project - -Both Collaborators and non-Collaborators may propose changes to the source code -of the projects of the organization. The mechanism to propose such a change is a -GitHub pull request. Collaborators review and merge (_land_) pull requests -following the [CONTRIBUTING](CONTRIBUTING.md#rules) guidelines. - -### Collaborator activities - -* Helping users and novice contributors -* Contributing code and documentation changes that improve the project -* Reviewing and commenting on issues and pull requests -* Participation in working groups -* Merging pull requests -* Release plugins - -The Lead Maintainers can remove inactive Collaborators or provide them with -_Past Collaborators_ status. Past Collaborators may request that the Lead -Maintainers restore them to active status. - - -## Great Contributors - -Great contributors on a specific area in the Fastify ecosystem will be invited -to join this group by Lead Maintainers. This group has the same permissions of a -contributor. - -## Collaborator nominations - -Individuals making significant and valuable contributions to the project may be -a candidate to join the Fastify organization. - -A Collaborator needs to open a private team discussion on GitHub and list the -candidates they want to sponsor with a link to the user's contributions. For -example: - -* Activities in the Fastify organization - `[USERNAME](https://github.com/search?q=author:USERNAME+org:fastify)` - -Otherwise, a Contributor may self-apply if they believe they meet the above -criteria by reaching out to a Lead Maintainer privately with the links to their -valuable contributions. The Lead Maintainers will reply to the Contributor and -will decide if candidate it to be made a collaborator. - -The consensus to grant a new candidate Collaborator status is reached when: - -- at least one of the Lead Maintainers approve -- at least two of the Team Members approve - -After these conditions are satisfied, the [onboarding -process](CONTRIBUTING.md#onboarding-collaborators) may start. - - -## Lead Maintainers nominations - -A Team Member may be promoted to a Lead Maintainers only through nomination by a -Lead maintainer and with agreement from the rest of Lead Maintainers. - - -## Consensus seeking process - -The Fastify organization follows a [Consensus Seeking][] decision-making model. - -[Consensus Seeking]: - https://en.wikipedia.org/wiki/Consensus-seeking_decision-making +Please see Fastify's [organization-wide governance +](https://github.com/fastify/.github/blob/main/GOVERNANCE.md) document. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md index 85f72e4bc77..c62a24c2bd1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,159 +1,4 @@ # Security Policy -This document describes the management of vulnerabilities for the Fastify -project and its official plugins. - -## Reporting vulnerabilities - -Individuals who find potential vulnerabilities in Fastify are invited to -complete a vulnerability report via the dedicated HackerOne page: -[https://hackerone.com/fastify](https://hackerone.com/fastify). - -### Strict measures when reporting vulnerabilities - -It is of the utmost importance that you read carefully and follow these -guidelines to ensure the ecosystem as a whole isn't disrupted due to improperly -reported vulnerabilities: - -* Avoid creating new "informative" reports on HackerOne. Only create new - HackerOne reports on a vulnerability if you are absolutely sure this should be - tagged as an actual vulnerability. Third-party vendors and individuals are - tracking any new vulnerabilities reported in HackerOne and will flag them as - such for their customers (think about snyk, npm audit, ...). -* HackerOne reports should never be created and triaged by the same person. If - you are creating a HackerOne report for a vulnerability that you found, or on - behalf of someone else, there should always be a 2nd Security Team member who - triages it. If in doubt, invite more Fastify Collaborators to help triage the - validity of the report. In any case, the report should follow the same process - as outlined below of inviting the maintainers to review and accept the - vulnerability. -* ***Do not*** attempt to show CI/CD vulnerabilities by creating new pull - requests to any of the Fastify organization's repositories. Doing so will - result in a [content report][cr] to GitHub as an unsolicited exploit. - The proper way to provide such reports is by creating a new repository, - configured in the same manner as the repository you would like to submit - a report about, and with a pull request to your own repository showing - the proof of concept. - -[cr]: https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam#reporting-an-issue-or-pull-request - -### Vulnerabilities found outside this process - -⚠ The Fastify project does not support any reporting outside the HackerOne -process. - -## Handling vulnerability reports - -When a potential vulnerability is reported, the following actions are taken: - -### Triage - -**Delay:** 4 business days - -Within 4 business days, a member of the security team provides a first answer to -the individual who submitted the potential vulnerability. The possible responses -can be: - -* Acceptance: what was reported is considered as a new vulnerability -* Rejection: what was reported is not considered as a new vulnerability -* Need more information: the security team needs more information in order to - evaluate what was reported. - -Triaging should include updating issue fields: -* Asset - set/create the module affected by the report -* Severity - TBD, currently left empty - -Reference: [HackerOne: Submitting -Reports](https://docs.hackerone.com/hackers/submitting-reports.html) - -### Correction follow-up - -**Delay:** 90 days - -When a vulnerability is confirmed, a member of the security team volunteers to -follow up on this report. - -With the help of the individual who reported the vulnerability, they contact the -maintainers of the vulnerable package to make them aware of the vulnerability. -The maintainers can be invited as participants to the reported issue. - -With the package maintainer, they define a release date for the publication of -the vulnerability. Ideally, this release date should not happen before the -package has been patched. - -The report's vulnerable versions upper limit should be set to: -* `*` if there is no fixed version available by the time of publishing the - report. -* the last vulnerable version. For example: `<=1.2.3` if a fix exists in `1.2.4` - -### Publication - -**Delay:** 90 days - -Within 90 days after the triage date, the vulnerability must be made public. - -**Severity**: Vulnerability severity is assessed using [CVSS -v.3](https://www.first.org/cvss/user-guide). More information can be found on -[HackerOne documentation](https://docs.hackerone.com/hackers/severity.html) - -If the package maintainer is actively developing a patch, an additional delay -can be added with the approval of the security team and the individual who -reported the vulnerability. - -At this point, a CVE should be requested through the HackerOne platform through -the UI, which should include the Report ID and a summary. - -Within HackerOne, this is handled through a "public disclosure request". - -Reference: [HackerOne: -Disclosure](https://docs.hackerone.com/hackers/disclosure.html) - -## The Fastify Security team - -The core team is responsible for the management of HackerOne program and this -policy and process. - -Members of this team are expected to keep all information that they have -privileged access to by being on the team completely private to the team. This -includes agreeing to not notify anyone outside the team of issues that have not -yet been disclosed publicly, including the existence of issues, expectations of -upcoming releases, and patching of any issues other than in the process of their -work as a member of the Fastify Core team. - -### Members - -* [__Matteo Collina__](https://github.com/mcollina), - , -* [__Tomas Della Vedova__](https://github.com/delvedor), - , -* [__Vincent Le Goff__](https://github.com/zekth) -* [__KaKa Ng__](https://github.com/climba03003) -* [__James Sumners__](https://github.com/jsumners), - , - -## OpenSSF CII Best Practices - -[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585) - -There are three “tiers”: passing, silver, and gold. - -### Passing -We meet 100% of the “passing” criteria. - -### Silver -We meet 87% of the “silver” criteria. The gaps are as follows: - - we do not have a DCO or a CLA process for contributions. - - we do not currently document - “what the user can and cannot expect in terms of security” for our project. - - we do not currently document ”the architecture (aka high-level design)” - for our project. - -### Gold -We meet 70% of the “gold” criteria. The gaps are as follows: - - we do not yet have the “silver” badge; see all the gaps above. - - We do not include a copyright or license statement in each source file. - Efforts are underway to change this archaic practice into a - suggestion instead of a hard requirement. - - There are a few unanswered questions around cryptography that are - waiting for clarification. - +Please see Fastify's [organization-wide security policy +](https://github.com/fastify/.github/blob/main/SECURITY.md). \ No newline at end of file From 101ba57f894281421d902bf509adb4270473799d Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:06:36 +0800 Subject: [PATCH 0581/1295] feat: Web Stream API (#5286) * feat: Web Stream API * chore: update error PR number * test: skip test when not available * test: exit when skip test * test: use Readable.toWeb instead * docs: update ToC and onSend hook * refactor: toString * refactor: direct for-of payload.headers * test: ensure compatibility of third-party Response * refactor: reduce toString call --- docs/Reference/Errors.md | 3 + docs/Reference/Hooks.md | 2 +- docs/Reference/Reply.md | 48 ++++++++ lib/errors.js | 4 + lib/reply.js | 57 +++++++++- test/internals/errors.test.js | 24 ++-- test/web-api.test.js | 208 ++++++++++++++++++++++++++++++++++ 7 files changed, 336 insertions(+), 10 deletions(-) create mode 100644 test/web-api.test.js diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 31633a77896..c1149c7e024 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -45,6 +45,7 @@ - [FST_ERR_LOG_INVALID_DESTINATION](#fst_err_log_invalid_destination) - [FST_ERR_LOG_INVALID_LOGGER](#fst_err_log_invalid_logger) - [FST_ERR_REP_INVALID_PAYLOAD_TYPE](#fst_err_rep_invalid_payload_type) + - [FST_ERR_REP_RESPONSE_BODY_CONSUMED](#fst_err_rep_response_body_consumed) - [FST_ERR_REP_ALREADY_SENT](#fst_err_rep_already_sent) - [FST_ERR_REP_SENT_VALUE](#fst_err_rep_sent_value) - [FST_ERR_SEND_INSIDE_ONERR](#fst_err_send_inside_onerr) @@ -312,6 +313,8 @@ Below is a table with all the error codes that Fastify uses. | FST_ERR_LOG_INVALID_DESTINATION | The logger does not accept the specified destination. | Use a `'stream'` or a `'file'` as the destination. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_LOG_INVALID_LOGGER | The logger should have all these methods: `'info'`, `'error'`, `'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`. | Use a logger with all the required methods. | [#4520](https://github.com/fastify/fastify/pull/4520) | | FST_ERR_REP_INVALID_PAYLOAD_TYPE | Reply payload can be either a `string` or a `Buffer`. | Use a `string` or a `Buffer` for the payload. | [#1168](https://github.com/fastify/fastify/pull/1168) | +| FST_ERR_REP_RESPONSE_BODY_CONSUMED | Using `Response` as reply payload +but the body is being consumed. | Make sure you don't consume the `Response.body` | [#5286](https://github.com/fastify/fastify/pull/5286) | | FST_ERR_REP_ALREADY_SENT | A response was already sent. | - | [#1336](https://github.com/fastify/fastify/pull/1336) | | FST_ERR_REP_SENT_VALUE | The only possible value for `reply.sent` is `true`. | - | [#1336](https://github.com/fastify/fastify/pull/1336) | | FST_ERR_SEND_INSIDE_ONERR | You cannot use `send` inside the `onError` hook. | - | [#1348](https://github.com/fastify/fastify/pull/1348) | diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 0d3cb9df709..ce316f78c6b 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -232,7 +232,7 @@ fastify.addHook('onSend', (request, reply, payload, done) => { > `null`. Note: If you change the payload, you may only change it to a `string`, a -`Buffer`, a `stream`, or `null`. +`Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`. ### onResponse diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 2307c299825..430457212b2 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -33,6 +33,8 @@ - [Strings](#strings) - [Streams](#streams) - [Buffers](#buffers) + - [ReadableStream](#send-readablestream) + - [Response](#send-response) - [Errors](#errors) - [Type of the final payload](#type-of-the-final-payload) - [Async-Await and Promises](#async-await-and-promises) @@ -756,6 +758,52 @@ fastify.get('/streams', function (request, reply) { }) ``` +#### ReadableStream + + +`ReadableStream` will be treated as a node stream mentioned above, +the content is considered to be pre-serialized, so they will be +sent unmodified without response validation. + +```js +const fs = require('node:fs') +const { ReadableStream } = require('node:stream/web') +fastify.get('/streams', function (request, reply) { + const stream = fs.createReadStream('some-file') + reply.header('Content-Type', 'application/octet-stream') + reply.send(ReadableStream.from(stream)) +}) +``` + +#### Response + + +`Response` allows to manage the reply payload, status code and +headers in one place. The payload provided inside `Response` is +considered to be pre-serialized, so they will be sent unmodified +without response validation. + +Plese be aware when using `Response`, the status code and headers +will not directly reflect to `reply.statusCode` and `reply.getHeaders()`. +Such behavior is based on `Response` only allow `readonly` status +code and headers. The data is not allow to be bi-direction editing, +and may confuse when checking the `payload` in `onSend` hooks. + +```js +const fs = require('node:fs') +const { ReadableStream } = require('node:stream/web') +fastify.get('/streams', function (request, reply) { + const stream = fs.createReadStream('some-file') + const readableStream = ReadableStream.from(stream) + const response = new Response(readableStream, { + status: 200, + headers: { 'content-type': 'application/octet-stream' } + }) + reply.send(response) +}) +``` + + #### Errors diff --git a/lib/errors.js b/lib/errors.js index d800d23ae74..e3c8811e476 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -212,6 +212,10 @@ const codes = { 500, TypeError ), + FST_ERR_REP_RESPONSE_BODY_CONSUMED: createError( + 'FST_ERR_REP_RESPONSE_BODY_CONSUMED', + 'Response.body is already consumed.' + ), FST_ERR_REP_ALREADY_SENT: createError( 'FST_ERR_REP_ALREADY_SENT', 'Reply was already sent, did you forget to "return reply" in "%s" (%s)?' diff --git a/lib/reply.js b/lib/reply.js index e4de3430b3d..33c47951613 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -1,6 +1,7 @@ 'use strict' const eos = require('node:stream').finished +const Readable = require('node:stream').Readable const { kFourOhFourContext, @@ -44,6 +45,7 @@ const CONTENT_TYPE = { } const { FST_ERR_REP_INVALID_PAYLOAD_TYPE, + FST_ERR_REP_RESPONSE_BODY_CONSUMED, FST_ERR_REP_ALREADY_SENT, FST_ERR_REP_SENT_VALUE, FST_ERR_SEND_INSIDE_ONERR, @@ -55,6 +57,8 @@ const { } = require('./errors') const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020 } = require('./warnings') +const toString = Object.prototype.toString + function Reply (res, request, log) { this.raw = res this[kReplySerializer] = null @@ -163,7 +167,14 @@ Reply.prototype.send = function (payload) { const hasContentType = contentType !== undefined if (payload !== null) { - if (typeof payload.pipe === 'function') { + if ( + // node:stream + typeof payload.pipe === 'function' || + // node:stream/web + typeof payload.getReader === 'function' || + // Response + toString.call(payload) === '[object Response]' + ) { onSendHook(this, payload) return this } @@ -570,7 +581,6 @@ function safeWriteHead (reply, statusCode) { function onSendEnd (reply, payload) { const res = reply.raw const req = reply.request - const statusCode = res.statusCode // we check if we need to update the trailers header and set it if (reply[kReplyTrailers] !== null) { @@ -586,6 +596,17 @@ function onSendEnd (reply, payload) { reply.header('Trailer', header.trim()) } + // since Response contain status code, we need to update before + // any action that used statusCode + const isResponse = toString.call(payload) === '[object Response]' + if (isResponse) { + // https://developer.mozilla.org/en-US/docs/Web/API/Response/status + if (typeof payload.status === 'number') { + reply.code(payload.status) + } + } + const statusCode = res.statusCode + if (payload === undefined || payload === null) { // according to https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 // we cannot send a content-length for 304 and 204, and all status code @@ -617,11 +638,38 @@ function onSendEnd (reply, payload) { return } + // node:stream if (typeof payload.pipe === 'function') { sendStream(payload, res, reply) return } + // node:stream/web + if (typeof payload.getReader === 'function') { + sendWebStream(payload, res, reply) + return + } + + // Response + if (isResponse) { + // https://developer.mozilla.org/en-US/docs/Web/API/Response/headers + if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') { + for (const [headerName, headerValue] of payload.headers) { + reply.header(headerName, headerValue) + } + } + + // https://developer.mozilla.org/en-US/docs/Web/API/Response/body + if (payload.body != null) { + if (payload.bodyUsed) { + throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED() + } + // Response.body always a ReadableStream + sendWebStream(payload.body, res, reply) + } + return + } + if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) { throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload) } @@ -654,6 +702,11 @@ function logStreamError (logger, err, res) { } } +function sendWebStream (payload, res, reply) { + const nodeStream = Readable.fromWeb(payload) + sendStream(nodeStream, res, reply) +} + function sendStream (payload, res, reply) { let sourceOpen = true let errorLogged = false diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 4ab0709b207..eaa64263648 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -test('should expose 78 errors', t => { +test('should expose 79 errors', t => { t.plan(1) const exportedKeys = Object.keys(errors) let counter = 0 @@ -14,11 +14,11 @@ test('should expose 78 errors', t => { counter++ } } - t.equal(counter, 78) + t.equal(counter, 79) }) test('ensure name and codes of Errors are identical', t => { - t.plan(78) + t.plan(79) const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -337,6 +337,16 @@ test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => { t.ok(error instanceof TypeError) }) +test('FST_ERR_REP_RESPONSE_BODY_CONSUMED', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_RESPONSE_BODY_CONSUMED() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') + t.equal(error.message, 'Response.body is already consumed.') + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + test('FST_ERR_REP_ALREADY_SENT', t => { t.plan(5) const error = new errors.FST_ERR_REP_ALREADY_SENT('/hello', 'GET') @@ -818,7 +828,7 @@ test('FST_ERR_LISTEN_OPTIONS_INVALID', t => { }) test('Ensure that all errors are in Errors.md TOC', t => { - t.plan(78) + t.plan(79) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -830,7 +840,7 @@ test('Ensure that all errors are in Errors.md TOC', t => { }) test('Ensure that non-existing errors are not in Errors.md TOC', t => { - t.plan(78) + t.plan(79) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = / {4}- \[([A-Z0-9_]+)\]\(#[a-z0-9_]+\)/g @@ -843,7 +853,7 @@ test('Ensure that non-existing errors are not in Errors.md TOC', t => { }) test('Ensure that all errors are in Errors.md documented', t => { - t.plan(78) + t.plan(79) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -855,7 +865,7 @@ test('Ensure that all errors are in Errors.md documented', t => { }) test('Ensure that non-existing errors are not in Errors.md documented', t => { - t.plan(78) + t.plan(79) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /([0-9a-zA-Z_]+)<\/a>/g diff --git a/test/web-api.test.js b/test/web-api.test.js new file mode 100644 index 00000000000..6c32279410c --- /dev/null +++ b/test/web-api.test.js @@ -0,0 +1,208 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('../fastify') +const fs = require('node:fs') +const semver = require('semver') +const { Readable } = require('node:stream') +const { fetch: undiciFetch } = require('undici') + +if (semver.lt(process.versions.node, '18.0.0')) { + t.skip('Response or ReadableStream not available, skipping test') + process.exit(0) +} + +test('should response with a ReadableStream', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = fs.createReadStream(__filename) + reply.code(200).send(Readable.toWeb(stream)) + }) + + const { + statusCode, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + const expected = await fs.promises.readFile(__filename) + + t.equal(statusCode, 200) + t.equal(expected.toString(), body.toString()) +}) + +test('should response with a Response', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = fs.createReadStream(__filename) + reply.send(new Response(Readable.toWeb(stream), { + status: 200, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + const expected = await fs.promises.readFile(__filename) + + t.equal(statusCode, 200) + t.equal(expected.toString(), body.toString()) + t.equal(headers.hello, 'world') +}) + +test('able to use in onSend hook - ReadableStream', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = fs.createReadStream(__filename) + reply.code(500).send(Readable.toWeb(stream)) + }) + + fastify.addHook('onSend', (request, reply, payload, done) => { + t.equal(Object.prototype.toString.call(payload), '[object ReadableStream]') + done(null, new Response(payload, { + status: 200, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + const expected = await fs.promises.readFile(__filename) + + t.equal(statusCode, 200) + t.equal(expected.toString(), body.toString()) + t.equal(headers.hello, 'world') +}) + +test('able to use in onSend hook - Response', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = fs.createReadStream(__filename) + reply.send(new Response(Readable.toWeb(stream), { + status: 500, + headers: { + hello: 'world' + } + })) + }) + + fastify.addHook('onSend', (request, reply, payload, done) => { + t.equal(Object.prototype.toString.call(payload), '[object Response]') + done(null, new Response(payload.body, { + status: 200, + headers: payload.headers + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + const expected = await fs.promises.readFile(__filename) + + t.equal(statusCode, 200) + t.equal(expected.toString(), body.toString()) + t.equal(headers.hello, 'world') +}) + +test('Error when Response.bodyUsed', async (t) => { + t.plan(4) + + const expected = await fs.promises.readFile(__filename) + + const fastify = Fastify() + + fastify.get('/', async function (request, reply) { + const stream = fs.createReadStream(__filename) + const response = new Response(Readable.toWeb(stream), { + status: 200, + headers: { + hello: 'world' + } + }) + const file = await response.text() + t.equal(expected.toString(), file) + t.equal(response.bodyUsed, true) + return reply.send(response) + }) + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.equal(response.statusCode, 500) + const body = response.json() + t.equal(body.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') +}) + +test('allow to pipe with fetch', async (t) => { + t.plan(2) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (request, reply) { + return fetch(`${fastify.listeningOrigin}/fetch`, { + method: 'GET' + }) + }) + + fastify.get('/fetch', function (request, reply) { + reply.code(200).send({ ok: true }) + }) + + await fastify.listen() + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.equal(response.statusCode, 200) + t.same(response.json(), { ok: true }) +}) + +test('allow to pipe with undici.fetch', async (t) => { + t.plan(2) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/', function (request, reply) { + return undiciFetch(`${fastify.listeningOrigin}/fetch`, { + method: 'GET' + }) + }) + + fastify.get('/fetch', function (request, reply) { + reply.code(200).send({ ok: true }) + }) + + await fastify.listen() + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.equal(response.statusCode, 200) + t.same(response.json(), { ok: true }) +}) From 1211962b9323d82da7aeae5cd3f641f8ec28c877 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 29 Jan 2024 18:50:06 +0100 Subject: [PATCH 0582/1295] chore: sync generated code (#5295) --- lib/error-serializer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/error-serializer.js b/lib/error-serializer.js index de1c3927319..f209e86e598 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -23,6 +23,8 @@ ? input.toJSON() : input + if (obj === null) return '{}' + let addComma = false let json = '{' From e10ae402f5736b03dc7e9c6c2541bf87c01df2c9 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 29 Jan 2024 19:52:49 +0100 Subject: [PATCH 0583/1295] Bumped v4.26.0 --- fastify.js | 2 +- package.json | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/fastify.js b/fastify.js index 8092d72686c..94bfbfcd256 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.25.2' +const VERSION = '4.26.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 1506b5741ee..c7c7b187c62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.25.2", + "version": "4.26.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", @@ -142,10 +142,6 @@ "url": "https://github.com/fastify/fastify/issues" }, "homepage": "https://fastify.dev/", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - }, "funding": [ { "type": "github", From 9c0389674d26da03bdf7bcb9692ddc8a5ea09b20 Mon Sep 17 00:00:00 2001 From: Yoav Gal <45267791+yoav0gal@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:29:56 +0200 Subject: [PATCH 0584/1295] docs(ecosystem): adds fastify-hana to the community plugins list (#5289) * docs(ecosystem): adds fastify-hana to the community plugins list * docs(Ecosystem): changed fastify-hana placemet to fit in a-z order * docs(ecosystem): adds fastify-hana to the community plugins list Signed-off-by: Yoav Gal <45267791+yoav0gal@users.noreply.github.com> --------- Signed-off-by: Yoav Gal <45267791+yoav0gal@users.noreply.github.com> --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index aaba63fe1f6..afc3b63754d 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -386,6 +386,8 @@ section. Providers. - [`fastify-guard`](https://github.com/hsynlms/fastify-guard) A Fastify plugin that protects endpoints by checking authenticated user roles and/or scopes. +- [`fastify-hana`](https://github.com/yoav0gal/fastify-hana) connects your + application to [`SAP-HANA`](https://help.sap.com/docs/SAP_HANA_CLIENT). - [`fastify-hashids`](https://github.com/andersonjoseph/fastify-hashids) A Fastify plugin to encode/decode IDs using [hashids](https://github.com/niieani/hashids.js). - [`fastify-hasura`](https://github.com/ManUtopiK/fastify-hasura) A Fastify From 5fe74e58de4de685283794c9e520ef800d5f6f59 Mon Sep 17 00:00:00 2001 From: Michael Scott-Nelson Date: Wed, 31 Jan 2024 23:17:53 -0500 Subject: [PATCH 0585/1295] fix misattributed property parent in deprecation warning: request.elapsedTime -> reply.elapsedTime (#5299) --- lib/warnings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/warnings.js b/lib/warnings.js index 0b446990264..0436c662aaa 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -79,7 +79,7 @@ const FSTDEP019 = createDeprecation({ const FSTDEP020 = createDeprecation({ code: 'FSTDEP020', - message: 'You are using the deprecated "reply.getResponseTime()"" method. Use the "request.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.' + message: 'You are using the deprecated "reply.getResponseTime()" method. Use the "reply.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.' }) const FSTWRN001 = createWarning({ From 37b9b3d947d79ae8e5d26e62b9a4b11fb9812c75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:34:24 +0000 Subject: [PATCH 0586/1295] chore: Bump lycheeverse/lychee-action from 1.8.0 to 1.9.3 (#5300) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.8.0 to 1.9.3. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/ec3ed119d4f44ad2673a7232460dc7dff59d2421...c053181aa0c3d17606addfe97a9075a32723548a) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 37c4d27e524..ae96cd6f21b 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@ec3ed119d4f44ad2673a7232460dc7dff59d2421 + uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a with: fail: true # As external links behaviour is not predictable, we check only internal links From eb4ae3cf87e83ef4e00abd66b8fa9009b2efc585 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:36:00 +0000 Subject: [PATCH 0587/1295] chore: Bump actions/dependency-review-action from 3 to 4 (#5301) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 3 to 4. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afd33f4e3a4..82a1e98ec23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: persist-credentials: false - name: Dependency review - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 check-licenses: name: Check licenses From 42cf27ca459319f5682a14167220709d870827ab Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 4 Feb 2024 18:39:48 +0000 Subject: [PATCH 0588/1295] chore(.gitignore): add .tap/ dir (#5303) Signed-off-by: Frazer Smith --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 37985cb1fb9..021407e223f 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,9 @@ yarn.lock .vscode .idea +# tap files +.tap/ + # Optional compressed files (npm generated package, zip, etc) /*.zip From f26de5d22256b1980fac5abf30214967ec91527b Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 9 Feb 2024 12:51:22 +0100 Subject: [PATCH 0589/1295] fix: amend error codes for latest avvio v8.3.0 (#5309) * Fix tests for latest avvio Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina --- lib/errors.js | 3 ++- package.json | 2 +- test/hooks.on-ready.test.js | 2 +- test/plugin.4.test.js | 6 +++--- test/pretty-print.test.js | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index e3c8811e476..90ae13cb763 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -464,5 +464,6 @@ module.exports.AVVIO_ERRORS_MAP = { AVV_ERR_PLUGIN_NOT_VALID: codes.FST_ERR_PLUGIN_NOT_VALID, AVV_ERR_ROOT_PLG_BOOTED: codes.FST_ERR_ROOT_PLG_BOOTED, AVV_ERR_PARENT_PLG_LOADED: codes.FST_ERR_PARENT_PLUGIN_BOOTED, - AVV_ERR_READY_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT + AVV_ERR_READY_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT, + AVV_ERR_PLUGIN_EXEC_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT } diff --git a/package.json b/package.json index c7c7b187c62..15487465139 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "@fastify/error": "^3.4.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", - "avvio": "^8.2.1", + "avvio": "^8.3.0", "fast-content-type-parse": "^1.1.0", "fast-json-stringify": "^5.8.0", "find-my-way": "^8.0.0", diff --git a/test/hooks.on-ready.test.js b/test/hooks.on-ready.test.js index c61ee60b4fd..4e33f31fcf1 100644 --- a/test/hooks.on-ready.test.js +++ b/test/hooks.on-ready.test.js @@ -295,7 +295,7 @@ t.test('onReady cannot add lifecycle hooks', t => { t.ok(error) t.equal(error.message, 'Root plugin has already booted') // TODO: look where the error pops up - t.equal(error.code, 'AVV_ERR_PLUGIN_NOT_VALID') + t.equal(error.code, 'AVV_ERR_ROOT_PLG_BOOTED') done(error) } }) diff --git a/test/plugin.4.test.js b/test/plugin.4.test.js index ac3d419ba12..b14c881a5be 100644 --- a/test/plugin.4.test.js +++ b/test/plugin.4.test.js @@ -22,7 +22,7 @@ test('pluginTimeout', t => { "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // to no call done on purpose'. You may have forgotten to call 'done' function or to resolve a Promise") t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + t.equal(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') }) }) @@ -40,7 +40,7 @@ test('pluginTimeout - named function', t => { "fastify-plugin: Plugin did not start in time: 'nameFunction'. You may have forgotten to call 'done' function or to resolve a Promise") t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + t.equal(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') }) }) @@ -60,7 +60,7 @@ test('pluginTimeout default', t => { "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // default time elapsed without calling done'. You may have forgotten to call 'done' function or to resolve a Promise") t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + t.equal(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') }) t.teardown(clock.uninstall) diff --git a/test/pretty-print.test.js b/test/pretty-print.test.js index 08d45ace5e4..8c8713e798e 100644 --- a/test/pretty-print.test.js +++ b/test/pretty-print.test.js @@ -220,7 +220,7 @@ test('pretty print - empty plugins', t => { fastify.ready(() => { const tree = fastify.printPlugins() t.equal(typeof tree, 'string') - t.match(tree, 'bound root') + t.match(tree, /root \d+ ms\n└── bound _after \d+ ms/m) }) }) From 2f4f5f5c627352ed022dd00545f23d33de0a26ad Mon Sep 17 00:00:00 2001 From: Roberto Bianchi Date: Fri, 9 Feb 2024 14:52:39 +0100 Subject: [PATCH 0590/1295] fix(types): Request route options url add undefined (#5307) * fix(types): Request route options url add undefined * Add test Signed-off-by: Roberto Bianchi --------- Signed-off-by: Roberto Bianchi Co-authored-by: Matteo Collina --- test/types/request.test-d.ts | 2 +- types/request.d.ts | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 08218127f4c..5d81eb14c00 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -1,4 +1,3 @@ -import pino from 'pino' import { expectAssignable, expectType } from 'tsd' import fastify, { ContextConfigDefault, @@ -83,6 +82,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.routeSchema) expectType(request.routeOptions.schema) expectType(request.routeOptions.handler) + expectType(request.routeOptions.url) expectType(request.headers) request.headers = {} diff --git a/types/request.d.ts b/types/request.d.ts index 5bd9ae3bb11..59630ba378d 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -22,10 +22,11 @@ export interface ValidationFunction { export interface RequestRouteOptions { method: string; - url: string; - bodyLimit:number; - attachValidation:boolean; - logLevel:string; + // `url` can be `undefined` for instance when `request.is404` is true + url: string | undefined; + bodyLimit: number; + attachValidation: boolean; + logLevel: string; version: string | undefined; exposeHeadRoute: boolean; prefixTrailingSlash: string; From 3a646b863be964d1008785e7318ffe964aac6aff Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Sat, 10 Feb 2024 11:01:32 -0500 Subject: [PATCH 0591/1295] Add docs for tracing warnings (#5310) --- docs/Reference/Warnings.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 94c126a0e31..116c5d12098 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -29,8 +29,21 @@ ## Warnings ### Warnings In Fastify -Warnings are enabled by default. They can be disabled by using any -of the following methods: + +Fastify utilizes Node.js's [warning event](https://nodejs.org/api/process.html#event-warning) +API to notify users of deprecated features and known coding mistakes. Fastify's +warnings are recognizable by the `FSTWRN` and `FSTDEP` prefixes on warning +code. When encountering such a warning, it is highly recommended that the +cause of the warning be determined through use of the +[`--trace-warnings`](https://nodejs.org/api/cli.html#--trace-warnings) and +[`--trace-deprecation`](https://nodejs.org/api/cli.html#--trace-deprecation) +flags. These will produce stack traces pointing out where the issue occurs +in the application's code. Issues opened about warnings without including +this information may be closed due to lack of information. + +In addition to tracing, warnings can also be disabled. It is not recommended to +disable warnings as a matter of course, but if necessary, they can be disabled +by using any of the following methods: - setting the `NODE_NO_WARNINGS` environment variable to `1` - passing the `--no-warnings` flag to the node process From ffbc92c78a588e5ec6f16d20492f23b08654345f Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 12 Feb 2024 15:34:15 +0100 Subject: [PATCH 0592/1295] Bumped v4.26.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 57 ++++++++++++++++++++--------------------- package.json | 2 +- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/fastify.js b/fastify.js index 94bfbfcd256..a7f6058a16e 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.26.0' +const VERSION = '4.26.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index f209e86e598..b6ba2db79b0 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -25,35 +25,34 @@ if (obj === null) return '{}' - - let addComma = false - let json = '{' - - if (obj["statusCode"] !== undefined) { - !addComma && (addComma = true) || (json += ',') - json += "\"statusCode\":" - json += serializer.asNumber(obj["statusCode"]) - } - - if (obj["code"] !== undefined) { - !addComma && (addComma = true) || (json += ',') - json += "\"code\":" - json += serializer.asString(obj["code"]) - } - - if (obj["error"] !== undefined) { - !addComma && (addComma = true) || (json += ',') - json += "\"error\":" - json += serializer.asString(obj["error"]) - } - - if (obj["message"] !== undefined) { - !addComma && (addComma = true) || (json += ',') - json += "\"message\":" - json += serializer.asString(obj["message"]) - } - - return json + '}' + let json = '{' +let addComma = false + + if (obj["statusCode"] !== undefined) { + !addComma && (addComma = true) || (json += ',') + json += "\"statusCode\":" + json += serializer.asNumber(obj["statusCode"]) + } + + if (obj["code"] !== undefined) { + !addComma && (addComma = true) || (json += ',') + json += "\"code\":" + json += serializer.asString(obj["code"]) + } + + if (obj["error"] !== undefined) { + !addComma && (addComma = true) || (json += ',') + json += "\"error\":" + json += serializer.asString(obj["error"]) + } + + if (obj["message"] !== undefined) { + !addComma && (addComma = true) || (json += ',') + json += "\"message\":" + json += serializer.asString(obj["message"]) + } + + return json + '}' } diff --git a/package.json b/package.json index 15487465139..2c2045f3a02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.26.0", + "version": "4.26.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From f4b9a2a848da3620697d4bd8731fe6dc14f60e42 Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Tue, 13 Feb 2024 20:51:37 +0200 Subject: [PATCH 0593/1295] fix: typo in module exports Signed-off-by: Liran Tal --- docs/Guides/Serverless.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 2dfe0d82ced..d3c9e1bba1d 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -203,7 +203,7 @@ const fastifyFunction = async (request, reply) => { fastify.server.emit('request', request, reply) } -export.fastifyFunction = fastifyFunction; +exports.fastifyFunction = fastifyFunction; ``` ### Local test From 44ac4fbe45ac0d1b22a2018972fa71a07ba8e385 Mon Sep 17 00:00:00 2001 From: Roberto Bianchi Date: Wed, 14 Feb 2024 08:16:24 +0100 Subject: [PATCH 0594/1295] docs(ts): Fix links (#5308) * chore(ts): Fix links Signed-off-by: Roberto Bianchi * Use absolute URLs Signed-off-by: Roberto Bianchi * Revert Signed-off-by: Roberto Bianchi --------- Signed-off-by: Roberto Bianchi --- docs/Reference/TypeScript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 3fa07159e27..d339ef83c7f 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -1093,7 +1093,7 @@ server.get('/', async (request, reply) => { ``` If you want to see a detailed example of using this interface check out the -Learn by Example section: [JSON Schema](#jsonschema). +Learn by Example section: [JSON Schema](#json-schema). ##### fastify.RawRequestDefaultExpression\<[RawServer][RawServerGeneric]\> [src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L23) From 54f8e3c6c12f55358b9703de7e70a8866198ed52 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Wed, 14 Feb 2024 12:29:17 +0100 Subject: [PATCH 0595/1295] fix: cb is not a function at fallbackErrorHandler (#5317) * fix: cb is not a function at fallbackErrorHandler * Update docs/Reference/Errors.md Signed-off-by: Aras Abbasi --------- Signed-off-by: Aras Abbasi --- docs/Reference/Errors.md | 2 ++ fastify.js | 7 ++++++- lib/errors.js | 6 ++++++ test/internals/errors.test.js | 24 +++++++++++++++++------- test/set-error-handler.test.js | 13 +++++++++++++ 5 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 test/set-error-handler.test.js diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index c1149c7e024..070029e2d11 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -92,6 +92,7 @@ - [FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE](#fst_err_plugin_not_present_in_instance) - [FST_ERR_VALIDATION](#fst_err_validation) - [FST_ERR_LISTEN_OPTIONS_INVALID](#fst_err_listen_options_invalid) + - [FST_ERR_ERROR_HANDLER_NOT_FN](#fst_err_error_handler_not_fn) ### Error Handling In Node.js @@ -361,4 +362,5 @@ but the body is being consumed. | Make sure you don't consume the `Response.body | FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE | The decorator is not present in the instance. | - | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_VALIDATION | The Request failed the payload validation. | Check the request payload. | [#4824](https://github.com/fastify/fastify/pull/4824) | | FST_ERR_LISTEN_OPTIONS_INVALID | Invalid listen options. | Check the listen options. | [#4886](https://github.com/fastify/fastify/pull/4886) | +| FST_ERR_ERROR_HANDLER_NOT_FN | Error Handler must be a function | Provide a function to `setErrorHandler`. | [#5317](https://github.com/fastify/fastify/pull/5317) | diff --git a/fastify.js b/fastify.js index a7f6058a16e..d0030fbd756 100644 --- a/fastify.js +++ b/fastify.js @@ -71,7 +71,8 @@ const { FST_ERR_INSTANCE_ALREADY_LISTENING, FST_ERR_REOPENED_CLOSE_SERVER, FST_ERR_ROUTE_REWRITE_NOT_STR, - FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN + FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN, + FST_ERR_ERROR_HANDLER_NOT_FN } = errorCodes const { buildErrorHandler } = require('./lib/error-handler.js') @@ -844,6 +845,10 @@ function fastify (options) { function setErrorHandler (func) { throwIfAlreadyStarted('Cannot call "setErrorHandler"!') + if (typeof func !== 'function') { + throw new FST_ERR_ERROR_HANDLER_NOT_FN() + } + this[kErrorHandler] = buildErrorHandler(this[kErrorHandler], func.bind(this)) return this } diff --git a/lib/errors.js b/lib/errors.js index 90ae13cb763..e64f73cdfde 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -64,6 +64,12 @@ const codes = { 500, TypeError ), + FST_ERR_ERROR_HANDLER_NOT_FN: createError( + 'FST_ERR_ERROR_HANDLER_NOT_FN', + 'Error Handler must be a function', + 500, + TypeError + ), /** * ContentTypeParser diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index eaa64263648..0f47c45f537 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -test('should expose 79 errors', t => { +test('should expose 80 errors', t => { t.plan(1) const exportedKeys = Object.keys(errors) let counter = 0 @@ -14,11 +14,11 @@ test('should expose 79 errors', t => { counter++ } } - t.equal(counter, 79) + t.equal(counter, 80) }) test('ensure name and codes of Errors are identical', t => { - t.plan(79) + t.plan(80) const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -827,8 +827,18 @@ test('FST_ERR_LISTEN_OPTIONS_INVALID', t => { t.ok(error instanceof TypeError) }) +test('FST_ERR_ERROR_HANDLER_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_ERROR_HANDLER_NOT_FN() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_ERROR_HANDLER_NOT_FN') + t.equal(error.message, 'Error Handler must be a function') + t.equal(error.statusCode, 500) + t.ok(error instanceof TypeError) +}) + test('Ensure that all errors are in Errors.md TOC', t => { - t.plan(79) + t.plan(80) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -840,7 +850,7 @@ test('Ensure that all errors are in Errors.md TOC', t => { }) test('Ensure that non-existing errors are not in Errors.md TOC', t => { - t.plan(79) + t.plan(80) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = / {4}- \[([A-Z0-9_]+)\]\(#[a-z0-9_]+\)/g @@ -853,7 +863,7 @@ test('Ensure that non-existing errors are not in Errors.md TOC', t => { }) test('Ensure that all errors are in Errors.md documented', t => { - t.plan(79) + t.plan(80) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -865,7 +875,7 @@ test('Ensure that all errors are in Errors.md documented', t => { }) test('Ensure that non-existing errors are not in Errors.md documented', t => { - t.plan(79) + t.plan(80) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /([0-9a-zA-Z_]+)<\/a>/g diff --git a/test/set-error-handler.test.js b/test/set-error-handler.test.js new file mode 100644 index 00000000000..737fd02bb82 --- /dev/null +++ b/test/set-error-handler.test.js @@ -0,0 +1,13 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') +const { FST_ERR_ERROR_HANDLER_NOT_FN } = require('../lib/errors') + +test('setErrorHandler should throw an error if the handler is not a function', t => { + t.plan(1) + const fastify = Fastify() + + t.throws(() => fastify.setErrorHandler('not a function'), new FST_ERR_ERROR_HANDLER_NOT_FN()) +}) From 7e50e5307c9a6593a2e43171912630033aa1aacb Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Mon, 19 Feb 2024 17:42:52 +0200 Subject: [PATCH 0596/1295] feat: add a Firebase Functions step by step guide (#5318) * feat: add a Firebase Functions step by step guide This PR adds a new section to the Serverless Guide on the website that explains how to use Fastify as the wrapper around Firebase's own onRequest HTTP handler Signed-off-by: Liran Tal * Update docs/Guides/Serverless.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Liran Tal * Update docs/Guides/Serverless.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Liran Tal * Update docs/Guides/Serverless.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Liran Tal * Update docs/Guides/Serverless.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Liran Tal --------- Signed-off-by: Liran Tal Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> --- docs/Guides/Serverless.md | 110 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index d3c9e1bba1d..be434b967f8 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -26,6 +26,7 @@ snippet of code. - [AWS](#aws) - [Google Cloud Functions](#google-cloud-functions) +- [Google Firebase Functions](#google-firebase-functions) - [Google Cloud Run](#google-cloud-run) - [Netlify Lambda](#netlify-lambda) - [Platformatic Cloud](#platformatic-cloud) @@ -260,6 +261,115 @@ curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \ - [Google Cloud Functions - Node.js Quickstart ](https://cloud.google.com/functions/docs/quickstart-nodejs) +## Google Firebase Functions + +Follow this guide if you want to use Fastify as the HTTP framework for +Firebase Functions instead of the vanilla JavaScript router provided with +`onRequest(async (req, res) => {}`. + +### The onRequest() handler + +We use the `onRequest` function to wrap our Fastify application instance. + +As such, we'll begin with importing it to the code: + +```js +const { onRequest } = require("firebase-functions/v2/https") +``` + +### Creation of Fastify instance + +Create the Fastify instance and encapsulate the returned application instance +in a function which will register routes, await the server's processing of +plugins, hooks and other settings. As follows: + +```js +const fastify = require("fastify")({ + logger: true, +}) + +const fastifyApp = async (request, reply) => { + await registerRoutes(fastify) + await fastify.ready() + fastify.server.emit("request", request, reply) +} +``` + +### Add Custom `contentTypeParser` to Fastify instance and define endpoints + +Firebase Function's HTTP layer already parses the request +and makes a JSON payload available. It also provides access +to the raw body, unparsed, which is useful in order to calculate +request signatures to validate HTTP webhooks. + +Add as follows to the `registerRoutes()` function: + +```js +async function registerRoutes (fastify) { + fastify.addContentTypeParser("application/json", {}, (req, payload, done) => { + // useful to include the request's raw body on the `req` object that will + // later be available in your other routes so you can calculate the HMAC + // if needed + req.rawBody = payload.rawBody + + // payload.body is already the parsed JSON so we just fire the done callback + // with it + done(null, payload.body) + }) + + // define your endpoints here... + fastify.post("/some-route-here", async (request, reply) => {} + + fastify.get('/', async (request, reply) => { + reply.send({message: 'Hello World!'}) + }) +} +``` + +### Export the function using Firebase onRequest + +Final step is to export the Fastify app instance to Firebase's own +`onRequest()` function so it can pass the request and reply objects to it: + +```js +exports.app = onRequest(fastifyApp) +``` + +### Local test + +Install the Firebase tools functions so you can use the CLI: + +```bash +npm i -g firebase-tools +``` + +Then you can run your function locally with: + +```bash +firebase emulators:start --only functions +``` + +### Deploy + +Deploy your Firebase Functions with: + +```bash +firebase deploy --only functions +``` + +#### Read logs + +Use the Firebase tools CLI: + +```bash +firebase functions:log +``` + +### References +- [Fastify on Firebase Functions](https://github.com/lirantal/lemon-squeezy-firebase-webhook-fastify/blob/main/package.json) +- [An article about HTTP webhooks on Firebase Functions and Fastify: A Practical Case Study with Lemon Squeezy](https://lirantal.com/blog/http-webhooks-firebase-functions-fastify-practical-case-study-lemon-squeezy) + + ## Google Cloud Run Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless From 1fa911a6b37f3e119018be071aa2dd7e6f094ffd Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Wed, 21 Feb 2024 09:36:52 -0500 Subject: [PATCH 0597/1295] feat: set useSemicolonDefault default option to false (#5320) * feat: set useSemicolonDefault default option to false * feat: add migration guide for v5 * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Frazer Smith Signed-off-by: Aras Abbasi --------- Signed-off-by: Aras Abbasi Co-authored-by: Aras Abbasi Co-authored-by: Frazer Smith --- build/build-validation.js | 2 +- docs/Guides/Migration-Guide-V5.md | 20 ++++++++++ docs/Reference/Server.md | 6 +-- lib/configValidator.js | 60 ++++++++++++++-------------- test/internals/initialConfig.test.js | 4 +- test/useSemicolonDelimiter.test.js | 9 ++--- 6 files changed, 59 insertions(+), 42 deletions(-) create mode 100644 docs/Guides/Migration-Guide-V5.md diff --git a/build/build-validation.js b/build/build-validation.js index 9108da57138..38d312faa84 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -42,7 +42,7 @@ const defaultInitOptions = { requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, // 72 seconds exposeHeadRoutes: true, - useSemicolonDelimiter: true + useSemicolonDelimiter: false } const schema = { diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md new file mode 100644 index 00000000000..c4bcbb0869c --- /dev/null +++ b/docs/Guides/Migration-Guide-V5.md @@ -0,0 +1,20 @@ +# V5 Migration Guide + +This guide is intended to help with migration from Fastify v4 to v5. + +Before migrating to v5, please ensure that you have fixed all deprecation +warnings from v4. All v4 deprecations have been removed and they will no longer +work after upgrading. + +## Breaking Changes + +### `useSemicolonDelimiter` false by default + +Starting with v5, Fastify instances will no longer default to supporting the use +of semicolon delimiters in the query string as they did in v4. +This is due to it being non-standard +behavior and not adhering to [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.4). + +If you still wish to use semicolons as delimiters, you can do so by +setting `useSemicolonDelimiter: true` in the server configuration. + diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 6319a0c6eab..225308ec515 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -871,14 +871,14 @@ function rewriteUrl (req) { ### `useSemicolonDelimiter` -+ Default `true` ++ Default `false` Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which supports, separating the path and query string with a `;` character (code 59), e.g. `/dev;foo=bar`. This decision originated from [delvedor/find-my-way#76] (https://github.com/delvedor/find-my-way/issues/76). Thus, this option will support -backwards compatiblilty for the need to split on `;`. To disable support for splitting -on `;` set `useSemicolonDelimiter` to `false`. +backwards compatiblilty for the need to split on `;`. To enable support for splitting +on `;` set `useSemicolonDelimiter` to `true`. ```js const fastify = require('fastify')({ diff --git a/lib/configValidator.js b/lib/configValidator.js index 121546f61f7..5235b416a2d 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -3,7 +3,7 @@ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -70,7 +70,7 @@ if(data.exposeHeadRoutes === undefined){ data.exposeHeadRoutes = true; } if(data.useSemicolonDelimiter === undefined){ -data.useSemicolonDelimiter = true; +data.useSemicolonDelimiter = false; } const _errs1 = errors; for(const key0 in data){ @@ -1006,34 +1006,34 @@ data["exposeHeadRoutes"] = coerced25; var valid0 = _errs67 === errors; if(valid0){ let data23 = data.useSemicolonDelimiter; -const _errs68 = errors; +const _errs69 = errors; if(typeof data23 !== "boolean"){ -let coerced25 = undefined; -if(!(coerced25 !== undefined)){ +let coerced26 = undefined; +if(!(coerced26 !== undefined)){ if(data23 === "false" || data23 === 0 || data23 === null){ -coerced25 = false; +coerced26 = false; } else if(data23 === "true" || data23 === 1){ -coerced25 = true; +coerced26 = true; } else { validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } -if(coerced25 !== undefined){ -data23 = coerced25; +if(coerced26 !== undefined){ +data23 = coerced26; if(data !== undefined){ -data["useSemicolonDelimiter"] = coerced25; +data["useSemicolonDelimiter"] = coerced26; } } } -var valid0 = _errs68 === errors; +var valid0 = _errs69 === errors; if(valid0){ if(data.versioning !== undefined){ let data24 = data.versioning; -const _errs70 = errors; -if(errors === _errs70){ +const _errs71 = errors; +if(errors === _errs71){ if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ let missing1; if(((data24.storage === undefined) && (missing1 = "storage")) || ((data24.deriveVersion === undefined) && (missing1 = "deriveVersion"))){ @@ -1046,7 +1046,7 @@ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/prop return false; } } -var valid0 = _errs70 === errors; +var valid0 = _errs71 === errors; } else { var valid0 = true; @@ -1054,13 +1054,13 @@ var valid0 = true; if(valid0){ if(data.constraints !== undefined){ let data25 = data.constraints; -const _errs73 = errors; -if(errors === _errs73){ +const _errs74 = errors; +if(errors === _errs74){ if(data25 && typeof data25 == "object" && !Array.isArray(data25)){ for(const key2 in data25){ let data26 = data25[key2]; -const _errs76 = errors; -if(errors === _errs76){ +const _errs77 = errors; +if(errors === _errs77){ if(data26 && typeof data26 == "object" && !Array.isArray(data26)){ let missing2; if(((((data26.name === undefined) && (missing2 = "name")) || ((data26.storage === undefined) && (missing2 = "storage"))) || ((data26.validate === undefined) && (missing2 = "validate"))) || ((data26.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){ @@ -1071,24 +1071,24 @@ else { if(data26.name !== undefined){ let data27 = data26.name; if(typeof data27 !== "string"){ -let dataType26 = typeof data27; -let coerced26 = undefined; -if(!(coerced26 !== undefined)){ -if(dataType26 == "number" || dataType26 == "boolean"){ -coerced26 = "" + data27; +let dataType27 = typeof data27; +let coerced27 = undefined; +if(!(coerced27 !== undefined)){ +if(dataType27 == "number" || dataType27 == "boolean"){ +coerced27 = "" + data27; } else if(data27 === null){ -coerced26 = ""; +coerced27 = ""; } else { validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } -if(coerced26 !== undefined){ -data27 = coerced26; +if(coerced27 !== undefined){ +data27 = coerced27; if(data26 !== undefined){ -data26["name"] = coerced26; +data26["name"] = coerced27; } } } @@ -1100,7 +1100,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/ return false; } } -var valid7 = _errs76 === errors; +var valid7 = _errs77 === errors; if(!valid7){ break; } @@ -1111,7 +1111,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs73 === errors; +var valid0 = _errs74 === errors; } else { var valid0 = true; @@ -1152,4 +1152,4 @@ return errors === 0; } -module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":true} +module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false} diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index 2ddd8306b29..c1864705078 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -49,7 +49,7 @@ test('without options passed to Fastify, initialConfig should expose default val requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, exposeHeadRoutes: true, - useSemicolonDelimiter: true + useSemicolonDelimiter: false } t.same(Fastify().initialConfig, fastifyDefaultOptions) @@ -289,7 +289,7 @@ test('Should not have issues when passing stream options to Pino.js', t => { requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, exposeHeadRoutes: true, - useSemicolonDelimiter: true + useSemicolonDelimiter: false }) } catch (error) { t.fail() diff --git a/test/useSemicolonDelimiter.test.js b/test/useSemicolonDelimiter.test.js index 3a178157024..639e9f88a76 100644 --- a/test/useSemicolonDelimiter.test.js +++ b/test/useSemicolonDelimiter.test.js @@ -5,30 +5,27 @@ const test = t.test const Fastify = require('..') const sget = require('simple-get').concat -test('use semicolon delimiter default true', t => { +test('use semicolon delimiter default false', t => { t.plan(4) const fastify = Fastify({}) t.teardown(fastify.close.bind(fastify)) - fastify.get('/1234', (req, reply) => { + fastify.get('/1234;foo=bar', (req, reply) => { reply.send(req.query) }) fastify.listen({ port: 0 }, err => { t.error(err) - console.log('http://localhost:' + fastify.server.address().port + '/1234;foo=bar') sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' }, (err, response, body) => { t.error(err) t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { - foo: 'bar' - }) + t.same(JSON.parse(body), {}) }) }) }) From adc88d3f83314c52c3192a8620e171a9c9c801ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 25 Feb 2024 23:20:00 +0100 Subject: [PATCH 0598/1295] fix type (#5330) --- test/types/fastify.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 73bbe9df79c..e19070a8181 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -57,7 +57,7 @@ expectAssignable({ query: '' }) fastify({ http2: true, https: {} }).inject().then((resp) => { expectAssignable(resp) }) -const lightMyRequestCallback: LightMyRequestCallback = (err: Error, response: LightMyRequestResponse) => { +const lightMyRequestCallback: LightMyRequestCallback = (err: Error | undefined, response: LightMyRequestResponse | undefined) => { if (err) throw err } fastify({ http2: true, https: {} }).inject({}, lightMyRequestCallback) From db758b321b1fb416a2dea963b347c698fe74a5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Tue, 27 Feb 2024 22:02:50 +0100 Subject: [PATCH 0599/1295] perf: use FifoMap (#5331) --- lib/contentTypeParser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 63c33f31b1c..e41527d3d23 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -1,7 +1,7 @@ 'use strict' const { AsyncResource } = require('node:async_hooks') -const { Fifo } = require('toad-cache') +const { FifoMap: Fifo } = require('toad-cache') const { safeParse: safeParseContentType, defaultContentType } = require('fast-content-type-parse') const secureJson = require('secure-json-parse') const { From a0335401d154241fa82de5b11a57e9d3b8abc982 Mon Sep 17 00:00:00 2001 From: Matthias Keckl <53833818+matthyk@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:14:30 +0100 Subject: [PATCH 0600/1295] Update Ecosystem.md (#5336) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index afc3b63754d..7c27d7f756e 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -529,6 +529,8 @@ middlewares into Fastify plugins across every part of your server. - [`fastify-osm`](https://github.com/gzileni/fastify-osm) Fastify OSM plugin to run overpass queries by OpenStreetMap. +- [`fastify-override`](https://github.com/matthyk/fastify-override) + Fastify plugin to override decorators, plugins and hooks for testing purposes - [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo) Fastify plugin for memoize responses by expressive settings. - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker From 20cff61302f0bd129ab8c916f2507f006c6d855c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Fri, 1 Mar 2024 08:35:06 +0100 Subject: [PATCH 0601/1295] feat: make contentTypeParser's existingParser check more strict (#5329) --- lib/contentTypeParser.js | 7 +++++-- test/content-type.test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index e41527d3d23..e0036c90d37 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -42,9 +42,12 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { const contentTypeIsString = typeof contentType === 'string' - if (!contentTypeIsString && !(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() - if (contentTypeIsString && contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE() if (typeof parserFn !== 'function') throw new FST_ERR_CTP_INVALID_HANDLER() + if (!contentTypeIsString && !(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() + if (contentTypeIsString) { + contentType = contentType.trim().toLowerCase() + if (contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE() + } if (this.existingParser(contentType)) { throw new FST_ERR_CTP_ALREADY_PRESENT(contentType) diff --git a/test/content-type.test.js b/test/content-type.test.js index 06e91f9b544..2007052bff3 100644 --- a/test/content-type.test.js +++ b/test/content-type.test.js @@ -3,6 +3,39 @@ const t = require('tap') const test = t.test const Fastify = require('..') +const { + FST_ERR_CTP_ALREADY_PRESENT +} = require('../lib/errors') + +test('should lowercase contentTypeParser names', async t => { + t.plan(1) + const fastify = Fastify() + fastify.addContentTypeParser('text/html', function (req, done) { + done() + }) + try { + fastify.addContentTypeParser('TEXT/html', function (req, done) { + done() + }) + } catch (err) { + t.same(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) + } +}) + +test('should trim contentTypeParser names', async t => { + t.plan(1) + const fastify = Fastify() + fastify.addContentTypeParser('text/html', function (req, done) { + done() + }) + try { + fastify.addContentTypeParser(' text/html', function (req, done) { + done() + }) + } catch (err) { + t.same(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) + } +}) test('should remove content-type for setErrorHandler', async t => { t.plan(8) From f776447423fc1df7a6497abb76baea25b797560f Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 1 Mar 2024 15:35:46 +0800 Subject: [PATCH 0602/1295] feat: loosen content-type checking (#4450) --- docs/Reference/ContentTypeParser.md | 9 ++- lib/contentTypeParser.js | 81 +++---------------- lib/warnings.js | 11 ++- test/content-parser.test.js | 121 +++------------------------- 4 files changed, 41 insertions(+), 181 deletions(-) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index 1e09d7c81fe..801d972d2d0 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -29,6 +29,13 @@ is given in the content-type header. If it is not given, the [catch-all](#catch-all) parser is not executed as with `POST`, `PUT` and `PATCH`, but the payload is simply not parsed. +> ## ⚠ Security Notice +> When using with RegExp to detect `Content-Type`, you should beware of +> how to properly detect the `Content-Type`. For example, if you need +> `application/*`, you should use `/^application\/([\w-]+);?/` to match the +> [essence MIME type](https://mimesniff.spec.whatwg.org/#mime-type-miscellaneous) +> only. + ### Usage ```js fastify.addContentTypeParser('application/jsoff', function (request, payload, done) { @@ -52,7 +59,7 @@ fastify.addContentTypeParser('application/jsoff', async function (request, paylo }) // Handle all content types that matches RegExp -fastify.addContentTypeParser(/^image\/.*/, function (request, payload, done) { +fastify.addContentTypeParser(/^image\/([\w-]+);?/, function (request, payload, done) { imageParser(payload, function (err, body) { done(err, body) }) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index e0036c90d37..8d13ed5541b 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -2,7 +2,6 @@ const { AsyncResource } = require('node:async_hooks') const { FifoMap: Fifo } = require('toad-cache') -const { safeParse: safeParseContentType, defaultContentType } = require('fast-content-type-parse') const secureJson = require('secure-json-parse') const { kDefaultJsonParse, @@ -27,6 +26,7 @@ const { FST_ERR_CTP_EMPTY_JSON_BODY, FST_ERR_CTP_INSTANCE_ALREADY_STARTED } = require('./errors') +const { FSTSEC001 } = require('./warnings') function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) { this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning) @@ -34,7 +34,7 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) this.customParsers = new Map() this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse])) this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser)) - this.parserList = [new ParserListItem('application/json'), new ParserListItem('text/plain')] + this.parserList = ['application/json', 'text/plain'] this.parserRegExpList = [] this.cache = new Fifo(100) } @@ -70,9 +70,9 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { this.customParsers.set('', parser) } else { if (contentTypeIsString) { - this.parserList.unshift(new ParserListItem(contentType)) + this.parserList.unshift(contentType) } else { - contentType.isEssence = contentType.source.indexOf(';') === -1 + validateRegExp(contentType) this.parserRegExpList.unshift(contentType) } this.customParsers.set(contentType.toString(), parser) @@ -102,20 +102,11 @@ ContentTypeParser.prototype.getParser = function (contentType) { const parser = this.cache.get(contentType) if (parser !== undefined) return parser - const parsed = safeParseContentType(contentType) - - // dummyContentType always the same object - // we can use === for the comparison and return early - if (parsed === defaultContentType) { - return this.customParsers.get('') - } - // eslint-disable-next-line no-var for (var i = 0; i !== this.parserList.length; ++i) { const parserListItem = this.parserList[i] - if (compareContentType(parsed, parserListItem)) { - const parser = this.customParsers.get(parserListItem.name) - // we set request content-type in cache to reduce parsing of MIME type + if (contentType.indexOf(parserListItem) === 0) { + const parser = this.customParsers.get(parserListItem) this.cache.set(contentType, parser) return parser } @@ -124,9 +115,8 @@ ContentTypeParser.prototype.getParser = function (contentType) { // eslint-disable-next-line no-var for (var j = 0; j !== this.parserRegExpList.length; ++j) { const parserRegExp = this.parserRegExpList[j] - if (compareRegExpContentType(contentType, parsed.type, parserRegExp)) { + if (parserRegExp.test(contentType)) { const parser = this.customParsers.get(parserRegExp.toString()) - // we set request content-type in cache to reduce parsing of MIME type this.cache.set(contentType, parser) return parser } @@ -376,60 +366,15 @@ function removeAllContentTypeParsers () { this[kContentTypeParser].removeAll() } -function compareContentType (contentType, parserListItem) { - if (parserListItem.isEssence) { - // we do essence check - return contentType.type.indexOf(parserListItem) !== -1 - } else { - // when the content-type includes parameters - // we do a full-text search - // reject essence content-type before checking parameters - if (contentType.type.indexOf(parserListItem.type) === -1) return false - for (const key of parserListItem.parameterKeys) { - // reject when missing parameters - if (!(key in contentType.parameters)) return false - // reject when parameters do not match - if (contentType.parameters[key] !== parserListItem.parameters[key]) return false - } - return true +function validateRegExp (regexp) { + // RegExp should either start with ^ or include ;? + // It can ensure the user is properly detect the essence + // MIME types. + if (regexp.source[0] !== '^' && regexp.source.includes(';?') === false) { + FSTSEC001(regexp.source) } } -function compareRegExpContentType (contentType, essenceMIMEType, regexp) { - if (regexp.isEssence) { - // we do essence check - return regexp.test(essenceMIMEType) - } else { - // when the content-type includes parameters - // we do a full-text match - return regexp.test(contentType) - } -} - -function ParserListItem (contentType) { - this.name = contentType - // we pre-calculate all the needed information - // before content-type comparison - const parsed = safeParseContentType(contentType) - this.isEssence = contentType.indexOf(';') === -1 - // we should not allow empty string for parser list item - // because it would become a match-all handler - if (this.isEssence === false && parsed.type === '') { - // handle semicolon or empty string - const tmp = contentType.split(';', 1)[0] - this.type = tmp === '' ? contentType : tmp - } else { - this.type = parsed.type - } - this.parameters = parsed.parameters - this.parameterKeys = Object.keys(parsed.parameters) -} - -// used in ContentTypeParser.remove -ParserListItem.prototype.toString = function () { - return this.name -} - module.exports = ContentTypeParser module.exports.helpers = { buildContentTypeParser, diff --git a/lib/warnings.js b/lib/warnings.js index 37814751cce..ec31b642108 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -18,6 +18,7 @@ const { createDeprecation, createWarning } = require('process-warning') * - FSTDEP018 * - FSTDEP019 * - FSTWRN001 + * - FSTSEC001 */ const FSTDEP005 = createDeprecation({ @@ -97,6 +98,13 @@ const FSTWRN001 = createWarning({ unlimited: true }) +const FSTSEC001 = createWarning({ + name: 'FastifySecurity', + code: 'FSTSEC001', + message: 'You are using /%s/ Content-Type which may be vulnerable to CORS attack. Please make sure your RegExp start with "^" or include ";?" to proper detection of the essence MIME type.', + unlimited: true +}) + module.exports = { FSTDEP005, FSTDEP006, @@ -112,5 +120,6 @@ module.exports = { FSTDEP018, FSTDEP019, FSTDEP020, - FSTWRN001 + FSTWRN001, + FSTSEC001 } diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 2e49c10bb44..f79170a5575 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -421,32 +421,25 @@ test('Safeguard against content-type spoofing - string', async t => { }) }) -test('Safeguard against content-type spoofing - regexp', async t => { - t.plan(1) +test('Warning against improper content-type - regexp', t => { + t.plan(2) const fastify = Fastify() + + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'FastifySecurity') + t.equal(warning.code, 'FSTSEC001') + } + t.teardown(() => process.removeListener('warning', onWarning)) + fastify.removeAllContentTypeParsers() fastify.addContentTypeParser(/text\/plain/, function (request, body, done) { - t.pass('should be called') done(null, body) }) fastify.addContentTypeParser(/application\/json/, function (request, body, done) { - t.fail('shouldn\'t be called') done(null, body) }) - - fastify.post('/', async () => { - return 'ok' - }) - - await fastify.inject({ - method: 'POST', - path: '/', - headers: { - 'content-type': 'text/plain; content-type="application/json"' - }, - body: '' - }) }) test('content-type match parameters - string 1', async t => { @@ -477,34 +470,6 @@ test('content-type match parameters - string 1', async t => { }) }) -test('content-type match parameters - string 2', async t => { - t.plan(1) - - const fastify = Fastify() - fastify.removeAllContentTypeParsers() - fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { - t.pass('should be called') - done(null, body) - }) - fastify.addContentTypeParser('text/plain; charset=utf8; foo=bar', function (request, body, done) { - t.fail('shouldn\'t be called') - done(null, body) - }) - - fastify.post('/', async () => { - return 'ok' - }) - - await fastify.inject({ - method: 'POST', - path: '/', - headers: { - 'content-type': 'application/json; foo=bar; charset=utf8' - }, - body: '' - }) -}) - test('content-type match parameters - regexp', async t => { t.plan(1) @@ -650,72 +615,6 @@ test('content-type regexp list should be cloned when plugin override', async t = } }) -test('allow partial content-type - essence check', async t => { - t.plan(1) - - const fastify = Fastify() - fastify.removeAllContentTypeParsers() - fastify.addContentTypeParser('json', function (request, body, done) { - t.pass('should be called') - done(null, body) - }) - - fastify.post('/', async () => { - return 'ok' - }) - - await fastify.inject({ - method: 'POST', - path: '/', - headers: { - 'content-type': 'application/json; foo=bar; charset=utf8' - }, - body: '' - }) - - await fastify.inject({ - method: 'POST', - path: '/', - headers: { - 'content-type': 'image/jpeg' - }, - body: '' - }) -}) - -test('allow partial content-type - not essence check', async t => { - t.plan(1) - - const fastify = Fastify() - fastify.removeAllContentTypeParsers() - fastify.addContentTypeParser('json;', function (request, body, done) { - t.pass('should be called') - done(null, body) - }) - - fastify.post('/', async () => { - return 'ok' - }) - - await fastify.inject({ - method: 'POST', - path: '/', - headers: { - 'content-type': 'application/json; foo=bar; charset=utf8' - }, - body: '' - }) - - await fastify.inject({ - method: 'POST', - path: '/', - headers: { - 'content-type': 'image/jpeg' - }, - body: '' - }) -}) - test('edge case content-type - ;', async t => { t.plan(1) From 52e7ac1e11d148edea5e004ffed264ed9b8857e1 Mon Sep 17 00:00:00 2001 From: Matthias Keckl <53833818+matthyk@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:40:24 +0100 Subject: [PATCH 0603/1295] types: Export preClose hook types (#5335) --- fastify.d.ts | 6 +++--- test/types/hooks.test-d.ts | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/fastify.d.ts b/fastify.d.ts index 88b469db26d..4ddf06ef61f 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -12,7 +12,7 @@ import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequest import { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser' import { FastifyRequestContext, FastifyContextConfig, FastifyReplyContext } from './types/context' import { FastifyErrorCodes } from './types/errors' -import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler } from './types/hooks' +import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler } from './types/hooks' import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel } from './types/logger' import { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' @@ -152,7 +152,7 @@ declare namespace fastify { ) => void, rewriteUrl?: ( // The RawRequestDefaultExpression, RawReplyDefaultExpression, and FastifyTypeProviderDefault parameters - // should be narrowed further but those generic parameters are not passed to this FastifyServerOptions type + // should be narrowed further but those generic parameters are not passed to this FastifyServerOptions type this: FastifyInstance, RawReplyDefaultExpression, Logger, FastifyTypeProviderDefault>, req: RawRequestDefaultExpression ) => string, @@ -183,7 +183,7 @@ declare namespace fastify { FastifyError, // '@fastify/error' FastifySchema, FastifySchemaCompiler, // './types/schema' HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault, // './types/utils' - DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, // './types/hooks' + DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, // './types/hooks' FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory' FastifyTypeProvider, FastifyTypeProviderDefault, // './types/type-provider' FastifyErrorCodes, // './types/errors' diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index bf2ec7d06d7..ebacee9d65a 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -12,7 +12,10 @@ import fastify, { RawRequestDefaultExpression, RawServerDefault, RegisterOptions, - RouteOptions + RouteOptions, + // preClose hook types should be exported correctly https://github.com/fastify/fastify/pull/5335 + preCloseAsyncHookHandler, + preCloseHookHandler } from '../../fastify' import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, preHandlerAsyncHookHandler } from '../../types/hooks' import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' From ef3a755b058f70e3a6e56a5d53a73e6c715550cd Mon Sep 17 00:00:00 2001 From: Muhammed Nuhman Date: Fri, 1 Mar 2024 14:04:55 +0400 Subject: [PATCH 0604/1295] Establish database connection in migration code (#5339) --- docs/Guides/Database.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md index 7b597a9e1ba..0adfe10e9a7 100644 --- a/docs/Guides/Database.md +++ b/docs/Guides/Database.md @@ -287,6 +287,8 @@ async function migrate() { }); try { + await client.connect(); + const postgrator = new Postgrator({ migrationPattern: path.join(__dirname, '/migrations/*'), driver: 'pg', From 8345c9bfb13a78ba0268ed06bd886d0a2a70df59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:23:10 +0000 Subject: [PATCH 0605/1295] chore: Bump pnpm/action-setup from 2 to 3 (#5341) Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 3. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v2...v3) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index f6efd0c64b6..3da1064a41c 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -43,7 +43,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install Pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: ${{ matrix.pnpm-version }} diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 7db25b827b4..c3f239576bb 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -35,7 +35,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install with pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: ${{ matrix.pnpm-version }} From 63cd9f1886a70cd28760d4ffd411345b2ee36325 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:26:02 +0000 Subject: [PATCH 0606/1295] chore: Bump xt0rted/markdownlint-problem-matcher from 2.0.0 to 3.0.0 (#5342) Bumps [xt0rted/markdownlint-problem-matcher](https://github.com/xt0rted/markdownlint-problem-matcher) from 2.0.0 to 3.0.0. - [Release notes](https://github.com/xt0rted/markdownlint-problem-matcher/releases) - [Changelog](https://github.com/xt0rted/markdownlint-problem-matcher/blob/main/CHANGELOG.md) - [Commits](https://github.com/xt0rted/markdownlint-problem-matcher/compare/98d94724052d20ca2e06c091f202e4c66c3c59fb...1a5fabfb577370cfdf5af944d418e4be3ea06f27) --- updated-dependencies: - dependency-name: xt0rted/markdownlint-problem-matcher dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/md-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index d3601a6cda7..e915c1a32c5 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -37,7 +37,7 @@ jobs: run: npm install --ignore-scripts - name: Add Matcher - uses: xt0rted/markdownlint-problem-matcher@98d94724052d20ca2e06c091f202e4c66c3c59fb + uses: xt0rted/markdownlint-problem-matcher@1a5fabfb577370cfdf5af944d418e4be3ea06f27 - name: Run Linter run: ./node_modules/.bin/markdownlint-cli2 From 5c263b4dc2cb6d5eda2df7b3602238ae24957c55 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 3 Mar 2024 18:16:55 +0100 Subject: [PATCH 0607/1295] Bumped v4.26.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index d0030fbd756..8e8d7e7152f 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.26.1' +const VERSION = '4.26.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 2c2045f3a02..1983a46f255 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.26.1", + "version": "4.26.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 9ad01227c1cf6b6b46b903d4bd729f111a4d9272 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 4 Mar 2024 00:09:27 +0100 Subject: [PATCH 0608/1295] docs(request): update request page (#5343) --- docs/Reference/Request.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 224e2e782e4..a64222faa58 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -107,7 +107,7 @@ fastify.post('/:params', options, function (request, reply) { console.log(request.hostname) console.log(request.protocol) console.log(request.url) - console.log(request.routerMethod) + console.log(request.routeOptions.method) console.log(request.routeOptions.bodyLimit) console.log(request.routeOptions.method) console.log(request.routeOptions.url) @@ -116,7 +116,7 @@ fastify.post('/:params', options, function (request, reply) { console.log(request.routeOptions.version) console.log(request.routeOptions.exposeHeadRoute) console.log(request.routeOptions.prefixTrailingSlash) - console.log(request.routerPath.logLevel) + console.log(request.routeOptions.logLevel) request.log.info('some info') }) ``` From e58046e788657dfb6b1f9b23eebc6c5ad51f72b6 Mon Sep 17 00:00:00 2001 From: Jefferson <55195249+riosje@users.noreply.github.com> Date: Tue, 5 Mar 2024 15:16:42 -0500 Subject: [PATCH 0609/1295] ci: Add Nsolid runtime to CI and Integration tests (#5332) * ci: Add Nsolid CI and Integration tests as separate pipelines Signed-off-by: Jefferson --- .github/workflows/ci-alternative-runtime.yml | 127 ++++++++++++++++++ .../integration-alternative-runtimes.yml | 64 +++++++++ docs/Reference/LTS.md | 52 ++++--- 3 files changed, 223 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/ci-alternative-runtime.yml create mode 100644 .github/workflows/integration-alternative-runtimes.yml diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml new file mode 100644 index 00000000000..69f9c261a8b --- /dev/null +++ b/.github/workflows/ci-alternative-runtime.yml @@ -0,0 +1,127 @@ +name: ci Alternative Runtimes + +on: + push: + branches: + - main + - next + - 'v*' + paths-ignore: + - 'docs/**' + - '*.md' + pull_request: + paths-ignore: + - 'docs/**' + - '*.md' + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + test-unit: + runs-on: ${{ matrix.os }} + permissions: + contents: read + strategy: + matrix: + node-version: [18, 20] + os: [macos-latest, ubuntu-latest, windows-latest] + include: + - runtime: nsolid + node-version: 18 + nsolid-version: 5 + - runtime: nsolid + node-version: 20 + nsolid-version: 5 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: nodesource/setup-nsolid@v1 + if: ${{ matrix.runtime == 'nsolid' }} + with: + node-version: ${{ matrix.node-version }} + nsolid-version: ${{ matrix.nsolid-version }} + + - name: Install + run: | + npm install --ignore-scripts + + - name: Run tests + run: | + npm run unit + + test-typescript: + runs-on: 'ubuntu-latest' + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: nodesource/setup-nsolid@v1 + with: + node-version: 20 + nsolid-version: 5 + + - name: Install + run: | + npm install --ignore-scripts + + - name: Run typescript tests + run: | + npm run test:typescript + env: + NODE_OPTIONS: no-network-family-autoselection + + package: + needs: + - test-typescript + - test-unit + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: nodesource/setup-nsolid@v1 + with: + nsolid-version: 5 + - name: install fastify + run: | + npm install --ignore-scripts + - name: install webpack stack + run: | + cd test/bundler/webpack && npm install + - name: Test webpack bundle + run: | + cd test/bundler/webpack && npm run test + - name: install esbuild stack + run: | + cd test/bundler/esbuild && npm install + - name: Test esbuild bundle + run: | + cd test/bundler/esbuild && npm run test + + automerge: + if: > + github.event_name == 'pull_request' && + github.event.pull_request.user.login == 'dependabot[bot]' + needs: + - test-typescript + - test-unit + - package + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + steps: + - uses: fastify/github-action-merge-dependabot@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml new file mode 100644 index 00000000000..f74a53b7c9d --- /dev/null +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -0,0 +1,64 @@ +name: integration Alternative Runtimes + +on: + push: + branches: + - main + - master + - next + - 'v*' + paths-ignore: + - 'docs/**' + - '*.md' + pull_request: + paths-ignore: + - 'docs/**' + - '*.md' + +jobs: + install-production: + runs-on: ${{ matrix.os }} + permissions: + contents: read + + strategy: + matrix: + os: [ubuntu-latest] + runtime: [nsolid] + node-version: [18, 20] + pnpm-version: [8] + include: + - nsolid-version: 5 + node-version: 18 + runtime: nsolid + - nsolid-version: 5 + node-version: 20 + runtime: nsolid + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: nodesource/setup-nsolid@v1 + if: ${{ matrix.runtime == 'nsolid' }} + with: + node-version: ${{ matrix.node-version }} + nsolid-version: ${{ matrix.nsolid-version }} + + - name: Install Pnpm + uses: pnpm/action-setup@v2 + with: + version: ${{ matrix.pnpm-version }} + + - name: Install Production + run: | + pnpm install --prod + + - name: Run server + run: | + node integration/server.js & + + - name: Test + if: ${{ success() }} + run: | + bash integration/test.sh diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 880b63cd0c6..30a1b8cec79 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -1,7 +1,8 @@

Fastify

## Long Term Support - + +`` Fastify's Long Term Support (LTS) is provided according to the schedule laid out in this document: @@ -10,18 +11,27 @@ in this document: versions, are supported for a minimum period of six months from their release date. The release date of any specific version can be found at [https://github.com/fastify/fastify/releases](https://github.com/fastify/fastify/releases). - 2. Major releases will receive security updates for an additional six months from the release of the next major release. After this period we will still review and release security fixes as long as they are provided by the community and they do not violate other constraints, e.g. minimum supported Node.js version. - 3. Major releases will be tested and verified against all Node.js release lines that are supported by the [Node.js LTS policy](https://github.com/nodejs/Release) within the LTS period of that given Fastify release line. This implies that only the latest Node.js release of a given line is supported. +4. In addition to Node.js runtime, major releases of Fastify will also be tested + and verified against alternative runtimes that are compatible with Node.js. + The maintenance teams of these alternative runtimes are responsible for ensuring + and guaranteeing these tests work properly. + 1. [N|Solid](https://docs.nodesource.com/nsolid), maintained by NodeSource, + commits to testing and verifying each Fastify major release against the N|Solid + LTS versions that are current at the time of the Fastify release. + NodeSource guarantees that Fastify will be compatible and function correctly + with N|Solid, aligning with the support and compatibility scope of the N|Solid + LTS versions available at the time of the Fastify release. + This ensures users of N|Solid can confidently use Fastify. A "month" is defined as 30 consecutive days. @@ -38,33 +48,35 @@ A "month" is defined as 30 consecutive days. > dependency as `"fastify": "~3.15.x"`. This will leave your application > vulnerable, so please use with caution. -[semver]: https://semver.org/ - ### Schedule - -| Version | Release Date | End Of LTS Date | Node.js | -| :------ | :----------- | :-------------- | :------------------- | -| 1.0.0 | 2018-03-06 | 2019-09-01 | 6, 8, 9, 10, 11 | -| 2.0.0 | 2019-02-25 | 2021-01-31 | 6, 8, 10, 12, 14 | -| 3.0.0 | 2020-07-07 | 2023-06-30 | 10, 12, 14, 16, 18 | -| 4.0.0 | 2022-06-08 | TBD | 14, 16, 18, 20 | +`` + +| Version | Release Date | End Of LTS Date | Node.js | Nsolid(Node) | +| :------ | :----------- | :-------------- | :----------------- | :------------- | +| 1.0.0 | 2018-03-06 | 2019-09-01 | 6, 8, 9, 10, 11 | | +| 2.0.0 | 2019-02-25 | 2021-01-31 | 6, 8, 10, 12, 14 | | +| 3.0.0 | 2020-07-07 | 2023-06-30 | 10, 12, 14, 16, 18 | v5(18) | +| 4.0.0 | 2022-06-08 | TBD | 14, 16, 18, 20 | v5(18), v5(20) | ### CI tested operating systems - -Fastify uses GitHub Actions for CI testing, please refer to [GitHub's +`` + +Fastify uses GitHub Actions for CI testing, please refer to [GitHub's documentation regarding workflow runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources) for further details on what the latest virtual environment is in relation to the YAML workflow labels below: -| OS | YAML Workflow Label | Package Manager | Node.js | -|---------|------------------------|---------------------------|--------------| -| Linux | `ubuntu-latest` | npm | 14,16,18,20 | -| Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18,20 | -| Windows | `windows-latest` | npm | 14,16,18,20 | -| MacOS | `macos-latest` | npm | 14,16,18,20 | +| OS | YAML Workflow Label | Package Manager | Node.js | Nsolid(Node) | +| ------- | ------------------- | --------------- | ----------- | ------------- | +| Linux | `ubuntu-latest` | npm | 14,16,18,20 | v5(18),v5(20) | +| Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18,20 | v5(18),v5(20) | +| Windows | `windows-latest` | npm | 14,16,18,20 | v5(18),v5(20) | +| MacOS | `macos-latest` | npm | 14,16,18,20 | v5(18),v5(20) | Using [yarn](https://yarnpkg.com/) might require passing the `--ignore-engines` flag. + +[semver]: https://semver.org/ From 0685fc7b1fc95055c459f3a557e2b99332288e1c Mon Sep 17 00:00:00 2001 From: Shyam Chen Date: Thu, 7 Mar 2024 18:11:29 +0800 Subject: [PATCH 0610/1295] docs(Ecosystem): add fastify-i18n, vite-plugin-fastify, and vite-plugin-fastify-routes (#5346) Co-authored-by: Carlos Fuentes --- docs/Guides/Ecosystem.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 7c27d7f756e..207cc16fe6c 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -304,9 +304,7 @@ section. - [`fastify-cloudflare-turnstile`](https://github.com/112RG/fastify-cloudflare-turnstile) Fastify plugin for CloudFlare Turnstile. - [`fastify-cloudinary`](https://github.com/Vanilla-IceCream/fastify-cloudinary) - The Cloudinary Fastify SDK allows you to quickly and easily integrate your - application with Cloudinary. Effortlessly optimize and transform your cloud's - assets. + Plugin to share a common Cloudinary connection across Fastify. - [`fastify-cockroachdb`](https://github.com/alex-ppg/fastify-cockroachdb) Fastify plugin to connect to a CockroachDB PostgreSQL instance via the Sequelize ORM. @@ -416,6 +414,8 @@ section. Lightweight, proxy-aware redirect plugin from HTTP to HTTPS. - [`fastify-https-redirect`](https://github.com/tomsvogel/fastify-https-redirect) Fastify plugin for auto-redirect from HTTP to HTTPS. +- [`fastify-i18n`](https://github.com/Vanilla-IceCream/fastify-i18n) + Internationalization plugin for Fastify. Built upon node-polyglot. - [`fastify-impressions`](https://github.com/manju4ever/fastify-impressions) Fastify plugin to track impressions of all the routes. - [`fastify-influxdb`](https://github.com/alex-ppg/fastify-influxdb) Fastify @@ -722,3 +722,7 @@ middlewares into Fastify plugins Fastify APIs using decorators and convert Typescript interface to JSON Schema. - [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to generate JSON Schema from TypeScript interfaces. +- [`vite-plugin-fastify`](https://github.com/Vanilla-IceCream/vite-plugin-fastify) + Fastify plugin for Vite with Hot-module Replacement. +- [`vite-plugin-fastify-routes`](https://github.com/Vanilla-IceCream/vite-plugin-fastify-routes) + File-based routing for Fastify applications using Vite. From a69804a9a4a622959981c864e9cb090f766ad80e Mon Sep 17 00:00:00 2001 From: Jorge Vargas Date: Sun, 10 Mar 2024 07:55:54 -0500 Subject: [PATCH 0611/1295] docs(ecosystem): update x-ray community plugin link (#5350) * docs: update x-ray community plugin link * docs: update package name and description * docs: sort in alphabetical order --- docs/Guides/Ecosystem.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 207cc16fe6c..6d993cd1a7b 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -222,6 +222,8 @@ section. API key management solution. - [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for Kubernetes Liveness and Readiness Probes. +- [`aws-xray-sdk-fastify`](https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/fastify) + A Fastify plugin to log requests and subsegments through AWSXray. - [`cls-rtracer`](https://github.com/puzpuzpuz/cls-rtracer) Fastify middleware for CLS-based request ID generation. An out-of-the-box solution for adding request IDs into your logs. @@ -682,8 +684,6 @@ middlewares into Fastify plugins [uws](https://github.com/uNetworking/uWebSockets). - [`fastify-xml-body-parser`](https://github.com/NaturalIntelligence/fastify-xml-body-parser) Parse XML payload / request body into JS / JSON object. -- [`fastify-xray`](https://github.com/jeromemacias/fastify-xray) Fastify plugin - for AWS XRay recording. - [`http-wizard`](https://github.com/flodlc/http-wizard) Exports a typescript api client for your Fastify api and ensures fullstack type safety for your project. From c957be26a14a74d3f421760315e57daa767209eb Mon Sep 17 00:00:00 2001 From: Jamie King Date: Mon, 11 Mar 2024 11:17:26 -0700 Subject: [PATCH 0612/1295] docs: spelling corrections (#5349) * docs: spelling corrections * docs: spelling corrections * test: correct spelling in specs and assertions Co-authored-by: Frazer Smith Signed-off-by: Jamie King --------- Signed-off-by: Jamie King Co-authored-by: Frazer Smith --- .github/workflows/links-check.yml | 2 +- docs/Guides/Database.md | 4 ++-- docs/Guides/Delay-Accepting-Requests.md | 2 +- docs/Guides/Detecting-When-Clients-Abort.md | 2 +- docs/Guides/Ecosystem.md | 12 ++++++------ docs/Guides/Plugins-Guide.md | 2 +- docs/Guides/Prototype-Poisoning.md | 2 +- docs/Reference/Errors.md | 2 +- docs/Reference/Logging.md | 2 +- docs/Reference/Reply.md | 6 +++--- docs/Reference/Server.md | 4 ++-- docs/Reference/TypeScript.md | 12 ++++++------ docs/Reference/Validation-and-Serialization.md | 2 +- fastify.js | 2 +- lib/fourOhFour.js | 2 +- test/404s.test.js | 4 ++-- test/close.test.js | 2 +- test/encapsulated-error-handler.test.js | 2 +- test/hooks.test.js | 4 ++-- test/internals/handleRequest.test.js | 4 ++-- test/logger/instantiation.test.js | 2 +- test/logger/options.test.js | 2 +- test/schema-examples.test.js | 2 +- test/schema-feature.test.js | 12 ++++++------ test/schema-serialization.test.js | 4 ++-- test/schema-special-usage.test.js | 2 +- test/skip-reply-send.test.js | 2 +- test/stream.5.test.js | 4 ++-- test/types/instance.test-d.ts | 2 +- test/types/type-provider.test-d.ts | 2 +- types/type-provider.d.ts | 2 +- 31 files changed, 55 insertions(+), 55 deletions(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index ae96cd6f21b..f0c5f976929 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -22,7 +22,7 @@ jobs: uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a with: fail: true - # As external links behaviour is not predictable, we check only internal links + # As external links behavior is not predictable, we check only internal links # to ensure consistency. # See: https://github.com/lycheeverse/lychee-action/issues/17#issuecomment-1162586751 args: --offline --verbose --no-progress './**/*.md' diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md index 0adfe10e9a7..8bee1119046 100644 --- a/docs/Guides/Database.md +++ b/docs/Guides/Database.md @@ -237,14 +237,14 @@ development. Migrations provide a repeatable and testable way to modify a database's schema and prevent data loss. As stated at the beginning of the guide, Fastify is database agnostic and any -NodeJS database migration tool can be used with it. We will give an example of +Node.js database migration tool can be used with it. We will give an example of using [Postgrator](https://www.npmjs.com/package/postgrator) which has support for Postgres, MySQL, SQL Server and SQLite. For MongoDB migrations, please check [migrate-mongo](https://www.npmjs.com/package/migrate-mongo). #### [Postgrator](https://www.npmjs.com/package/postgrator) -Postgrator is NodeJS SQL migration tool that uses a directory of SQL scripts to +Postgrator is Node.js SQL migration tool that uses a directory of SQL scripts to alter the database schema. Each file an migrations folder need to follow the pattern: ` [version].[action].[optional-description].sql`. diff --git a/docs/Guides/Delay-Accepting-Requests.md b/docs/Guides/Delay-Accepting-Requests.md index d2134c53af7..b6726217ea1 100644 --- a/docs/Guides/Delay-Accepting-Requests.md +++ b/docs/Guides/Delay-Accepting-Requests.md @@ -49,7 +49,7 @@ That will be achieved by wrapping into a custom plugin two main features: 1. the mechanism for authenticating with the provider [decorating](../Reference/Decorators.md) the `fastify` object with the -authentication key (`magicKey` from here onwards) +authentication key (`magicKey` from here onward) 1. the mechanism for denying requests that would, otherwise, fail ### Hands-on diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md index 1bdedec3a99..ce47ff366d7 100644 --- a/docs/Guides/Detecting-When-Clients-Abort.md +++ b/docs/Guides/Detecting-When-Clients-Abort.md @@ -90,7 +90,7 @@ of `{ ok: true }`. In the request close event, you should examine the diff between a successful request and one aborted by the client to determine the best property for your use case. You can find details about request properties in the -[NodeJS documentation](https://nodejs.org/api/http.html). +[Node.js documentation](https://nodejs.org/api/http.html). You can also perform this logic outside of a hook, directly in a specific route. diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 6d993cd1a7b..5b1cfbbb285 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -185,7 +185,7 @@ section. - [`@fastify-userland/typeorm-query-runner`](https://github.com/fastify-userland/typeorm-query-runner) Fastify typeorm QueryRunner plugin - [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server) - Tiny (~5k), Fast, KISS, and dependency-free Node.JS library to make your + Tiny (~5k), Fast, KISS, and dependency-free Node.js library to make your Fastify API graceful. - [`@h4ad/serverless-adapter`](https://github.com/H4ad/serverless-adapter) Run REST APIs and other web applications using your existing Node.js @@ -249,7 +249,7 @@ section. [`@angular/platform-server`](https://github.com/angular/angular/tree/master/packages/platform-server) for Fastify - [`fastify-api-key`](https://github.com/arkerone/fastify-api-key) Fastify - plugin to authenticate HTTP requests based on api key and signature + plugin to authenticate HTTP requests based on API key and signature - [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify Plugin for interacting with Appwrite server. - [`fastify-at-mysql`](https://github.com/mateonunez/fastify-at-mysql) Fastify @@ -290,7 +290,7 @@ section. - [`fastify-bugsnag`](https://github.com/ZigaStrgar/fastify-bugsnag) Fastify plugin to add support for [Bugsnag](https://www.bugsnag.com/) error reporting. - [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman) - Small and efficient cache provider for Node.JS with In-memory, File, Redis + Small and efficient cache provider for Node.js with In-memory, File, Redis and MongoDB engines for Fastify - [`fastify-casbin`](https://github.com/nearform/fastify-casbin) Casbin support for Fastify. @@ -571,7 +571,7 @@ middlewares into Fastify plugins - [`fastify-rbac`](https://gitlab.com/m03geek/fastify-rbac) Fastify role-based access control plugin. - [`fastify-recaptcha`](https://github.com/qwertyforce/fastify-recaptcha) - Fastify plugin for recaptcha verification. + Fastify plugin for reCAPTCHA verification. - [`fastify-redis-channels`](https://github.com/hearit-io/fastify-redis-channels) A plugin for fast, reliable, and scalable channels implementation based on Redis streams. @@ -604,7 +604,7 @@ middlewares into Fastify plugins - [`fastify-sentry`](https://github.com/alex-ppg/fastify-sentry) Fastify plugin to add the Sentry SDK error handler to requests. - [`fastify-sequelize`](https://github.com/lyquocnam/fastify-sequelize) Fastify - plugin work with Sequelize (adapter for NodeJS -> Sqlite, Mysql, Mssql, + plugin work with Sequelize (adapter for Node.js -> Sqlite, Mysql, Mssql, Postgres). - [`fastify-server-session`](https://github.com/jsumners/fastify-server-session) A session plugin with support for arbitrary backing caches via @@ -685,7 +685,7 @@ middlewares into Fastify plugins - [`fastify-xml-body-parser`](https://github.com/NaturalIntelligence/fastify-xml-body-parser) Parse XML payload / request body into JS / JSON object. - [`http-wizard`](https://github.com/flodlc/http-wizard) - Exports a typescript api client for your Fastify api and ensures fullstack type + Exports a typescript API client for your Fastify API and ensures fullstack type safety for your project. - [`i18next-http-middleware`](https://github.com/i18next/i18next-http-middleware#fastify-usage) An [i18next](https://www.i18next.com) based i18n (internationalization) diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index 6aa3ea71b6a..ae5e7d6f599 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -353,7 +353,7 @@ This variant becomes extremely useful if you plan to distribute your plugin, as described in the next section. As you probably noticed by now, `request` and `reply` are not the standard -Nodejs *request* and *response* objects, but Fastify's objects. +Node.js *request* and *response* objects, but Fastify's objects. ## How to handle encapsulation and distribution diff --git a/docs/Guides/Prototype-Poisoning.md b/docs/Guides/Prototype-Poisoning.md index e6b0b2be43a..c2ff2f1cc31 100644 --- a/docs/Guides/Prototype-Poisoning.md +++ b/docs/Guides/Prototype-Poisoning.md @@ -43,7 +43,7 @@ defect a validation library can have. To understand this, we need to understand how JavaScript works a bit. Every object in JavaScript can have a prototype. It is a set of methods and properties it "inherits" from another object. I have put inherits in quotes -because JavaScript isn't really an object-oriented language.It is prototype- +because JavaScript isn't really an object-oriented language. It is a prototype- based object-oriented language. A long time ago, for a bunch of irrelevant reasons, someone decided that it diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 070029e2d11..ae98a1aa17a 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -338,7 +338,7 @@ but the body is being consumed. | Make sure you don't consume the `Response.body | FST_ERR_INIT_OPTS_INVALID | Invalid initialization options. | Use valid initialization options. | [#1471](https://github.com/fastify/fastify/pull/1471) | | FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE | Cannot set forceCloseConnections to `idle` as your HTTP server does not support `closeIdleConnections` method. | Use a different value for `forceCloseConnections`. | [#3925](https://github.com/fastify/fastify/pull/3925) | | FST_ERR_DUPLICATED_ROUTE | The HTTP method already has a registered controller for that URL. | Use a different URL or register the controller for another HTTP method. | [#2954](https://github.com/fastify/fastify/pull/2954) | -| FST_ERR_BAD_URL | The router received an invalid url. | Use a valid URL. | [#2106](https://github.com/fastify/fastify/pull/2106) | +| FST_ERR_BAD_URL | The router received an invalid URL. | Use a valid URL. | [#2106](https://github.com/fastify/fastify/pull/2106) | | FST_ERR_ASYNC_CONSTRAINT | The router received an error when using asynchronous constraints. | - | [#4323](https://github.com/fastify/fastify/pull/4323) | | FST_ERR_DEFAULT_ROUTE_INVALID_TYPE | The `defaultRoute` type should be a function. | Use a function for the `defaultRoute`. | [#2733](https://github.com/fastify/fastify/pull/2733) | | FST_ERR_INVALID_URL | URL must be a string. | Use a string for the URL. | [#3653](https://github.com/fastify/fastify/pull/3653) | diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 0edeffa39b1..64874c242fc 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -106,7 +106,7 @@ is generated. See Fastify Factory The default logger is configured with a set of standard serializers that serialize objects with `req`, `res`, and `err` properties. The object received by `req` is the Fastify [`Request`](./Request.md) object, while the object -received by `res` is the Fastify [`Reply`](./Reply.md) object. This behaviour +received by `res` is the Fastify [`Reply`](./Reply.md) object. This behavior can be customized by specifying custom serializers. ```js diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 430457212b2..bbaf4f510ee 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -62,7 +62,7 @@ since the request was received by Fastify. - `.hasTrailer(key)` - Determine if a trailer has been set. - `.removeTrailer(key)` - Remove the value of a previously set trailer. - `.type(value)` - Sets the header `Content-Type`. -- `.redirect([code,] dest)` - Redirect to the specified url, the status code is +- `.redirect([code,] dest)` - Redirect to the specified URL, the status code is optional (default to `302`). - `.callNotFound()` - Invokes the custom not found handler. - `.serialize(payload)` - Serializes the specified payload using the default @@ -783,7 +783,7 @@ headers in one place. The payload provided inside `Response` is considered to be pre-serialized, so they will be sent unmodified without response validation. -Plese be aware when using `Response`, the status code and headers +Please be aware when using `Response`, the status code and headers will not directly reflect to `reply.statusCode` and `reply.getHeaders()`. Such behavior is based on `Response` only allow `readonly` status code and headers. The data is not allow to be bi-direction editing, @@ -842,7 +842,7 @@ To customize the JSON error output you can do it by: - add the additional properties to the `Error` instance Notice that if the returned status code is not in the response schema list, the -default behaviour will be applied. +default behavior will be applied. ```js fastify.get('/', { diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index f23081c9203..2be8393d7de 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -572,7 +572,7 @@ const fastify = require('fastify')({ comma separated values (e.g. `'127.0.0.1,192.168.1.1/24'`). + `Array`: Trust only given IP/CIDR list (e.g. `['127.0.0.1']`). + `number`: Trust the nth hop from the front-facing proxy server as the client. -+ `Function`: Custom trust function that takes `address` as first arg ++ `Function`: Custom trust function that takes `address` as first argument ```js function myTrustFn(address, hop) { return address === '1.2.3.4' || hop === 1 @@ -871,7 +871,7 @@ Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which suppor separating the path and query string with a `;` character (code 59), e.g. `/dev;foo=bar`. This decision originated from [delvedor/find-my-way#76] (https://github.com/delvedor/find-my-way/issues/76). Thus, this option will support -backwards compatiblilty for the need to split on `;`. To disable support for splitting +backwards compatibility for the need to split on `;`. To disable support for splitting on `;` set `useSemicolonDelimiter` to `false`. ```js diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index d339ef83c7f..2eb057b983e 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -34,7 +34,7 @@ examples there is further, more detailed documentation for the type system. This example will get you up and running with Fastify and TypeScript. It results in a blank http Fastify server. -1. Create a new npm project, install Fastify, and install typescript & node.js +1. Create a new npm project, install Fastify, and install typescript & Node.js types as peer dependencies: ```bash npm init -y @@ -157,7 +157,7 @@ route-level `request` object. ``` 4. Build and run the server code with `npm run build` and `npm run start` -5. Query the api +5. Query the API ```bash curl localhost:8080/auth?username=admin&password=Password123! ``` @@ -971,7 +971,7 @@ Union type of: `'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | ##### fastify.RawServerBase [src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L13) -Dependant on `@types/node` modules `http`, `https`, `http2` +Dependent on `@types/node` modules `http`, `https`, `http2` Union type of: `http.Server | https.Server | http2.Http2Server | http2.Http2SecureServer` @@ -979,7 +979,7 @@ http2.Http2SecureServer` ##### fastify.RawServerDefault [src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L18) -Dependant on `@types/node` modules `http` +Dependent on `@types/node` modules `http` Type alias for `http.Server` @@ -1098,7 +1098,7 @@ Learn by Example section: [JSON Schema](#json-schema). ##### fastify.RawRequestDefaultExpression\<[RawServer][RawServerGeneric]\> [src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L23) -Dependant on `@types/node` modules `http`, `https`, `http2` +Dependent on `@types/node` modules `http`, `https`, `http2` Generic parameter `RawServer` defaults to [`RawServerDefault`][RawServerDefault] @@ -1158,7 +1158,7 @@ declare module 'fastify' { ##### fastify.RawReplyDefaultExpression<[RawServer][RawServerGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L27) -Dependant on `@types/node` modules `http`, `https`, `http2` +Dependent on `@types/node` modules `http`, `https`, `http2` Generic parameter `RawServer` defaults to [`RawServerDefault`][RawServerDefault] diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 460373f9213..790e79bfe5f 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -435,7 +435,7 @@ validator you are using._ The `setValidatorCompiler` function makes it easy to substitute `ajv` with -almost any Javascript validation library ([joi](https://github.com/hapijs/joi/), +almost any JavaScript validation library ([joi](https://github.com/hapijs/joi/), [yup](https://github.com/jquense/yup/), ...) or a custom one: ```js diff --git a/fastify.js b/fastify.js index 8e8d7e7152f..0f0c6c911ce 100644 --- a/fastify.js +++ b/fastify.js @@ -726,7 +726,7 @@ function fastify (options) { // In the vast majority of cases, it's a network error and/or some // config issue on the load balancer side. this.log.trace({ err }, `client ${errorLabel}`) - // Copying standard node behaviour + // Copying standard node behavior // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666 // If the socket is not writable, there is no reason to try to send data. diff --git a/lib/fourOhFour.js b/lib/fourOhFour.js index b5f71432c28..da12232ec12 100644 --- a/lib/fourOhFour.js +++ b/lib/fourOhFour.js @@ -23,7 +23,7 @@ const { getGenReqId } = require('./reqIdGenFactory.js') /** * Each fastify instance have a: - * kFourOhFourLevelInstance: point to a fastify instance that has the 404 handler setted + * kFourOhFourLevelInstance: point to a fastify instance that has the 404 handler set * kCanSetNotFoundHandler: bool to track if the 404 handler has already been set * kFourOhFour: the singleton instance of this 404 module * kFourOhFourContext: the context in the reply object where the handler will be executed diff --git a/test/404s.test.js b/test/404s.test.js index b1f1a616e6d..362c25379ec 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -1081,12 +1081,12 @@ test('Unknown method', t => { const handler = () => {} // See https://github.com/fastify/light-my-request/pull/20 t.throws(() => fastify.inject({ - method: 'UNKNWON_METHOD', + method: 'UNKNOWN_METHOD', url: '/' }, handler), Error) sget({ - method: 'UNKNWON_METHOD', + method: 'UNKNOWN_METHOD', url: getServerUrl(fastify) }, (err, response, body) => { t.error(err) diff --git a/test/close.test.js b/test/close.test.js index 6b8a9e0b610..03817d6b1a6 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -263,7 +263,7 @@ test('Current opened connection should NOT continue to work after closing and re client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.on('error', function () { - // Dependending on the Operating System + // Depending on the Operating System // the socket could error or not. // However, it will always be closed. }) diff --git a/test/encapsulated-error-handler.test.js b/test/encapsulated-error-handler.test.js index aba8c1a8c1f..bfd8b7d4e70 100644 --- a/test/encapsulated-error-handler.test.js +++ b/test/encapsulated-error-handler.test.js @@ -3,7 +3,7 @@ const { test } = require('tap') const Fastify = require('..') -test('encapuslates an error handler', async t => { +test('encapsulates an error handler', async t => { t.plan(3) const fastify = Fastify() diff --git a/test/hooks.test.js b/test/hooks.test.js index 1cd2968546d..3ee6c8d80a8 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -721,7 +721,7 @@ test('onRoute hook should be called once when prefixTrailingSlash', t => { fastify.ready(err => { t.error(err) t.equal(onRouteCalled, 1) // onRoute hook was called once - t.equal(routePatched, 1) // and plugin acted once and avoided redundaunt route patching + t.equal(routePatched, 1) // and plugin acted once and avoided redundant route patching }) }) @@ -2249,7 +2249,7 @@ test('onRequest, preHandler, and onResponse hooks that resolve to a value do not }) }) -test('If a response header has been set inside an hook it shoulod not be overwritten by the final response handler', t => { +test('If a response header has been set inside an hook it should not be overwritten by the final response handler', t => { t.plan(5) const fastify = Fastify() diff --git a/test/internals/handleRequest.test.js b/test/internals/handleRequest.test.js index 6635a952fe8..f423d9df9d4 100644 --- a/test/internals/handleRequest.test.js +++ b/test/internals/handleRequest.test.js @@ -13,9 +13,9 @@ const Ajv = require('ajv') const ajv = new Ajv({ coerceTypes: true }) function schemaValidator ({ schema, method, url, httpPart }) { - const validateFuncion = ajv.compile(schema) + const validateFunction = ajv.compile(schema) const fn = function (body) { - const isOk = validateFuncion(body) + const isOk = validateFunction(body) if (isOk) return return false } diff --git a/test/logger/instantiation.test.js b/test/logger/instantiation.test.js index ffa38ac8cab..5e0e9964b79 100644 --- a/test/logger/instantiation.test.js +++ b/test/logger/instantiation.test.js @@ -49,7 +49,7 @@ t.test('logger instantiation', (t) => { for await (const [line] of on(stream, 'data')) { const regex = lines.shift() - t.ok(regex.test(line.msg), '"' + line.msg + '" dont match "' + regex + '"') + t.ok(regex.test(line.msg), '"' + line.msg + '" does not match "' + regex + '"') if (lines.length === 0) break } }) diff --git a/test/logger/options.test.js b/test/logger/options.test.js index ba665821c36..cd6e0c321fb 100644 --- a/test/logger/options.test.js +++ b/test/logger/options.test.js @@ -14,7 +14,7 @@ t.test('logger options', (t) => { t.plan(12) - t.test('logger can be silented', (t) => { + t.test('logger can be silenced', (t) => { t.plan(17) const fastify = Fastify({ logger: false diff --git a/test/schema-examples.test.js b/test/schema-examples.test.js index fc2a26ccdf7..c27361089c4 100644 --- a/test/schema-examples.test.js +++ b/test/schema-examples.test.js @@ -320,7 +320,7 @@ test('Example - serialization 2', t => { } }, 201: { - // the contract sintax + // the contract syntax value: { type: 'string' } } } diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index 8bf208ed317..08911bfc209 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -207,7 +207,7 @@ test('Should throw of the schema does not exists in output', t => { fastify.ready(err => { t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') - t.match(err.message, /^Failed building the serialization schema for GET: \/:id, due to error Cannot find reference.*/) // error from fast-json-strinfigy + t.match(err.message, /^Failed building the serialization schema for GET: \/:id, due to error Cannot find reference.*/) // error from fast-json-stringify }) }) @@ -1267,7 +1267,7 @@ test('Check how many AJV instances are built #2 - verify validatorPool', t => { fastify.register(function sibling3 (instance, opts, done) { addRandomRoute(instance) - // this trigger to dont't reuse the same compiler pool + // this trigger to don't reuse the same compiler pool instance.addSchema({ $id: 'diff', type: 'object' }) t.notOk(instance.validatorCompiler, 'validator not initialized') @@ -1555,7 +1555,7 @@ test('setSchemaController: Inherits correctly parent schemas with a customized v t.equal(json.message, 'querystring/msg must be array') t.equal(json.statusCode, 400) - t.equal(res.statusCode, 400, 'Should not coearce the string into array') + t.equal(res.statusCode, 400, 'Should not coerce the string into array') }) test('setSchemaController: Inherits buildSerializer from parent if not present within the instance', async t => { @@ -1785,7 +1785,7 @@ test('setSchemaController: Inherits buildValidator from parent if not present wi t.equal(rootSerializerCalled, 0, 'Should be called from the child') t.equal(rootValidatorCalled, 1, 'Should not be called from the child') t.equal(childSerializerCalled, 1, 'Should be called from the child') - t.equal(res.statusCode, 400, 'Should not coearce the string into array') + t.equal(res.statusCode, 400, 'Should not coerce the string into array') }) test('Should throw if not default validator passed', async t => { @@ -1867,7 +1867,7 @@ test('Should throw if not default validator passed', async t => { }) t.equal(res.json().message, 'querystring/msg must be array') - t.equal(res.statusCode, 400, 'Should not coearce the string into array') + t.equal(res.statusCode, 400, 'Should not coerce the string into array') } catch (err) { t.error(err) } @@ -1928,7 +1928,7 @@ test('Should coerce the array if the default validator is used', async t => { }) t.equal(res.statusCode, 200) - t.same(res.json(), { msg: ['string'] }, 'Should coearce the string into array') + t.same(res.json(), { msg: ['string'] }, 'Should coerce the string into array') } catch (err) { t.error(err) } diff --git a/test/schema-serialization.test.js b/test/schema-serialization.test.js index a0ceb380cf3..f122e020a6c 100644 --- a/test/schema-serialization.test.js +++ b/test/schema-serialization.test.js @@ -55,7 +55,7 @@ test('custom serializer options', t => { fastify.inject('/', (err, res) => { t.error(err) - t.equal(res.payload, '5', 'it must use the ceil rouding') + t.equal(res.payload, '5', 'it must use the ceil rounding') t.equal(res.statusCode, 200) }) }) @@ -945,7 +945,7 @@ test('error in custom schema serialize compiler, throw FST_ERR_SCH_SERIALIZATION }) }) -test('Errors in searilizer sended to errorHandler', async t => { +test('Errors in serializer send to errorHandler', async t => { let savedError const fastify = Fastify() diff --git a/test/schema-special-usage.test.js b/test/schema-special-usage.test.js index e71dc053d6c..bc8ae4634d6 100644 --- a/test/schema-special-usage.test.js +++ b/test/schema-special-usage.test.js @@ -703,7 +703,7 @@ test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t => t.pass('fastify is ready') }) -test('The default schema compilers should not be called when overwritte by the user', async t => { +test('The default schema compilers should not be called when overwritten by the user', async t => { const Fastify = t.mock('../', { '@fastify/ajv-compiler': () => { t.fail('The default validator compiler should not be called') diff --git a/test/skip-reply-send.test.js b/test/skip-reply-send.test.js index fa9a418967d..4de6cc2321f 100644 --- a/test/skip-reply-send.test.js +++ b/test/skip-reply-send.test.js @@ -220,7 +220,7 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { }) }) - test('Throwing an error doesnt trigger any hooks', t => { + test('Throwing an error does not trigger any hooks', t => { const stream = split(JSON.parse) const app = Fastify({ logger: { diff --git a/test/stream.5.test.js b/test/stream.5.test.js index 5d08f2fe01e..27986dd1d56 100644 --- a/test/stream.5.test.js +++ b/test/stream.5.test.js @@ -150,7 +150,7 @@ test('request terminated should not crash fastify', t => { await new Promise((resolve) => { setTimeout(resolve, 100).unref() }) - stream.push('

should disply on second stream

') + stream.push('

should display on second stream

') stream.push(null) return reply }) @@ -184,7 +184,7 @@ test('request terminated should not crash fastify', t => { payload += chunk.toString() }) res.on('end', function () { - t.equal(payload, '

HTML

should disply on second stream

') + t.equal(payload, '

HTML

should display on second stream

') t.pass('should end properly') }) }) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 2eaac8be13c..e943ef73928 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -253,7 +253,7 @@ expectAssignable(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 expectAssignable(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true }, () => {})) expectAssignable(server.listen({ port: 3000, host: '::/0', ipv6Only: true }, () => {})) -// test listen opts objects Typescript deprectation exclusion +// test listen opts objects Typescript deprecation exclusion expectNotDeprecated(server.listen()) expectNotDeprecated(server.listen({ port: 3000 })) expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0' })) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index e7894fb4229..9809a5ed234 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -164,7 +164,7 @@ expectAssignable(server.withTypeProvider().route({ } })) -// infering schema `as const` +// inferring schema `as const` expectAssignable(server.withTypeProvider().get( '/', diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 04447e816a0..85de1445bd7 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -72,7 +72,7 @@ export type FastifyReplyType = Reply // Resolves the Reply type either via generic argument or from response schema. This type uses a different // resolution strategy to Requests where the Reply will infer a union of each status code type specified -// by the user. The Reply can be explicitly overriden by users providing a generic Reply type on the route. +// by the user. The Reply can be explicitly overridden by users providing a generic Reply type on the route. export type ResolveFastifyReplyType = UndefinedToUnknown extends never ? ResolveReplyFromSchemaCompiler : RouteGeneric['Reply']> // ----------------------------------------------------------------------------------------------- From 1a4972c4eee7b5d4c10f6da9c9c60449f87fe84d Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Tue, 19 Mar 2024 16:13:16 +0100 Subject: [PATCH 0613/1295] Remove ES module warning (#5361) https://github.com/fastify/fastify/issues/4241 seems to closed. Signed-off-by: Melroy van den Berg --- docs/Reference/TypeScript.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 2eb057b983e..bf44cdb18b7 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -61,11 +61,6 @@ in a blank http Fastify server. *Note: Set `target` property in `tsconfig.json` to `es2017` or greater to avoid [FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.* -*Note 2: Avoid using ```"moduleResolution": "NodeNext"``` in tsconfig.json with -```"type": "module"``` in package.json. This combination is currently not -supported by fastify typing system. -[ts(2349)](https://github.com/fastify/fastify/issues/4241) warning.* - 4. Create an `index.ts` file - this will contain the server code 5. Add the following code block to your file: ```typescript From ac462b2b4d859e88d029019869a9cb4b8626e6fd Mon Sep 17 00:00:00 2001 From: Giovanni Fiordeponti <38134891+flower-of-the-bridges@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:23:08 +0100 Subject: [PATCH 0614/1295] docs(ecosystem): add fastify-msgraph-change-notifications-webhook to community plugins (#5360) * Update Ecosystem.md add fastify-msgraph-change-notifications-webhook to community plugins Signed-off-by: Giovanni Fiordeponti <38134891+flower-of-the-bridges@users.noreply.github.com> * fix: markdown lint in Ecosystem.md Signed-off-by: Giovanni Fiordeponti <38134891+flower-of-the-bridges@users.noreply.github.com> --------- Signed-off-by: Giovanni Fiordeponti <38134891+flower-of-the-bridges@users.noreply.github.com> --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 5b1cfbbb285..ae8863eec81 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -485,6 +485,9 @@ middlewares into Fastify plugins [mqtt](https://www.npmjs.com/package/mqtt) client across Fastify. - [`fastify-msgpack`](https://github.com/kenriortega/fastify-msgpack) Fastify and MessagePack, together at last. Uses @msgpack/msgpack by default. +- [`fastify-msgraph-webhook`](https://github.com/flower-of-the-bridges/fastify-msgraph-change-notifications-webhook) + to manage + [MS Graph Change Notifications webhooks](https://learn.microsoft.com/it-it/graph/change-notifications-delivery-webhooks?tabs=http). - [`fastify-multer`](https://github.com/fox1t/fastify-multer) Multer is a plugin for handling multipart/form-data, which is primarily used for uploading files. - [`fastify-nats`](https://github.com/mahmed8003/fastify-nats) Plugin to share From 76674fdf46e4da5f1c18e0c86f615c4b538282cd Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Sat, 23 Mar 2024 10:03:53 +0100 Subject: [PATCH 0615/1295] docs: Add GitHub links to Type Providers + mention Zod (#5365) * Add GitHub links and mention Zod Signed-off-by: Melroy van den Berg * Fix text for TypeBox Signed-off-by: Melroy van den Berg * Create links around the package names Signed-off-by: Melroy van den Berg * Also list the wrapper packages on Type Provider page + consistency Signed-off-by: Melroy van den Berg * Update docs/Reference/TypeScript.md Co-authored-by: Frazer Smith Signed-off-by: Melroy van den Berg * Add 3rd party Signed-off-by: Melroy van den Berg * Moved 3rs party line Signed-off-by: Melroy van den Berg * Fix linter? Signed-off-by: Melroy van den Berg * Make linter happy, plz Signed-off-by: Melroy van den Berg --------- Signed-off-by: Melroy van den Berg Co-authored-by: Frazer Smith --- docs/Reference/Type-Providers.md | 14 ++++++++++---- docs/Reference/TypeScript.md | 15 ++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 85d5a430930..0f81be2c274 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -17,10 +17,16 @@ convention, and there are several community ones available as well. The following inference packages are supported: -- `json-schema-to-ts` - - [github](https://github.com/ThomasAribart/json-schema-to-ts) -- `typebox` - [github](https://github.com/sinclairzx81/typebox) -- `zod` - [github](https://github.com/colinhacks/zod) +- [`json-schema-to-ts`](https://github.com/ThomasAribart/json-schema-to-ts) +- [`typebox`](https://github.com/sinclairzx81/typebox) +- [`zod`](https://github.com/colinhacks/zod) + +See also the Type Provider wrapper packages for each of the packages respectively: + +- [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) +- [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox) +- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod) + (3rd party) ### Json Schema to Ts diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index bf44cdb18b7..a28000a94c5 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -202,21 +202,23 @@ Here are some options on how to achieve this. Fastify offers two packages wrapping `json-schema-to-ts` and `typebox`: -- `@fastify/type-provider-json-schema-to-ts` -- `@fastify/type-provider-typebox` +- [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) +- [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox) + +And a `zod` wrapper by a third party called [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod) They simplify schema validation setup and you can read more about them in [Type Providers](./Type-Providers.md) page. -Below is how to setup schema validation using vanilla `typebox` and +Below is how to setup schema validation using _vanilla_ `typebox` and `json-schema-to-ts` packages. -#### typebox +#### TypeBox A useful library for building types and a schema at once is -[typebox](https://www.npmjs.com/package/@sinclair/typebox) along with +[TypeBox](https://www.npmjs.com/package/@sinclair/typebox) along with [fastify-type-provider-typebox](https://github.com/fastify/fastify-type-provider-typebox). -With typebox you define your schema within your code and use them +With TypeBox you define your schema within your code and use them directly as types or schemas as you need them. When you want to use it for validation of some payload in a fastify route you @@ -269,7 +271,6 @@ can do it as follows: ) ``` - #### Schemas in JSON Files In the last example we used interfaces to define the types for the request From 5f163d4d6c322a9946410a8254a9341f75f8cbb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Mon, 25 Mar 2024 11:00:55 +0100 Subject: [PATCH 0616/1295] feat: hasParser, only accept string or RegExp (#5372) --- lib/contentTypeParser.js | 60 +++++++++++++++++++++------------ test/content-parser.test.js | 65 ++++++++++++++++++++++++++++++++---- test/content-type.test.js | 33 ------------------ test/custom-parser.1.test.js | 41 ++++++++++++++++++++++- 4 files changed, 138 insertions(+), 61 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 8d13ed5541b..3b18286a262 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -42,11 +42,15 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { const contentTypeIsString = typeof contentType === 'string' - if (typeof parserFn !== 'function') throw new FST_ERR_CTP_INVALID_HANDLER() - if (!contentTypeIsString && !(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() if (contentTypeIsString) { contentType = contentType.trim().toLowerCase() if (contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE() + } else if (!(contentType instanceof RegExp)) { + throw new FST_ERR_CTP_INVALID_TYPE() + } + + if (typeof parserFn !== 'function') { + throw new FST_ERR_CTP_INVALID_HANDLER() } if (this.existingParser(contentType)) { @@ -66,21 +70,29 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { parserFn ) - if (contentTypeIsString && contentType === '*') { + if (contentType === '*') { this.customParsers.set('', parser) } else { if (contentTypeIsString) { this.parserList.unshift(contentType) + this.customParsers.set(contentType, parser) } else { validateRegExp(contentType) this.parserRegExpList.unshift(contentType) + this.customParsers.set(contentType.toString(), parser) } - this.customParsers.set(contentType.toString(), parser) } } ContentTypeParser.prototype.hasParser = function (contentType) { - return this.customParsers.has(typeof contentType === 'string' ? contentType : contentType.toString()) + if (typeof contentType === 'string') { + contentType = contentType.trim().toLowerCase() + } else { + if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() + contentType = contentType.toString() + } + + return this.customParsers.has(contentType) } ContentTypeParser.prototype.existingParser = function (contentType) { @@ -95,18 +107,20 @@ ContentTypeParser.prototype.existingParser = function (contentType) { } ContentTypeParser.prototype.getParser = function (contentType) { - if (this.hasParser(contentType)) { - return this.customParsers.get(contentType) - } + let parser = this.customParsers.get(contentType) + if (parser !== undefined) return parser - const parser = this.cache.get(contentType) + parser = this.cache.get(contentType) if (parser !== undefined) return parser // eslint-disable-next-line no-var for (var i = 0; i !== this.parserList.length; ++i) { const parserListItem = this.parserList[i] - if (contentType.indexOf(parserListItem) === 0) { - const parser = this.customParsers.get(parserListItem) + if ( + contentType.indexOf(parserListItem) === 0 && + (contentType.length === parserListItem.length || contentType.charCodeAt(parserListItem.length) === 59 /* `;` */ || contentType.charCodeAt(parserListItem.length) === 32 /* ` ` */) + ) { + parser = this.customParsers.get(parserListItem) this.cache.set(contentType, parser) return parser } @@ -116,7 +130,7 @@ ContentTypeParser.prototype.getParser = function (contentType) { for (var j = 0; j !== this.parserRegExpList.length; ++j) { const parserRegExp = this.parserRegExpList[j] if (parserRegExp.test(contentType)) { - const parser = this.customParsers.get(parserRegExp.toString()) + parser = this.customParsers.get(parserRegExp.toString()) this.cache.set(contentType, parser) return parser } @@ -133,13 +147,19 @@ ContentTypeParser.prototype.removeAll = function () { } ContentTypeParser.prototype.remove = function (contentType) { - if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() - - const removed = this.customParsers.delete(contentType.toString()) + let parsers - const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList + if (typeof contentType === 'string') { + contentType = contentType.trim().toLowerCase() + parsers = this.parserList + } else { + if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() + contentType = contentType.toString() + parsers = this.parserRegExpList + } - const idx = parsers.findIndex(ct => ct.toString() === contentType.toString()) + const removed = this.customParsers.delete(contentType) + const idx = parsers.findIndex(ct => ct.toString() === contentType) if (idx > -1) { parsers.splice(idx, 1) @@ -175,7 +195,7 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply } else { const result = parser.fn(request, request[kRequestPayloadStream], done) - if (result && typeof result.then === 'function') { + if (typeof result?.then === 'function') { result.then(body => done(null, body), done) } } @@ -198,9 +218,7 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply function rawBody (request, reply, options, parser, done) { const asString = parser.asString const limit = options.limit === null ? parser.bodyLimit : options.limit - const contentLength = request.headers['content-length'] === undefined - ? NaN - : Number(request.headers['content-length']) + const contentLength = Number(request.headers['content-length']) if (contentLength > limit) { // We must close the connection as the client is going diff --git a/test/content-parser.test.js b/test/content-parser.test.js index f79170a5575..88356c7741f 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -12,19 +12,20 @@ const third = function (req, payload, done) {} test('hasContentTypeParser', t => { test('should know about internal parsers', t => { - t.plan(4) + t.plan(5) const fastify = Fastify() fastify.ready(err => { t.error(err) t.ok(fastify.hasContentTypeParser('application/json')) t.ok(fastify.hasContentTypeParser('text/plain')) + t.ok(fastify.hasContentTypeParser(' text/plain ')) t.notOk(fastify.hasContentTypeParser('application/jsoff')) }) }) - test('should work with string and RegExp', t => { - t.plan(7) + test('should only work with string and RegExp', t => { + t.plan(8) const fastify = Fastify() fastify.addContentTypeParser(/^image\/.*/, first) @@ -38,6 +39,7 @@ test('hasContentTypeParser', t => { t.notOk(fastify.hasContentTypeParser(/^image\/.+\+xml/)) t.notOk(fastify.hasContentTypeParser('image/png')) t.notOk(fastify.hasContentTypeParser('*')) + t.throws(() => fastify.hasContentTypeParser(123), FST_ERR_CTP_INVALID_TYPE) }) t.end() @@ -45,7 +47,7 @@ test('hasContentTypeParser', t => { test('getParser', t => { test('should return matching parser', t => { - t.plan(3) + t.plan(6) const fastify = Fastify() @@ -56,9 +58,12 @@ test('getParser', t => { t.equal(fastify[keys.kContentTypeParser].getParser('application/t+xml').fn, second) t.equal(fastify[keys.kContentTypeParser].getParser('image/png').fn, first) t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, third) + t.equal(fastify[keys.kContentTypeParser].getParser('text/html; charset=utf-8').fn, third) + t.equal(fastify[keys.kContentTypeParser].getParser('text/html ; charset=utf-8').fn, third) + t.equal(fastify[keys.kContentTypeParser].getParser('text/htmlINVALID')?.fn, undefined) }) - test('should return matching parser with caching', t => { + test('should return matching parser with caching /1', t => { t.plan(6) const fastify = Fastify() @@ -73,6 +78,21 @@ test('getParser', t => { t.equal(fastify[keys.kContentTypeParser].cache.size, 1) }) + test('should return matching parser with caching /2', t => { + t.plan(6) + + const fastify = Fastify() + + fastify.addContentTypeParser(/^text\/html(;\s*charset=[^;]+)?$/, first) + + t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 1) + t.equal(fastify[keys.kContentTypeParser].getParser('text/html;charset=utf-8').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 2) + t.equal(fastify[keys.kContentTypeParser].getParser('text/html;charset=utf-8').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 2) + }) + test('should prefer content type parser with string value', t => { t.plan(2) @@ -199,6 +219,36 @@ test('add', t => { t.equal(contentTypeParser.customParsers.get('').fn, first) }) + test('should lowercase contentTypeParser name', async t => { + t.plan(1) + const fastify = Fastify() + fastify.addContentTypeParser('text/html', function (req, done) { + done() + }) + try { + fastify.addContentTypeParser('TEXT/html', function (req, done) { + done() + }) + } catch (err) { + t.same(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) + } + }) + + test('should trim contentTypeParser name', async t => { + t.plan(1) + const fastify = Fastify() + fastify.addContentTypeParser('text/html', function (req, done) { + done() + }) + try { + fastify.addContentTypeParser(' text/html', function (req, done) { + done() + }) + } catch (err) { + t.same(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) + } + }) + t.end() }) @@ -256,7 +306,7 @@ test('Error thrown 415 from content type is null and make post request to server test('remove', t => { test('should remove default parser', t => { - t.plan(3) + t.plan(6) const fastify = Fastify() const contentTypeParser = fastify[keys.kContentTypeParser] @@ -264,6 +314,9 @@ test('remove', t => { t.ok(contentTypeParser.remove('application/json')) t.notOk(contentTypeParser.customParsers['application/json']) t.notOk(contentTypeParser.parserList.find(parser => parser === 'application/json')) + t.ok(contentTypeParser.remove(' text/plain ')) + t.notOk(contentTypeParser.customParsers['text/plain']) + t.notOk(contentTypeParser.parserList.find(parser => parser === 'text/plain')) }) test('should remove RegExp parser', t => { diff --git a/test/content-type.test.js b/test/content-type.test.js index 2007052bff3..06e91f9b544 100644 --- a/test/content-type.test.js +++ b/test/content-type.test.js @@ -3,39 +3,6 @@ const t = require('tap') const test = t.test const Fastify = require('..') -const { - FST_ERR_CTP_ALREADY_PRESENT -} = require('../lib/errors') - -test('should lowercase contentTypeParser names', async t => { - t.plan(1) - const fastify = Fastify() - fastify.addContentTypeParser('text/html', function (req, done) { - done() - }) - try { - fastify.addContentTypeParser('TEXT/html', function (req, done) { - done() - }) - } catch (err) { - t.same(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) - } -}) - -test('should trim contentTypeParser names', async t => { - t.plan(1) - const fastify = Fastify() - fastify.addContentTypeParser('text/html', function (req, done) { - done() - }) - try { - fastify.addContentTypeParser(' text/html', function (req, done) { - done() - }) - } catch (err) { - t.same(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) - } -}) test('should remove content-type for setErrorHandler', async t => { t.plan(8) diff --git a/test/custom-parser.1.test.js b/test/custom-parser.1.test.js index 43c8355d74e..29ffe4d9cb2 100644 --- a/test/custom-parser.1.test.js +++ b/test/custom-parser.1.test.js @@ -63,7 +63,7 @@ test('Should have typeof body object with no custom parser defined, undefined bo }) }) -test('Should get the body as string', t => { +test('Should get the body as string /1', t => { t.plan(6) const fastify = Fastify() @@ -102,6 +102,45 @@ test('Should get the body as string', t => { }) }) +test('Should get the body as string /2', t => { + t.plan(6) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('text/plain/test', { parseAs: 'string' }, function (req, body, done) { + t.ok('called') + t.ok(typeof body === 'string') + try { + const plainText = body + done(null, plainText) + } catch (err) { + err.statusCode = 400 + done(err, undefined) + } + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + + sget({ + method: 'POST', + url: getServerUrl(fastify), + body: 'hello world', + headers: { + 'Content-Type': ' text/plain/test ' + } + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(body.toString(), 'hello world') + fastify.close() + }) + }) +}) + test('Should get the body as buffer', t => { t.plan(6) const fastify = Fastify() From af2ccb5ff681c1d0ac22eb7314c6fa803f73c873 Mon Sep 17 00:00:00 2001 From: Aivo Paas Date: Wed, 27 Mar 2024 10:39:56 +0200 Subject: [PATCH 0617/1295] docs: fix table layout (#5374) Signed-off-by: Aivo Paas --- docs/Reference/Errors.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index ae98a1aa17a..52370c00ebc 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -314,8 +314,7 @@ Below is a table with all the error codes that Fastify uses. | FST_ERR_LOG_INVALID_DESTINATION | The logger does not accept the specified destination. | Use a `'stream'` or a `'file'` as the destination. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_LOG_INVALID_LOGGER | The logger should have all these methods: `'info'`, `'error'`, `'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`. | Use a logger with all the required methods. | [#4520](https://github.com/fastify/fastify/pull/4520) | | FST_ERR_REP_INVALID_PAYLOAD_TYPE | Reply payload can be either a `string` or a `Buffer`. | Use a `string` or a `Buffer` for the payload. | [#1168](https://github.com/fastify/fastify/pull/1168) | -| FST_ERR_REP_RESPONSE_BODY_CONSUMED | Using `Response` as reply payload -but the body is being consumed. | Make sure you don't consume the `Response.body` | [#5286](https://github.com/fastify/fastify/pull/5286) | +| FST_ERR_REP_RESPONSE_BODY_CONSUMED | Using `Response` as reply payload, but the body is being consumed. | Make sure you don't consume the `Response.body` | [#5286](https://github.com/fastify/fastify/pull/5286) | | FST_ERR_REP_ALREADY_SENT | A response was already sent. | - | [#1336](https://github.com/fastify/fastify/pull/1336) | | FST_ERR_REP_SENT_VALUE | The only possible value for `reply.sent` is `true`. | - | [#1336](https://github.com/fastify/fastify/pull/1336) | | FST_ERR_SEND_INSIDE_ONERR | You cannot use `send` inside the `onError` hook. | - | [#1348](https://github.com/fastify/fastify/pull/1348) | From 6564ba9b7d8ec7c236cc16f4bf3cdb3cabff69f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:44:43 +0000 Subject: [PATCH 0618/1295] chore: Bump pnpm/action-setup from 2 to 3 (#5381) Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 3. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v2.0.0...v3) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/integration-alternative-runtimes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index f74a53b7c9d..f769150f259 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -46,7 +46,7 @@ jobs: nsolid-version: ${{ matrix.nsolid-version }} - name: Install Pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: ${{ matrix.pnpm-version }} From 98bcc2f26cf45f315eec4a82a57d2b5969f2476f Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Wed, 3 Apr 2024 12:16:22 +0200 Subject: [PATCH 0619/1295] docs(typescript): update generic constraints link (#5379) --- docs/Reference/TypeScript.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index a28000a94c5..05549785d90 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -717,8 +717,8 @@ constraint value(s). Read these articles for more information on TypeScript generics. - [Generic Parameter Default](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#generic-parameter-defaults) -- [Generic - Constraints](https://www.typescriptlang.org/docs/handbook/generics.html#generic-constraints) +- [Generic Constraints](https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints) + #### How to import From c90ac14c7179606bb0833447dda1b83007156fa5 Mon Sep 17 00:00:00 2001 From: Harsh Pandey Date: Wed, 3 Apr 2024 09:10:26 -0400 Subject: [PATCH 0620/1295] types: narrow code reply type based on schema (#5380) * narrow code reply type based on schema * more assertions --- test/types/type-provider.test-d.ts | 8 ++++++-- types/reply.d.ts | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index 9809a5ed234..a2778ed21bb 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -469,7 +469,9 @@ expectAssignable(server.withTypeProvider().get( res.send('hello') res.send(42) res.send({ error: 'error' }) - expectType<(payload?: string | number | { error: string }) => typeof res>(res.code(200).send) + expectType<(payload?: string) => typeof res>(res.code(200).send) + expectType<(payload?: number) => typeof res>(res.code(400).send) + expectType<(payload?: { error: string }) => typeof res>(res.code(500).send) expectError<(payload?: unknown) => typeof res>(res.code(200).send) } )) @@ -699,7 +701,9 @@ expectAssignable(server.withTypeProvider().get( res.send('hello') res.send(42) res.send({ error: 'error' }) - expectType<(payload?: string | number | { [x: string]: unknown, error?: string | undefined }) => typeof res>(res.code(200).send) + expectType<(payload?: string) => typeof res>(res.code(200).send) + expectType<(payload?: number) => typeof res>(res.code(400).send) + expectType<(payload?: { [x: string]: unknown; error?: string }) => typeof res>(res.code(500).send) expectError<(payload?: unknown) => typeof res>(res.code(200).send) } )) diff --git a/types/reply.d.ts b/types/reply.d.ts index dbb8e13ca95..0d53e88e556 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -5,7 +5,7 @@ import { FastifyBaseLogger } from './logger' import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' -import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider' +import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType, CallTypeProvider } from './type-provider' import { CodeToReplyKey, ContextConfigDefault, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes, HttpHeader } from './utils' export interface ReplyGenericInterface { @@ -23,7 +23,9 @@ type ReplyTypeConstrainer, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> = - ResolveFastifyReplyType }> + Code extends keyof SchemaCompiler['response'] ? + CallTypeProvider : + ResolveFastifyReplyType }> /** * FastifyReply is an instance of the standard http or http2 reply types. * It defaults to http.ServerResponse, and it also extends the relative reply object. From b73b812146eba3a8994e94e1e5b6164694ae5e05 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Sun, 7 Apr 2024 21:30:34 +0200 Subject: [PATCH 0621/1295] fix: errorCodes import using ESM (#5390) * Fix errorCodes for ESM Moving fastify errorCodes below module.exports = fastifty and also export it Signed-off-by: Melroy van den Berg * test: Add errorCodes test for ESM --------- Signed-off-by: Melroy van den Berg --- fastify.js | 3 +-- test/esm/errorCodes.test.mjs | 10 ++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 test/esm/errorCodes.test.mjs diff --git a/fastify.js b/fastify.js index 0f0c6c911ce..ee2be1b2545 100644 --- a/fastify.js +++ b/fastify.js @@ -893,8 +893,6 @@ function fastify (options) { } } -fastify.errorCodes = errorCodes - function validateSchemaErrorFormatter (schemaErrorFormatter) { if (typeof schemaErrorFormatter !== 'function') { throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN(typeof schemaErrorFormatter) @@ -915,5 +913,6 @@ function validateSchemaErrorFormatter (schemaErrorFormatter) { * - `import fastify, { TSC_definition } from 'fastify'` */ module.exports = fastify +module.exports.errorCodes = errorCodes module.exports.fastify = fastify module.exports.default = fastify diff --git a/test/esm/errorCodes.test.mjs b/test/esm/errorCodes.test.mjs new file mode 100644 index 00000000000..6dc0d266f5d --- /dev/null +++ b/test/esm/errorCodes.test.mjs @@ -0,0 +1,10 @@ +import { errorCodes } from '../../fastify.js' +import t from 'tap' + +t.test('errorCodes in ESM', async t => { + // test a custom fastify error using errorCodes with ESM + const customError = errorCodes.FST_ERR_VALIDATION('custom error message') + t.ok(typeof customError !== 'undefined') + t.ok(customError instanceof errorCodes.FST_ERR_VALIDATION) + t.equal(customError.message, 'custom error message') +}) From 4a35a5ae7d011b700446d7a145382958ba62d517 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Mon, 8 Apr 2024 11:09:21 +0200 Subject: [PATCH 0622/1295] fix: Extend tap with .test.mjs tests & Rename existing ESM test (#5391) * Fix other ESM test case. And add comments * Add extend tap with test.mjs files --- .taprc | 1 + test/esm/{esm.mjs => esm.test.mjs} | 0 test/esm/named-exports.mjs | 1 + test/esm/other.mjs | 1 + test/esm/plugin.mjs | 1 + 5 files changed, 4 insertions(+) rename test/esm/{esm.mjs => esm.test.mjs} (100%) diff --git a/.taprc b/.taprc index 3c1b6a03f8b..570f42180f0 100644 --- a/.taprc +++ b/.taprc @@ -8,3 +8,4 @@ node-arg: --allow-natives-syntax files: - 'test/**/*.test.js' + - 'test/**/*.test.mjs' diff --git a/test/esm/esm.mjs b/test/esm/esm.test.mjs similarity index 100% rename from test/esm/esm.mjs rename to test/esm/esm.test.mjs diff --git a/test/esm/named-exports.mjs b/test/esm/named-exports.mjs index 3f540849ff9..854e2e14505 100644 --- a/test/esm/named-exports.mjs +++ b/test/esm/named-exports.mjs @@ -1,6 +1,7 @@ import t from 'tap' import { fastify } from '../../fastify.js' +// This test is executed in index.test.js t.test('named exports support', async t => { const app = fastify() diff --git a/test/esm/other.mjs b/test/esm/other.mjs index 5d42b4b2a8d..26aa7fed777 100644 --- a/test/esm/other.mjs +++ b/test/esm/other.mjs @@ -1,3 +1,4 @@ +// Imported in both index.test.js & esm.test.mjs import t from 'tap' async function other (fastify, opts) { diff --git a/test/esm/plugin.mjs b/test/esm/plugin.mjs index 1435f563ab9..bbead40f6d8 100644 --- a/test/esm/plugin.mjs +++ b/test/esm/plugin.mjs @@ -1,3 +1,4 @@ +// Imported in both index.test.js & esm.test.mjs async function plugin (fastify, opts) { fastify.decorate('foo', opts.foo) } From be9fb25229974e1d82815e08d9a6093cd900740e Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Wed, 10 Apr 2024 07:24:59 +0200 Subject: [PATCH 0623/1295] docs(typescript): update example https server (#5389) --- docs/Reference/TypeScript.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 05549785d90..3b7d7f5ceb0 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -862,9 +862,14 @@ a more detailed http server walkthrough. import path from 'path' import fastify from 'fastify' ``` -2. Follow the steps in this official [Node.js https server - guide](https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTPS-server/) - to create the `key.pem` and `cert.pem` files +2. Perform the following steps before setting up a Fastify HTTPS server +to create the `key.pem` and `cert.pem` files: +```sh +openssl genrsa -out key.pem +openssl req -new -key key.pem -out csr.pem +openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem +rm csr.pem +``` 3. Instantiate a Fastify https server and add a route: ```typescript const server = fastify({ From bc60c42e667a6e545552d892a9351e24b1da1369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Fri, 12 Apr 2024 18:46:21 +0200 Subject: [PATCH 0624/1295] perf: limit search space for contentType (#5400) --- lib/contentTypeParser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 3b18286a262..99ca5f0399b 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -117,7 +117,7 @@ ContentTypeParser.prototype.getParser = function (contentType) { for (var i = 0; i !== this.parserList.length; ++i) { const parserListItem = this.parserList[i] if ( - contentType.indexOf(parserListItem) === 0 && + contentType.slice(0, parserListItem.length) === parserListItem && (contentType.length === parserListItem.length || contentType.charCodeAt(parserListItem.length) === 59 /* `;` */ || contentType.charCodeAt(parserListItem.length) === 32 /* ` ` */) ) { parser = this.customParsers.get(parserListItem) From 13f9b6eb9083e5f5dcd8de5895adc81101603db4 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Wed, 17 Apr 2024 11:05:57 +0200 Subject: [PATCH 0625/1295] docs(typescript): update snippet code (#5406) --- docs/Reference/TypeScript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 3b7d7f5ceb0..1caf8273249 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -1243,7 +1243,7 @@ Below is an example of the options inference in action: const server = fastify() const plugin: FastifyPluginCallback<{ -: option1: string; + option1: string; option2: boolean; }> = function (instance, opts, done) { } From 35a6b999bb719d53d1da58374bff1718694da66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 21 Apr 2024 13:24:42 +0200 Subject: [PATCH 0626/1295] fix bench co (#5414) --- .github/workflows/benchmark-parser.yml | 4 ++-- .github/workflows/benchmark.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index b9e1fad5370..7896b975904 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -33,7 +33,7 @@ jobs: - name: Install run: | - npm install --only=production --ignore-scripts + npm install --ignore-scripts - name: Run benchmark id: benchmark-pr @@ -52,7 +52,7 @@ jobs: - name: Install run: | - npm install --only=production --ignore-scripts + npm install --ignore-scripts - name: Run benchmark id: benchmark-main diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a16608f7840..178186519d5 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -33,7 +33,7 @@ jobs: - name: Install run: | - npm install --only=production --ignore-scripts + npm install --ignore-scripts - name: Run benchmark id: benchmark-pr @@ -52,7 +52,7 @@ jobs: - name: Install run: | - npm install --only=production --ignore-scripts + npm install --ignore-scripts - name: Run benchmark id: benchmark-main From c47b0330a6c3f546c7fc2535b53d641b20525ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 21 Apr 2024 17:20:55 +0200 Subject: [PATCH 0627/1295] fix: `remove-label` job uses the wrong repo (#5415) * fix: remove label gets passed the wrong repo information * add cithm too * use github event * add permissions * use list form --- .github/workflows/benchmark-parser.yml | 3 ++- .github/workflows/benchmark.yml | 7 +++++-- .github/workflows/ci.yml | 6 ++++-- .github/workflows/citgm.yml | 4 +++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 7896b975904..f4e8e182cad 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -64,7 +64,8 @@ jobs: echo 'EOF' >> $GITHUB_OUTPUT output-benchmark: - needs: [benchmark] + needs: + - benchmark runs-on: ubuntu-latest permissions: pull-requests: write diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 178186519d5..c598f4375e7 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -64,7 +64,8 @@ jobs: echo 'EOF' >> $GITHUB_OUTPUT output-benchmark: - needs: [benchmark] + needs: + - benchmark runs-on: ubuntu-latest permissions: pull-requests: write @@ -96,13 +97,15 @@ jobs: - benchmark - output-benchmark runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - name: Remove benchmark label uses: octokit/request-action@v2.x id: remove-label with: route: DELETE /repos/{repo}/issues/{issue_number}/labels/{name} - repo: ${{ github.event.pull_request.head.repo.full_name }} + repo: ${{ github.repository }} issue_number: ${{ github.event.pull_request.number }} name: benchmark env: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82a1e98ec23..a0e7b9bd8d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,13 +87,15 @@ jobs: npm run lint coverage-nix: - needs: lint + needs: + - lint permissions: contents: read uses: ./.github/workflows/coverage-nix.yml coverage-win: - needs: lint + needs: + - lint permissions: contents: read uses: ./.github/workflows/coverage-win.yml diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml index fb81215501a..a578b8b9bcc 100644 --- a/.github/workflows/citgm.yml +++ b/.github/workflows/citgm.yml @@ -90,13 +90,15 @@ jobs: - core-plugins continue-on-error: true runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - name: Remove citgm-core-plugins label uses: octokit/request-action@v2.x id: remove-label with: route: DELETE /repos/{repo}/issues/{issue_number}/labels/{name} - repo: ${{ github.event.pull_request.head.repo.full_name }} + repo: ${{ github.repository }} issue_number: ${{ github.event.pull_request.number }} name: citgm-core-plugins env: From 7942e38d78f9d98d92c17460681d7d42a278e7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 21 Apr 2024 18:11:19 +0200 Subject: [PATCH 0628/1295] only run citgm if citgm-core-plugins label is set (#5416) --- .github/workflows/citgm.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml index a578b8b9bcc..c4f51a63634 100644 --- a/.github/workflows/citgm.yml +++ b/.github/workflows/citgm.yml @@ -85,8 +85,8 @@ jobs: node-version: ${{ matrix.node-version }} remove-label: - if: always() - needs: + if: ${{ github.event.label.name == 'citgm-core-plugins' }} + needs: - core-plugins continue-on-error: true runs-on: ubuntu-latest From 69dcea15adbf1acb6f5417a3245e803be8378e29 Mon Sep 17 00:00:00 2001 From: Johaven Date: Mon, 22 Apr 2024 15:49:36 +0200 Subject: [PATCH 0629/1295] feat: adds webdav methods that require body & content type parsing (#5411) * feat: adds webdav methods that require body & content type parsing * fix: lint error * fix: UNLOCK method does not require body * fix: lock,propfind,proppatch tests with content type * fix: Routes.md * fix: tests coverage --- docs/Reference/Routes.md | 2 +- lib/handleRequest.js | 3 +- test/lock.test.js | 53 ++++++++++++++++++++++++++------ test/propfind.test.js | 48 ++++++++++++++++++++++++----- test/proppatch.test.js | 66 +++++++++++++++++++++++++++++----------- 5 files changed, 134 insertions(+), 38 deletions(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 0cbc52188a0..081b87dee91 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -42,7 +42,7 @@ fastify.route(options) [here](./Validation-and-Serialization.md) for more info. * `body`: validates the body of the request if it is a POST, PUT, PATCH, - TRACE, or SEARCH method. + TRACE, SEARCH, PROPFIND, PROPPATCH or LOCK method. * `querystring` or `query`: validates the querystring. This can be a complete JSON Schema object, with the property `type` of `object` and `properties` object of parameters, or simply the values of what would be contained in the diff --git a/lib/handleRequest.js b/lib/handleRequest.js index a29bf1e3f29..4e0e763603a 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -27,7 +27,8 @@ function handleRequest (err, request, reply) { const contentType = headers['content-type'] - if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH') { + if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH' || + method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK') { if (contentType === undefined) { if ( headers['transfer-encoding'] === undefined && diff --git a/test/lock.test.js b/test/lock.test.js index 28755886163..f67e40cf845 100644 --- a/test/lock.test.js +++ b/test/lock.test.js @@ -5,6 +5,15 @@ const test = t.test const sget = require('simple-get').concat const fastify = require('..')() +const bodySample = ` + + + + + http://example.org/~ejw/contact.html + + ` + test('can be created - lock', t => { t.plan(1) try { @@ -49,20 +58,44 @@ test('can be created - lock', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) + t.teardown(() => { + fastify.close() + }) - test('request - lock', t => { + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + test('request with body - lock', t => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, - body: ` - - - - - http://example.org/~ejw/contact.html - - `, + headers: { 'content-type': 'text/plain' }, + body: bodySample, + method: 'LOCK' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) + + test('request with body and no content type (415 error) - lock', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + body: bodySample, + method: 'LOCK' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) + + test('request without body - lock', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + headers: { 'content-type': 'text/plain' }, method: 'LOCK' }, (err, response, body) => { t.error(err) diff --git a/test/propfind.test.js b/test/propfind.test.js index 1b2bdfc878e..586eb94948f 100644 --- a/test/propfind.test.js +++ b/test/propfind.test.js @@ -5,6 +5,14 @@ const test = t.test const sget = require('simple-get').concat const fastify = require('..')() +const bodySample = ` + + + + + + ` + test('can be created - propfind', t => { t.plan(1) try { @@ -61,7 +69,9 @@ test('can be created - propfind', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) + t.teardown(() => { + fastify.close() + }) test('request - propfind', t => { t.plan(3) @@ -87,17 +97,39 @@ fastify.listen({ port: 0 }, err => { }) }) + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser test('request with body - propfind', t => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test`, - body: ` - - - - - - `, + headers: { 'content-type': 'text/plain' }, + body: bodySample, + method: 'PROPFIND' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) + + test('request with body and no content type (415 error) - propfind', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test`, + body: bodySample, + method: 'PROPFIND' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) + + test('request without body - propfind', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test`, method: 'PROPFIND' }, (err, response, body) => { t.error(err) diff --git a/test/proppatch.test.js b/test/proppatch.test.js index b4886eef52d..402285f33ef 100644 --- a/test/proppatch.test.js +++ b/test/proppatch.test.js @@ -5,6 +5,24 @@ const test = t.test const sget = require('simple-get').concat const fastify = require('..')() +const bodySample = ` + + + + + Jim Whitehead + Roy Fielding + + + + + + + + + ` + test('shorthand - proppatch', t => { t.plan(1) try { @@ -47,27 +65,39 @@ fastify.listen({ port: 0 }, err => { t.error(err) t.teardown(() => { fastify.close() }) - test('request - proppatch', t => { + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + test('request with body - proppatch', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + headers: { 'content-type': 'text/plain' }, + body: bodySample, + method: 'PROPPATCH' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) + + test('request with body and no content type (415 error) - proppatch', t => { + t.plan(3) + sget({ + url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + body: bodySample, + method: 'PROPPATCH' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + t.equal(response.headers['content-length'], '' + body.length) + }) + }) + + test('request without body - proppatch', t => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, - body: ` - - - - - Jim Whitehead - Roy Fielding - - - - - - - - - `, method: 'PROPPATCH' }, (err, response, body) => { t.error(err) From 6258d8a99ac9e5555b930e26a0bc9f34642f9455 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 22 Apr 2024 09:59:06 -0400 Subject: [PATCH 0630/1295] docs: improve onError docs by specifying what the error handler is (#5358) * docs: Improve onError docs by specifying what the error handler is * Apply suggestions from code review Signed-off-by: Aras Abbasi --------- Signed-off-by: Aras Abbasi Co-authored-by: Aras Abbasi --- docs/Reference/Hooks.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index ce316f78c6b..a9d7170e872 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -189,9 +189,11 @@ specific header in case of error. It is not intended for changing the error, and calling `reply.send` will throw an exception. -This hook will be executed only after the `customErrorHandler` has been -executed, and only if the `customErrorHandler` sends an error back to the user -*(Note that the default `customErrorHandler` always sends the error back to the +This hook will be executed only after +the [Custom Error Handler set by `setErrorHandler`](./Server.md#seterrorhandler) +has been executed, and only if the custom error handler sends an error back to the +user +*(Note that the default error handler always sends the error back to the user)*. **Notice:** unlike the other hooks, passing an error to the `done` function is not From f9f0c9f17cf328be4205ecb7b21fc0f5e404b6c6 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 22 Apr 2024 19:48:30 +0200 Subject: [PATCH 0631/1295] chore: add new sponsor (#5424) --- SPONSORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPONSORS.md b/SPONSORS.md index 6f5d8d5679d..52e8db19b9c 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -13,7 +13,7 @@ _Be the first!_ ## Tier 3 -_Be the first!_ +- [Mercedes-Benz Group](https://github.com/mercedes-benz) ## Tier 2 From fea36e3b205d8fe93eca980f62aa905208d60a7b Mon Sep 17 00:00:00 2001 From: Roberto Bianchi Date: Mon, 29 Apr 2024 09:48:14 +0200 Subject: [PATCH 0632/1295] chore(ecosystem): Add Fastify asyncforge plugin (#5429) * chore(ecosystem): Add Fastify asyncforge plugin Signed-off-by: Roberto Bianchi * Update Ecosystem.md --------- Signed-off-by: Roberto Bianchi --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index ae8863eec81..7f1acf9ecc0 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -252,6 +252,9 @@ section. plugin to authenticate HTTP requests based on API key and signature - [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify Plugin for interacting with Appwrite server. +- [`fastify-asyncforge`](https://github.com/mcollina/fastify-asyncforge) Plugin + to access Fastify instance, logger, request and reply from Node.js [Async + Local Storage](https://nodejs.org/api/async_context.html#class-asynclocalstorage). - [`fastify-at-mysql`](https://github.com/mateonunez/fastify-at-mysql) Fastify MySQL plugin with auto SQL injection attack prevention. - [`fastify-at-postgres`](https://github.com/mateonunez/fastify-at-postgres) Fastify From 20e3e351cb2b9da4a32a4f770dacc69a61be0f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Dewitte?= Date: Mon, 29 Apr 2024 14:37:52 +0200 Subject: [PATCH 0633/1295] `reply.getSerializationFunction` can return `undefined` (#5384) --- test/types/reply.test-d.ts | 4 ++-- types/reply.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 61466db3b6d..92318b33c8c 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -42,8 +42,8 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<(key: string) => boolean>(reply.hasTrailer) expectType<(key: string) => FastifyReply>(reply.removeTrailer) expectType(reply.server) - expectAssignable<((httpStatus: string) => DefaultSerializationFunction)>(reply.getSerializationFunction) - expectAssignable<((schema: {[key: string]: unknown}) => DefaultSerializationFunction)>(reply.getSerializationFunction) + expectAssignable<((httpStatus: string) => DefaultSerializationFunction | undefined)>(reply.getSerializationFunction) + expectAssignable<((schema: {[key: string]: unknown}) => DefaultSerializationFunction | undefined)>(reply.getSerializationFunction) expectAssignable<((schema: {[key: string]: unknown}, httpStatus?: string) => DefaultSerializationFunction)>(reply.compileSerializationSchema) expectAssignable<((input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string) => unknown)>(reply.serializeInput) expectAssignable<((input: {[key: string]: unknown}, httpStatus: string) => unknown)>(reply.serializeInput) diff --git a/types/reply.d.ts b/types/reply.d.ts index 0d53e88e556..f660a50a5e0 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -70,8 +70,8 @@ export interface FastifyReply< serializer(fn: (payload: any) => string): FastifyReply; serialize(payload: any): string | ArrayBuffer | Buffer; // Serialization Methods - getSerializationFunction(httpStatus: string, contentType?: string): (payload: {[key: string]: unknown}) => string; - getSerializationFunction(schema: {[key: string]: unknown}): (payload: {[key: string]: unknown}) => string; + getSerializationFunction(httpStatus: string, contentType?: string): ((payload: {[key: string]: unknown}) => string) | undefined; + getSerializationFunction(schema: {[key: string]: unknown}): ((payload: {[key: string]: unknown}) => string) | undefined; compileSerializationSchema(schema: {[key: string]: unknown}, httpStatus?: string, contentType?: string): (payload: {[key: string]: unknown}) => string; serializeInput(input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string, contentType?: string): string; serializeInput(input: {[key: string]: unknown}, httpStatus: string, contentType?: string): unknown; From 31f391dcb545b8c5879ac5066bb7e1d571dd819f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:17:10 +0000 Subject: [PATCH 0634/1295] chore: Bump pino from 8.21.0 to 9.0.0 in the dependencies-major group (#5431) Bumps the dependencies-major group with 1 update: [pino](https://github.com/pinojs/pino). Updates `pino` from 8.21.0 to 9.0.0 - [Release notes](https://github.com/pinojs/pino/releases) - [Commits](https://github.com/pinojs/pino/compare/v8.21.0...v9.0.0) --- updated-dependencies: - dependency-name: pino dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1983a46f255..0ee6cbba77b 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "fast-json-stringify": "^5.8.0", "find-my-way": "^8.0.0", "light-my-request": "^5.11.0", - "pino": "^8.17.0", + "pino": "^9.0.0", "process-warning": "^3.0.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", From d643885882c159e90e7d6b74e6c45371be1a44c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Mon, 29 Apr 2024 21:43:55 +0200 Subject: [PATCH 0635/1295] exclude node 14 and 16 on macos (#5433) --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0e7b9bd8d6..22d961a5aea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,6 +117,10 @@ jobs: # excludes node 14 on Windows - os: windows-latest node-version: 14 + - os: macos-latest + node-version: 14 + - os: macos-latest + node-version: 16 steps: - uses: actions/checkout@v4 From ad8925010af809cf136ba0864b39f74418e7cc42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 1 May 2024 15:40:32 +0200 Subject: [PATCH 0636/1295] perf: update method matching (#5419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update method matching implementation * push back string comparison * extract encoding * add missing method test * check for transferEncoding as well * skip instead of removing test * Update test/delete.test.js Co-authored-by: Manuel Spigolon Signed-off-by: Gürgün Dayıoğlu * Update test/method-missing.test.js Co-authored-by: Manuel Spigolon Signed-off-by: Gürgün Dayıoğlu --------- Signed-off-by: Gürgün Dayıoğlu Co-authored-by: Manuel Spigolon --- lib/handleRequest.js | 37 +++++++++++++--------------- lib/httpMethods.js | 48 ++++++++++++++++++++++++------------- test/delete.test.js | 22 ++++++++++++++++- test/helper.js | 6 +---- test/method-missing.test.js | 24 +++++++++++++++++++ 5 files changed, 94 insertions(+), 43 deletions(-) create mode 100644 test/method-missing.test.js diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 4e0e763603a..60ae1e00734 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -1,5 +1,6 @@ 'use strict' +const { bodylessMethods, bodyMethods } = require('./httpMethods') const { validate: validateSchema } = require('./validation') const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') const wrapThenable = require('./wrapThenable') @@ -20,42 +21,36 @@ function handleRequest (err, request, reply) { const headers = request.headers const context = request[kRouteContext] - if (method === 'GET' || method === 'HEAD') { + if (bodylessMethods.has(method)) { handler(request, reply) return } - const contentType = headers['content-type'] + if (bodyMethods.has(method)) { + const contentType = headers['content-type'] + const contentLength = headers['content-length'] + const transferEncoding = headers['transfer-encoding'] - if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH' || - method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK') { if (contentType === undefined) { if ( - headers['transfer-encoding'] === undefined && - (headers['content-length'] === '0' || headers['content-length'] === undefined) - ) { // Request has no body to parse + (contentLength === undefined || contentLength === '0') && + transferEncoding === undefined + ) { + // Request has no body to parse handler(request, reply) } else { context.contentTypeParser.run('', handler, request, reply) } } else { - context.contentTypeParser.run(contentType, handler, request, reply) - } - return - } + if (contentLength === undefined && transferEncoding === undefined && method === 'OPTIONS') { + // OPTIONS can have a Content-Type header without a body + handler(request, reply) + return + } - if (method === 'OPTIONS' || method === 'DELETE') { - if ( - contentType !== undefined && - ( - headers['transfer-encoding'] !== undefined || - headers['content-length'] !== undefined - ) - ) { context.contentTypeParser.run(contentType, handler, request, reply) - } else { - handler(request, reply) } + return } diff --git a/lib/httpMethods.js b/lib/httpMethods.js index f32abbaa099..131ea0e78c3 100644 --- a/lib/httpMethods.js +++ b/lib/httpMethods.js @@ -1,22 +1,38 @@ 'use strict' +const bodylessMethods = new Set([ + // Standard + 'GET', + 'HEAD', + 'TRACE', + + // WebDAV + 'UNLOCK' +]) + +const bodyMethods = new Set([ + // Standard + 'DELETE', + 'OPTIONS', + 'PATCH', + 'PUT', + 'POST', + + // WebDAV + 'COPY', + 'LOCK', + 'MOVE', + 'MKCOL', + 'PROPFIND', + 'PROPPATCH', + 'SEARCH' +]) + module.exports = { + bodylessMethods, + bodyMethods, supportedMethods: [ - 'DELETE', - 'GET', - 'HEAD', - 'PATCH', - 'POST', - 'PUT', - 'OPTIONS', - 'PROPFIND', - 'PROPPATCH', - 'MKCOL', - 'COPY', - 'MOVE', - 'LOCK', - 'UNLOCK', - 'TRACE', - 'SEARCH' + ...bodylessMethods, + ...bodyMethods ] } diff --git a/test/delete.test.js b/test/delete.test.js index 74f14d269d1..1a296fbe8f6 100644 --- a/test/delete.test.js +++ b/test/delete.test.js @@ -300,8 +300,28 @@ fastify.listen({ port: 0 }, err => { }) }) +test('shorthand - delete with application/json Content-Type header and null body', t => { + t.plan(4) + const fastify = require('..')() + fastify.delete('/', {}, (req, reply) => { + t.equal(req.body, null) + reply.send(req.body) + }) + fastify.inject({ + method: 'DELETE', + url: '/', + headers: { 'Content-Type': 'application/json' }, + body: 'null' + }, (err, response) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(response.payload.toString(), 'null') + }) +}) + // https://github.com/fastify/fastify/issues/936 -test('shorthand - delete with application/json Content-Type header and without body', t => { +// Skip this test because this is an invalid request +test('shorthand - delete with application/json Content-Type header and without body', { skip: 'https://github.com/fastify/fastify/pull/5419' }, t => { t.plan(4) const fastify = require('..')() fastify.delete('/', {}, (req, reply) => { diff --git a/test/helper.js b/test/helper.js index aa18f33e809..f3aeecc0d43 100644 --- a/test/helper.js +++ b/test/helper.js @@ -216,11 +216,7 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }, (err, response, body) => { t.error(err) - if (upMethod === 'OPTIONS') { - t.equal(response.statusCode, 200) - } else { - t.equal(response.statusCode, 415) - } + t.equal(response.statusCode, 415) }) }) diff --git a/test/method-missing.test.js b/test/method-missing.test.js new file mode 100644 index 00000000000..bf63bd51826 --- /dev/null +++ b/test/method-missing.test.js @@ -0,0 +1,24 @@ +const http = require('http') +const { test } = require('tap') +const Fastify = require('../fastify') + +test('missing method from http client', t => { + t.plan(2) + const fastify = Fastify() + + fastify.listen({ port: 3000 }, (err) => { + t.error(err) + + const port = fastify.server.address().port + const req = http.request({ + port, + method: 'REBIND', + path: '/' + }, (res) => { + t.equal(res.statusCode, 404) + fastify.close() + }) + + req.end() + }) +}) From 345c85ed2a7d9a1034d2c19a1d4b8cbcf21af1e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 14:26:38 +0000 Subject: [PATCH 0637/1295] chore: Bump lycheeverse/lychee-action from 1.9.3 to 1.10.0 (#5436) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.9.3 to 1.10.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/c053181aa0c3d17606addfe97a9075a32723548a...2b973e86fc7b1f6b36a93795fe2c9c6ae1118621) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index f0c5f976929..9560597069e 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a + uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 with: fail: true # As external links behavior is not predictable, we check only internal links From bf5d447a6119966b530e8be0beef88216da235f7 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 2 May 2024 09:50:47 +0200 Subject: [PATCH 0638/1295] docs: update indentation on snippet code (#5418) --- README.md | 2 ++ docs/Guides/Getting-Started.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index b163b5b64d0..ee91521284f 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ yarn add fastify // ESM import Fastify from 'fastify' + const fastify = Fastify({ logger: true }) @@ -150,6 +151,7 @@ with async-await: ```js // ESM import Fastify from 'fastify' + const fastify = Fastify({ logger: true }) diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index ec0ed1978f5..e6f57da0f6b 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -32,6 +32,7 @@ Let's write our first server: // ESM import Fastify from 'fastify' + const fastify = Fastify({ logger: true }) @@ -68,6 +69,7 @@ Do you prefer to use `async/await`? Fastify supports it out-of-the-box. ```js // ESM import Fastify from 'fastify' + const fastify = Fastify({ logger: true }) From 8d6692453244840e2eef25b460186ca423895882 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 2 May 2024 08:51:01 +0100 Subject: [PATCH 0639/1295] docs(guides/abort): suggest explicit use of the `aborted` property (#5438) --- docs/Guides/Detecting-When-Clients-Abort.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md index ce47ff366d7..644f3f6f747 100644 --- a/docs/Guides/Detecting-When-Clients-Abort.md +++ b/docs/Guides/Detecting-When-Clients-Abort.md @@ -87,10 +87,12 @@ of `{ ok: true }`. - Logic that triggers in the hook when the request is closed. - Logging that occurs when the closed request property `aborted` is true. -In the request close event, you should examine the diff between a successful -request and one aborted by the client to determine the best property for your -use case. You can find details about request properties in the -[Node.js documentation](https://nodejs.org/api/http.html). +Whilst the `aborted` property has been deprecated, `destroyed` is not a +suitable replacement as the +[Node.js documentation suggests](https://nodejs.org/api/http.html#requestaborted). +A request can be `destroyed` for various reasons, such as when the server closes +the connection. The `aborted` property is still the most reliable way to detect +when a client intentionally aborts a request. You can also perform this logic outside of a hook, directly in a specific route. From f2835db6b07a56dbc0d66b447fa4d26c786bfef5 Mon Sep 17 00:00:00 2001 From: SamSalvatico <40636569+SamSalvatico@users.noreply.github.com> Date: Thu, 2 May 2024 15:18:40 +0200 Subject: [PATCH 0640/1295] feat: disable request logging (#5435) * fix(error-handler): check for kDisableRequestLogging before logging * fix(error-handler): updated tests * fix(fourOhfour): check for logging disabled before log 404 * fix(fourOhfour): managed disableRequestLogging * fixed test for server listening message * Updated docs related to disableRequestLogging * fixed typo in docs --- docs/Reference/Server.md | 12 ++++++--- lib/error-handler.js | 39 ++++++++++++++++----------- lib/fourOhFour.js | 6 +++-- test/logger/logging.test.js | 53 ++++++++++++++++++++++++++----------- 4 files changed, 74 insertions(+), 36 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 2be8393d7de..1283f41d8ce 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -385,9 +385,15 @@ been sent. By setting this option to `true`, these log messages will be disabled. This allows for more flexible request start and end logging by attaching custom `onRequest` and `onResponse` hooks. -Please note that this option will also disable an error log written by the -default `onResponse` hook on reply callback errors. Other log messages -emitted by Fastify will stay enabled, like deprecation warnings and messages +The other log entries that will be disabled are: +- an error log written by the default `onResponse` hook on reply callback errors +- the error and info logs written by the `defaultErrorHandler` +on error management +- the info log written by the `fourOhFour` handler when a +non existent route is requested + +Other log messages emitted by Fastify will stay enabled, +like deprecation warnings and messages emitted when requests are received while the server is closing. ```js diff --git a/lib/error-handler.js b/lib/error-handler.js index 53f52631bba..0b9dbdbb996 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -7,7 +7,8 @@ const { kReplyNextErrorHandler, kReplyIsRunningOnErrorHook, kReplyHasStatusCode, - kRouteContext + kRouteContext, + kDisableRequestLogging } = require('./symbols.js') const { @@ -35,10 +36,12 @@ function handleError (reply, error, cb) { try { reply.raw.writeHead(reply.raw.statusCode, reply[kReplyHeaders]) } catch (error) { - reply.log.warn( - { req: reply.request, res: reply, err: error }, - error && error.message - ) + if (!reply.log[kDisableRequestLogging]) { + reply.log.warn( + { req: reply.request, res: reply, err: error }, + error && error.message + ) + } reply.raw.writeHead(reply.raw.statusCode) } reply.raw.end(payload) @@ -79,15 +82,19 @@ function defaultErrorHandler (error, request, reply) { reply.code(statusCode >= 400 ? statusCode : 500) } if (reply.statusCode < 500) { - reply.log.info( - { res: reply, err: error }, - error && error.message - ) + if (!reply.log[kDisableRequestLogging]) { + reply.log.info( + { res: reply, err: error }, + error && error.message + ) + } } else { - reply.log.error( - { req: request, res: reply, err: error }, - error && error.message - ) + if (!reply.log[kDisableRequestLogging]) { + reply.log.error( + { req: request, res: reply, err: error }, + error && error.message + ) + } } reply.send(error) } @@ -112,8 +119,10 @@ function fallbackErrorHandler (error, reply, cb) { statusCode: { value: statusCode } })) } catch (err) { - // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization - reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed') + if (!reply.log[kDisableRequestLogging]) { + // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization + reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed') + } reply.code(500) payload = serializeError(new FST_ERR_FAILED_ERROR_SERIALIZATION(err.message, error.message)) } diff --git a/lib/fourOhFour.js b/lib/fourOhFour.js index da12232ec12..de0575475bc 100644 --- a/lib/fourOhFour.js +++ b/lib/fourOhFour.js @@ -29,7 +29,7 @@ const { getGenReqId } = require('./reqIdGenFactory.js') * kFourOhFourContext: the context in the reply object where the handler will be executed */ function fourOhFour (options) { - const { logger } = options + const { logger, disableRequestLogging } = options // 404 router, used for handling encapsulated 404 handlers const router = FindMyWay({ onBadUrl: createOnBadUrl(), defaultRoute: fourOhFourFallBack }) @@ -49,7 +49,9 @@ function fourOhFour (options) { function basic404 (request, reply) { const { url, method } = request.raw const message = `Route ${method}:${url} not found` - request.log.info(message) + if (!disableRequestLogging) { + request.log.info(message) + } reply.code(404).send({ message, error: 'Not Found', diff --git a/test/logger/logging.test.js b/test/logger/logging.test.js index da4945ed9b1..517b5144685 100644 --- a/test/logger/logging.test.js +++ b/test/logger/logging.test.js @@ -17,7 +17,7 @@ t.test('logging', (t) => { let localhost let localhostForURL - t.plan(12) + t.plan(13) t.before(async function () { [localhost, localhostForURL] = await helper.getLoopbackHost() @@ -253,7 +253,7 @@ t.test('logging', (t) => { }) t.test('should not log incoming request and outgoing response when disabled', async (t) => { - t.plan(3) + t.plan(1) const stream = split(JSON.parse) const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) t.teardown(fastify.close.bind(fastify)) @@ -266,18 +266,12 @@ t.test('logging', (t) => { await fastify.inject({ method: 'GET', url: '/500' }) - { - const [line] = await once(stream, 'data') - t.ok(line.reqId, 'reqId is defined') - t.equal(line.msg, '500 error', 'message is set') - } - // no more readable data t.equal(stream.readableLength, 0) }) - t.test('should not log incoming request and outgoing response for 404 onBadUrl when disabled', async (t) => { - t.plan(3) + t.test('should not log incoming request, outgoing response and route not found for 404 onBadUrl when disabled', async (t) => { + t.plan(1) const stream = split(JSON.parse) const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) t.teardown(fastify.close.bind(fastify)) @@ -286,12 +280,6 @@ t.test('logging', (t) => { await fastify.inject({ method: 'GET', url: '/%c0' }) - { - const [line] = await once(stream, 'data') - t.ok(line.reqId, 'reqId is defined') - t.equal(line.msg, 'Route GET:/%c0 not found', 'message is set') - } - // no more readable data t.equal(stream.readableLength, 0) }) @@ -403,4 +391,37 @@ t.test('logging', (t) => { if (lines.length === 0) break } }) + + t.test('should not log the error if request logging is disabled', async (t) => { + t.plan(4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + }, + disableRequestLogging: true + }) + t.teardown(fastify.close.bind(fastify)) + + fastify.get('/error', function (req, reply) { + t.ok(req.log) + reply.send(new Error('a generic error')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + { + const [line] = await once(stream, 'data') + t.type(line.msg, 'string') + t.ok(line.msg.startsWith('Server listening at'), 'message is set') + } + + // no more readable data + t.equal(stream.readableLength, 0) + }) }) From 6dbe833cd5f2562ce87a7ce554d60463df6ebed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Thu, 2 May 2024 21:34:43 +0200 Subject: [PATCH 0641/1295] update readme (#5442) --- README.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ee91521284f..a2ac3bc0918 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,8 @@ listed in alphabetical order. , * [__Aras Abbasi__](https://github.com/uzlopak), +* [__Gürgün Dayıoğlu__](https://github.com/gurgunday), + ### Fastify Plugins team * [__Matteo Collina__](https://github.com/mcollina), diff --git a/package.json b/package.json index 0ee6cbba77b..f3d78c2369e 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ }, { "name": "Gürgün Dayıoğlu", - "email": "gurgun.dayioglu@icloud.com", + "email": "hey@gurgun.day", "url": "https://heyhey.to/G" }, { From bf64e474d9db9dde7c3940b9521feeba92660856 Mon Sep 17 00:00:00 2001 From: Arnaud Guilhamat <58270823+keyservlad@users.noreply.github.com> Date: Fri, 3 May 2024 10:49:04 +0200 Subject: [PATCH 0642/1295] feat: add mkcalendar and report methods (#5439) Co-authored-by: aguilhamat Co-authored-by: Carlos Fuentes --- docs/Reference/Routes.md | 3 +- lib/handleRequest.js | 2 +- lib/httpMethods.js | 4 +- test/mkcalendar.test.js | 165 +++++++++++++++++++++++++++++++++++++++ test/report.test.js | 161 ++++++++++++++++++++++++++++++++++++++ types/utils.d.ts | 2 +- 6 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 test/mkcalendar.test.js create mode 100644 test/report.test.js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 081b87dee91..9bd3355cedc 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -34,7 +34,8 @@ fastify.route(options) * `method`: currently it supports `'DELETE'`, `'GET'`, `'HEAD'`, `'PATCH'`, `'POST'`, `'PUT'`, `'OPTIONS'`, `'SEARCH'`, `'TRACE'`, `'PROPFIND'`, - `'PROPPATCH'`, `'MKCOL'`, `'COPY'`, `'MOVE'`, `'LOCK'` and `'UNLOCK'`. + `'PROPPATCH'`, `'MKCOL'`, `'COPY'`, `'MOVE'`, `'LOCK'`, `'UNLOCK'`, + `'REPORT'` and `'MKCALENDAR'`. It could also be an array of methods. * `url`: the path of the URL to match this route (alias: `path`). * `schema`: an object containing the schemas for the request and response. They diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 4e0e763603a..9a1aaad090f 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -28,7 +28,7 @@ function handleRequest (err, request, reply) { const contentType = headers['content-type'] if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH' || - method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK') { + method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK' || method === 'REPORT' || method === 'MKCALENDAR') { if (contentType === undefined) { if ( headers['transfer-encoding'] === undefined && diff --git a/lib/httpMethods.js b/lib/httpMethods.js index f32abbaa099..c030ea5ae68 100644 --- a/lib/httpMethods.js +++ b/lib/httpMethods.js @@ -17,6 +17,8 @@ module.exports = { 'LOCK', 'UNLOCK', 'TRACE', - 'SEARCH' + 'SEARCH', + 'REPORT', + 'MKCALENDAR' ] } diff --git a/test/mkcalendar.test.js b/test/mkcalendar.test.js new file mode 100644 index 00000000000..e5086342c35 --- /dev/null +++ b/test/mkcalendar.test.js @@ -0,0 +1,165 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('../fastify')() + +const bodySample = ` + + + + + + + 0 + + + + CALENDAR_NAME + BEGIN:VCALENDAR + VERSION:2.0 + + + + + ` + +test('can be created - mkcalendar', (t) => { + t.plan(1) + try { + fastify.route({ + method: 'MKCALENDAR', + url: '*', + handler: function (req, reply) { + return reply.code(207).send(` + + + / + + + + + + 2022-04-13T12:35:30Z + Wed, 13 Apr 2022 12:35:30 GMT + "e0-5dc8869b53ef1" + + + + + + + + + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + + `) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, (err) => { + t.error(err) + t.teardown(() => { + fastify.close() + }) + + test('request - mkcalendar', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/`, + method: 'MKCALENDAR' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) + + test('request with other path - mkcalendar', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/test`, + method: 'MKCALENDAR' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) + + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + test('request with body - mkcalendar', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/test`, + headers: { 'content-type': 'text/plain' }, + body: bodySample, + method: 'MKCALENDAR' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) + + test('request with body and no content type (415 error) - mkcalendar', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/test`, + body: bodySample, + method: 'MKCALENDAR' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) + + test('request without body - mkcalendar', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/test`, + method: 'MKCALENDAR' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) +}) diff --git a/test/report.test.js b/test/report.test.js new file mode 100644 index 00000000000..7d0a890e790 --- /dev/null +++ b/test/report.test.js @@ -0,0 +1,161 @@ +'use strict' + +const t = require('tap') +const test = t.test +const sget = require('simple-get').concat +const fastify = require('../fastify')() + +const bodySample = ` + + + + + + + + + + + + + + ` + +test('can be created - report', (t) => { + t.plan(1) + try { + fastify.route({ + method: 'REPORT', + url: '*', + handler: function (req, reply) { + return reply.code(207).send(` + + + / + + + + + + 2022-04-13T12:35:30Z + Wed, 13 Apr 2022 12:35:30 GMT + "e0-5dc8869b53ef1" + + + + + + + + + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + + `) + } + }) + t.pass() + } catch (e) { + t.fail() + } +}) + +fastify.listen({ port: 0 }, (err) => { + t.error(err) + t.teardown(() => { + fastify.close() + }) + + test('request - report', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/`, + method: 'REPORT' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) + + test('request with other path - report', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/test`, + method: 'REPORT' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) + + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + test('request with body - report', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/test`, + headers: { 'content-type': 'text/plain' }, + body: bodySample, + method: 'REPORT' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) + + test('request with body and no content type (415 error) - report', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/test`, + body: bodySample, + method: 'REPORT' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 415) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) + + test('request without body - report', (t) => { + t.plan(3) + sget( + { + url: `http://localhost:${fastify.server.address().port}/test`, + method: 'REPORT' + }, + (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 207) + t.equal(response.headers['content-length'], '' + body.length) + } + ) + }) +}) diff --git a/types/utils.d.ts b/types/utils.d.ts index 139461f0daa..fe7a84f4286 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -6,7 +6,7 @@ import * as https from 'https' * Standard HTTP method strings */ type _HTTPMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS' | -'PROPFIND' | 'PROPPATCH' | 'MKCOL' | 'COPY' | 'MOVE' | 'LOCK' | 'UNLOCK' | 'TRACE' | 'SEARCH' +'PROPFIND' | 'PROPPATCH' | 'MKCOL' | 'COPY' | 'MOVE' | 'LOCK' | 'UNLOCK' | 'TRACE' | 'SEARCH' | 'REPORT' | 'MKCALENDAR' export type HTTPMethods = Uppercase<_HTTPMethods> | Lowercase<_HTTPMethods> From ef1c2c7fee18389c06d020d5466eeef0421c755f Mon Sep 17 00:00:00 2001 From: Giovanni Bucci Date: Sat, 4 May 2024 14:28:10 +0200 Subject: [PATCH 0643/1295] chore: updated dependencies to latest versions (#5422) * updated all the packages which required minor / patch version bumps * updated: @typescript-eslint/eslint-plugin @typescript-eslint/parser * updated: c8 ajv-formats * updated: markdownlint-cli2 * fully working eslint config * removed some unused deps * removed some unused deps * updated: tsd undici --------- Co-authored-by: Giovanni Bucci --- .markdownlint-cli2.yaml | 2 +- package.json | 64 +++++++++++++++++++---------------------- types/.eslintrc.json | 48 ------------------------------- types/eslint.config.js | 53 ++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 83 deletions(-) delete mode 100644 types/.eslintrc.json create mode 100644 types/eslint.config.js diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml index 5c56986701f..408be45225f 100644 --- a/.markdownlint-cli2.yaml +++ b/.markdownlint-cli2.yaml @@ -7,7 +7,7 @@ config: MD013: line_length: 80 code_block_line_length: 120 - headers: false + headings: false tables: false strict: false stern: false diff --git a/package.json b/package.json index b19f0701e32..9ed0da4e7ad 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "lint:fix": "standard --fix && npm run lint:typescript:fix", "lint:markdown": "markdownlint-cli2", "lint:standard": "standard | snazzy", - "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", + "lint:typescript": "eslint -c types/eslint.config.js types/**/*.d.ts test/types/**/*.test-d.ts", "lint:typescript:fix": "npm run lint:typescript -- --fix", "prepublishOnly": "cross-env PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", "test": "npm run lint && npm run unit && npm run test:typescript", @@ -153,38 +153,33 @@ } ], "devDependencies": { - "@fastify/pre-commit": "^2.0.2", - "@sinclair/typebox": "^0.31.17", - "@sinonjs/fake-timers": "^11.1.0", - "@types/node": "^20.8.4", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", + "@eslint/js": "^9.1.1", + "@sinclair/typebox": "^0.32.22", + "@sinonjs/fake-timers": "^11.2.2", + "@types/node": "^20.12.7", + "@typescript-eslint/eslint-plugin": "^7.7.0", + "@typescript-eslint/parser": "^7.7.0", + "JSONStream": "^1.3.5", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", - "ajv-formats": "^2.1.1", + "ajv-formats": "^3.0.1", "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", - "autocannon": "^7.14.0", + "autocannon": "^7.15.0", "branch-comparer": "^1.1.0", - "c8": "^8.0.1", + "c8": "^9.1.0", "concurrently": "^8.2.2", "cross-env": "^7.0.3", - "eslint": "^8.51.0", - "eslint-config-standard": "^17.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-n": "^16.2.0", - "eslint-plugin-promise": "^6.1.1", + "eslint": "^9.1.0", "fast-json-body": "^1.1.0", "fastify-plugin": "^4.5.1", - "fluent-json-schema": "^4.1.2", + "fluent-json-schema": "^4.2.1", "form-data": "^4.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", - "joi": "^17.11.0", - "json-schema-to-ts": "^2.9.2", - "JSONStream": "^1.3.5", - "markdownlint-cli2": "^0.10.0", + "joi": "^17.12.3", + "json-schema-to-ts": "^3.0.1", + "markdownlint-cli2": "^0.13.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", "send": "^0.18.0", @@ -193,29 +188,30 @@ "split2": "^4.2.0", "standard": "^17.1.0", "tap": "^16.3.9", - "tsd": "^0.29.0", - "typescript": "^5.2.2", - "undici": "^5.26.0", + "tsd": "^0.31.0", + "typescript": "^5.4.5", + "typescript-eslint": "^7.7.0", + "undici": "^6.13.0", "vary": "^1.1.2", - "yup": "^1.3.2" + "yup": "^1.4.0" }, "dependencies": { "@fastify/ajv-compiler": "^3.5.0", - "@fastify/error": "^3.4.0", + "@fastify/error": "^3.4.1", "@fastify/fast-json-stringify-compiler": "^4.3.0", + "@fastify/pre-commit": "^2.1.0", "abstract-logging": "^2.0.1", "avvio": "^8.3.0", - "fast-content-type-parse": "^1.1.0", - "fast-json-stringify": "^5.8.0", - "find-my-way": "^8.0.0", - "light-my-request": "^5.11.0", - "pino": "^8.17.0", + "fast-json-stringify": "^5.14.1", + "find-my-way": "^8.1.0", + "light-my-request": "^5.13.0", + "pino": "^8.20.0", "process-warning": "^3.0.0", "proxy-addr": "^2.0.7", - "rfdc": "^1.3.0", + "rfdc": "^1.3.1", "secure-json-parse": "^2.7.0", - "semver": "^7.5.4", - "toad-cache": "^3.3.0" + "semver": "^7.6.0", + "toad-cache": "^3.7.0" }, "standard": { "ignore": [ diff --git a/types/.eslintrc.json b/types/.eslintrc.json deleted file mode 100644 index e0f08e65932..00000000000 --- a/types/.eslintrc.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "standard" - ], - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "env": { "node": true }, - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module", - "project": "./types/tsconfig.eslint.json", - "createDefaultProgram": true - }, - "rules": { - "no-console": "off", - "@typescript-eslint/indent": ["error", 2], - "semi": ["error", "never"], - "import/export": "off" // this errors on multiple exports (overload interfaces) - }, - "overrides": [ - { - "files": ["*.d.ts","*.test-d.ts"], - "rules": { - "@typescript-eslint/no-explicit-any": "off" - } - }, - { - "files": ["*.test-d.ts"], - "rules": { - "no-unused-vars": "off", - "n/handle-callback-err": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-misused-promises": ["error", { - "checksVoidReturn": false - }] - }, - "globals": { - "NodeJS": "readonly" - } - } - ] -} diff --git a/types/eslint.config.js b/types/eslint.config.js new file mode 100644 index 00000000000..25b37161598 --- /dev/null +++ b/types/eslint.config.js @@ -0,0 +1,53 @@ +const eslint = require('@eslint/js'); +const tsEslint = require('typescript-eslint'); +const typeScriptEsLint = require('@typescript-eslint/parser'); + +module.exports = [ + eslint.configs.recommended, + tsEslint.configs.eslintRecommended, + ...tsEslint.configs.recommended, + { + languageOptions: { + parser: typeScriptEsLint, + parserOptions: { + ecmaVersion: 6, + sourceType: "module", + project: "./tsconfig.eslint.json", + createDefaultProgram: true, + tsconfigRootDir: __dirname, + }, + globals: { + node: true, + } + } + }, + { + files: ['*.ts'], + rules: { + "no-console": "off", + "@typescript-eslint/indent": ["error", 2], + "semi": ["error", "never"], + "import/export": "off" // this errors on multiple exports (overload interfaces) + } + }, + { + "files": ["types/*.d.ts", "types/*.test-d.ts", "test/**/*.d.ts", "test/**/*.test-d.ts"], + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } + }, + { + "files": ["types/*.test-d.ts", "test/**/*.test-d.ts"], + "rules": { + "no-unused-vars": "off", + "n/handle-callback-err": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-misused-promises": ["error", { + "checksVoidReturn": false + }] + }, + } +] From 276f4441878e1de71701a94fb07c7513c16df6e5 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Sat, 4 May 2024 09:02:03 -0400 Subject: [PATCH 0644/1295] chore: revert eslint changes --- package.json | 14 +++++++---- types/.eslintrc.json | 48 ++++++++++++++++++++++++++++++++++++++ types/eslint.config.js | 53 ------------------------------------------ 3 files changed, 57 insertions(+), 58 deletions(-) create mode 100644 types/.eslintrc.json delete mode 100644 types/eslint.config.js diff --git a/package.json b/package.json index d91bd2db3ff..db88b52cb40 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "lint:fix": "standard --fix && npm run lint:typescript:fix", "lint:markdown": "markdownlint-cli2", "lint:standard": "standard | snazzy", - "lint:typescript": "eslint -c types/eslint.config.js types/**/*.d.ts test/types/**/*.test-d.ts", + "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", "lint:typescript:fix": "npm run lint:typescript -- --fix", "prepublishOnly": "cross-env PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", "test": "npm run lint && npm run unit && npm run test:typescript", @@ -154,12 +154,12 @@ ], "devDependencies": { "@eslint/js": "^9.1.1", + "@fastify/pre-commit": "^2.1.0", "@sinclair/typebox": "^0.32.22", "@sinonjs/fake-timers": "^11.2.2", "@types/node": "^20.12.7", "@typescript-eslint/eslint-plugin": "^7.7.0", "@typescript-eslint/parser": "^7.7.0", - "JSONStream": "^1.3.5", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -170,7 +170,12 @@ "c8": "^9.1.0", "concurrently": "^8.2.2", "cross-env": "^7.0.3", - "eslint": "^9.1.0", + "eslint": "^8.57.0", + "eslint-config-standard": "^17.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.1.1", "fast-json-body": "^1.1.0", "fastify-plugin": "^4.5.1", "fluent-json-schema": "^4.2.1", @@ -179,6 +184,7 @@ "http-errors": "^2.0.0", "joi": "^17.12.3", "json-schema-to-ts": "^3.0.1", + "JSONStream": "^1.3.5", "markdownlint-cli2": "^0.13.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", @@ -190,7 +196,6 @@ "tap": "^16.3.9", "tsd": "^0.31.0", "typescript": "^5.4.5", - "typescript-eslint": "^7.7.0", "undici": "^6.13.0", "vary": "^1.1.2", "yup": "^1.4.0" @@ -199,7 +204,6 @@ "@fastify/ajv-compiler": "^3.5.0", "@fastify/error": "^3.4.1", "@fastify/fast-json-stringify-compiler": "^4.3.0", - "@fastify/pre-commit": "^2.1.0", "abstract-logging": "^2.0.1", "avvio": "^8.3.0", "fast-content-type-parse": "^1.1.0", diff --git a/types/.eslintrc.json b/types/.eslintrc.json new file mode 100644 index 00000000000..e0f08e65932 --- /dev/null +++ b/types/.eslintrc.json @@ -0,0 +1,48 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "standard" + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "env": { "node": true }, + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "project": "./types/tsconfig.eslint.json", + "createDefaultProgram": true + }, + "rules": { + "no-console": "off", + "@typescript-eslint/indent": ["error", 2], + "semi": ["error", "never"], + "import/export": "off" // this errors on multiple exports (overload interfaces) + }, + "overrides": [ + { + "files": ["*.d.ts","*.test-d.ts"], + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } + }, + { + "files": ["*.test-d.ts"], + "rules": { + "no-unused-vars": "off", + "n/handle-callback-err": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-misused-promises": ["error", { + "checksVoidReturn": false + }] + }, + "globals": { + "NodeJS": "readonly" + } + } + ] +} diff --git a/types/eslint.config.js b/types/eslint.config.js deleted file mode 100644 index 25b37161598..00000000000 --- a/types/eslint.config.js +++ /dev/null @@ -1,53 +0,0 @@ -const eslint = require('@eslint/js'); -const tsEslint = require('typescript-eslint'); -const typeScriptEsLint = require('@typescript-eslint/parser'); - -module.exports = [ - eslint.configs.recommended, - tsEslint.configs.eslintRecommended, - ...tsEslint.configs.recommended, - { - languageOptions: { - parser: typeScriptEsLint, - parserOptions: { - ecmaVersion: 6, - sourceType: "module", - project: "./tsconfig.eslint.json", - createDefaultProgram: true, - tsconfigRootDir: __dirname, - }, - globals: { - node: true, - } - } - }, - { - files: ['*.ts'], - rules: { - "no-console": "off", - "@typescript-eslint/indent": ["error", 2], - "semi": ["error", "never"], - "import/export": "off" // this errors on multiple exports (overload interfaces) - } - }, - { - "files": ["types/*.d.ts", "types/*.test-d.ts", "test/**/*.d.ts", "test/**/*.test-d.ts"], - "rules": { - "@typescript-eslint/no-explicit-any": "off" - } - }, - { - "files": ["types/*.test-d.ts", "test/**/*.test-d.ts"], - "rules": { - "no-unused-vars": "off", - "n/handle-callback-err": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-misused-promises": ["error", { - "checksVoidReturn": false - }] - }, - } -] From 5210253b4f07f129b385e9cfda59694ca1b75f37 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Sat, 4 May 2024 09:31:48 -0400 Subject: [PATCH 0645/1295] chore: restore #5419 --- fastify.js | 6 +++++ lib/handleRequest.js | 37 +++++++++++++------------------ lib/httpMethods.js | 52 +++++++++++++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/fastify.js b/fastify.js index bc4e1800ee1..6392fdff088 100644 --- a/fastify.js +++ b/fastify.js @@ -280,6 +280,9 @@ function fastify (options) { proppatch: function _proppatch (url, options, handler) { return router.prepareRoute.call(this, { method: 'PROPPATCH', url, options, handler }) }, + mkcalendar: function _mkcalendar (url, options, handler) { + return router.prepareRoute.call(this, { method: 'MKCALENDAR', url, options, handler }) + }, mkcol: function _mkcol (url, options, handler) { return router.prepareRoute.call(this, { method: 'MKCOL', url, options, handler }) }, @@ -298,6 +301,9 @@ function fastify (options) { trace: function _trace (url, options, handler) { return router.prepareRoute.call(this, { method: 'TRACE', url, options, handler }) }, + report: function _mkcalendar (url, options, handler) { + return router.prepareRoute.call(this, { method: 'REPORT', url, options, handler }) + }, search: function _search (url, options, handler) { return router.prepareRoute.call(this, { method: 'SEARCH', url, options, handler }) }, diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 9a1aaad090f..45d766a8c13 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -1,5 +1,6 @@ 'use strict' +const { bodylessMethods, bodyMethods } = require('./httpMethods') const { validate: validateSchema } = require('./validation') const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') const wrapThenable = require('./wrapThenable') @@ -20,45 +21,37 @@ function handleRequest (err, request, reply) { const headers = request.headers const context = request[kRouteContext] - if (method === 'GET' || method === 'HEAD') { + if (bodylessMethods.has(method)) { handler(request, reply) return } - const contentType = headers['content-type'] + if (bodyMethods.has(method)) { + const contentType = headers['content-type'] + const contentLength = headers['content-length'] + const transferEncoding = headers['transfer-encoding'] - if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH' || - method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK' || method === 'REPORT' || method === 'MKCALENDAR') { if (contentType === undefined) { if ( - headers['transfer-encoding'] === undefined && - (headers['content-length'] === '0' || headers['content-length'] === undefined) - ) { // Request has no body to parse + (contentLength === undefined || contentLength === '0') && + transferEncoding === undefined + ) { + // Request has no body to parse handler(request, reply) } else { context.contentTypeParser.run('', handler, request, reply) } } else { + if (contentLength === undefined && transferEncoding === undefined && method === 'OPTIONS') { + // OPTIONS can have a Content-Type header without a body + handler(request, reply) + return + } context.contentTypeParser.run(contentType, handler, request, reply) } return } - if (method === 'OPTIONS' || method === 'DELETE') { - if ( - contentType !== undefined && - ( - headers['transfer-encoding'] !== undefined || - headers['content-length'] !== undefined - ) - ) { - context.contentTypeParser.run(contentType, handler, request, reply) - } else { - handler(request, reply) - } - return - } - // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion handler(request, reply) } diff --git a/lib/httpMethods.js b/lib/httpMethods.js index c030ea5ae68..6147f9c5c79 100644 --- a/lib/httpMethods.js +++ b/lib/httpMethods.js @@ -1,24 +1,40 @@ 'use strict' +const bodylessMethods = new Set([ + // Standard + 'GET', + 'HEAD', + 'TRACE', + + // WebDAV + 'UNLOCK' +]) + +const bodyMethods = new Set([ + // Standard + 'DELETE', + 'OPTIONS', + 'PATCH', + 'PUT', + 'POST', + + // WebDAV + 'COPY', + 'LOCK', + 'MOVE', + 'MKCOL', + 'PROPFIND', + 'PROPPATCH', + 'REPORT', + 'SEARCH', + 'MKCALENDAR' +]) + module.exports = { + bodylessMethods, + bodyMethods, supportedMethods: [ - 'DELETE', - 'GET', - 'HEAD', - 'PATCH', - 'POST', - 'PUT', - 'OPTIONS', - 'PROPFIND', - 'PROPPATCH', - 'MKCOL', - 'COPY', - 'MOVE', - 'LOCK', - 'UNLOCK', - 'TRACE', - 'SEARCH', - 'REPORT', - 'MKCALENDAR' + ...bodylessMethods, + ...bodyMethods ] } From cd8cf868155338b946c77de532b7b6599e344f28 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Sat, 4 May 2024 09:44:04 -0400 Subject: [PATCH 0646/1295] chore: update node version in workflows --- .github/workflows/benchmark-parser.yml | 2 +- .github/workflows/benchmark.yml | 6 +++--- .github/workflows/ci-alternative-runtime.yml | 6 +++--- .github/workflows/ci.yml | 2 +- .../workflows/integration-alternative-runtimes.yml | 6 +++--- .github/workflows/integration.yml | 13 ++++--------- .github/workflows/package-manager-ci.yml | 13 ++++--------- 7 files changed, 19 insertions(+), 29 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index f4e8e182cad..055fa00e902 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -19,7 +19,7 @@ jobs: MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} strategy: matrix: - node-version: [16, 18, 20] + node-version: [18, 20, 22] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index c598f4375e7..d6ceddc2f1a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -19,7 +19,7 @@ jobs: MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} strategy: matrix: - node-version: [16, 18, 20] + node-version: [18, 20, 22] steps: - uses: actions/checkout@v4 with: @@ -90,10 +90,10 @@ jobs: **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} - + remove-label: if: ${{ github.event.label.name == 'benchmark' }} - needs: + needs: - benchmark - output-benchmark runs-on: ubuntu-latest diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index 69f9c261a8b..c24c6cbcbcd 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -26,7 +26,7 @@ jobs: contents: read strategy: matrix: - node-version: [18, 20] + node-version: [18, 20, 22] os: [macos-latest, ubuntu-latest, windows-latest] include: - runtime: nsolid @@ -92,7 +92,7 @@ jobs: persist-credentials: false - uses: nodesource/setup-nsolid@v1 with: - nsolid-version: 5 + nsolid-version: 5 - name: install fastify run: | npm install --ignore-scripts @@ -113,7 +113,7 @@ jobs: if: > github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]' - needs: + needs: - test-typescript - test-unit - package diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20eb5b2e78f..4aef47bf480 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,7 +111,7 @@ jobs: strategy: matrix: - node-version: [16, 18, 20] + node-version: [18, 20, 22] os: [macos-latest, ubuntu-latest, windows-latest] exclude: - os: macos-latest diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index f769150f259..88d2fce8be6 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -20,12 +20,12 @@ jobs: runs-on: ${{ matrix.os }} permissions: contents: read - + strategy: matrix: os: [ubuntu-latest] runtime: [nsolid] - node-version: [18, 20] + node-version: [18, 20, 22] pnpm-version: [8] include: - nsolid-version: 5 @@ -49,7 +49,7 @@ jobs: uses: pnpm/action-setup@v3 with: version: ${{ matrix.pnpm-version }} - + - name: Install Production run: | pnpm install --prod diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 3da1064a41c..d890cb6c127 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -20,23 +20,18 @@ jobs: runs-on: ${{ matrix.os }} permissions: contents: read - + strategy: matrix: - node-version: [16, 18, 20] + node-version: [18, 20, 22] os: [ubuntu-latest] pnpm-version: [8] - # pnpm@8 does not support Node.js 14 so include it separately - include: - - node-version: 14 - os: ubuntu-latest - pnpm-version: 7 steps: - uses: actions/checkout@v4 with: persist-credentials: false - + - name: Use Node.js uses: actions/setup-node@v4 with: @@ -46,7 +41,7 @@ jobs: uses: pnpm/action-setup@v3 with: version: ${{ matrix.pnpm-version }} - + - name: Install Production run: | pnpm install --prod diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index c3f239576bb..22b45d88c16 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -15,14 +15,9 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [16, 18, 20] + node-version: [18, 20, 22] os: [ubuntu-latest] pnpm-version: [8] - # pnpm@8 does not support Node.js 14 so include it separately - include: - - node-version: 14 - os: ubuntu-latest - pnpm-version: 7 steps: - uses: actions/checkout@v4 @@ -45,7 +40,7 @@ jobs: run: | pnpm run test:ci env: - NODE_OPTIONS: no-network-family-autoselection + NODE_OPTIONS: no-network-family-autoselection yarn: runs-on: ${{ matrix.os }} @@ -53,7 +48,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [14, 16, 18, 20] + node-version: [18, 20, 22] os: [ubuntu-latest] steps: @@ -77,4 +72,4 @@ jobs: run: | yarn run test:ci env: - NODE_OPTIONS: no-network-family-autoselection + NODE_OPTIONS: no-network-family-autoselection From 4a9a34466684faa0966d75deb7b3b1595c628f85 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Sat, 4 May 2024 09:49:09 -0400 Subject: [PATCH 0647/1295] chore: alt runtimes do not support 22 yet --- .github/workflows/ci-alternative-runtime.yml | 2 +- .github/workflows/integration-alternative-runtimes.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index c24c6cbcbcd..b894cc49136 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -26,7 +26,7 @@ jobs: contents: read strategy: matrix: - node-version: [18, 20, 22] + node-version: [18, 20] os: [macos-latest, ubuntu-latest, windows-latest] include: - runtime: nsolid diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index 88d2fce8be6..967e4a751da 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -25,7 +25,7 @@ jobs: matrix: os: [ubuntu-latest] runtime: [nsolid] - node-version: [18, 20, 22] + node-version: [18, 20] pnpm-version: [8] include: - nsolid-version: 5 From 9a6cf2703819eec1476fba9f80e3bba2b7a5a41f Mon Sep 17 00:00:00 2001 From: James Sumners Date: Sat, 4 May 2024 10:03:00 -0400 Subject: [PATCH 0648/1295] chore: update benchmark ci --- .github/workflows/benchmark-parser.yml | 16 ++++++++-------- .github/workflows/benchmark.yml | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 055fa00e902..5891b26a048 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -11,12 +11,12 @@ jobs: permissions: contents: read outputs: - PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }} PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} - MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }} + PR-BENCH-22: ${{ steps.benchmark-pr.outputs.BENCH_RESULT22 }} MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} + MAIN-BENCH-22: ${{ steps.benchmark-main.outputs.BENCH_RESULT22 }} strategy: matrix: node-version: [18, 20, 22] @@ -75,12 +75,6 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | - **Node**: 16 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }} - - --- - **Node**: 18 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} @@ -90,3 +84,9 @@ jobs: **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} + + --- + + **Node**: 22 + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-22 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-22 }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d6ceddc2f1a..c574dd74942 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -11,12 +11,12 @@ jobs: permissions: contents: read outputs: - PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }} PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} - MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }} + PR-BENCH-22: ${{ steps.benchmark-pr.outputs.BENCH_RESULT22 }} MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} + MAIN-BENCH-22: ${{ steps.benchmark-main.outputs.BENCH_RESULT22 }} strategy: matrix: node-version: [18, 20, 22] @@ -75,12 +75,6 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | - **Node**: 16 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }} - - --- - **Node**: 18 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} @@ -90,6 +84,12 @@ jobs: **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} + + --- + + **Node**: 22 + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-22 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-22 }} remove-label: if: ${{ github.event.label.name == 'benchmark' }} From b8cbd3383cfebc0d7997cd337d85630f08d934d8 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 5 May 2024 12:18:35 +0200 Subject: [PATCH 0649/1295] feat: handle synchronous errors in errorHandler (#5445) --- lib/error-handler.js | 16 ++++++++++------ lib/wrapThenable.js | 2 ++ test/500s.test.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/lib/error-handler.js b/lib/error-handler.js index 0b9dbdbb996..cde82f8baef 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -65,13 +65,17 @@ function handleError (reply, error, cb) { return } - const result = func(error, reply.request, reply) - if (result !== undefined) { - if (result !== null && typeof result.then === 'function') { - wrapThenable(result, reply) - } else { - reply.send(result) + try { + const result = func(error, reply.request, reply) + if (result !== undefined) { + if (result !== null && typeof result.then === 'function') { + wrapThenable(result, reply) + } else { + reply.send(result) + } } + } catch (err) { + reply.send(err) } } diff --git a/lib/wrapThenable.js b/lib/wrapThenable.js index b746a62e920..abb75bdd03a 100644 --- a/lib/wrapThenable.js +++ b/lib/wrapThenable.js @@ -39,6 +39,8 @@ function wrapThenable (thenable, reply) { // try-catch allow to re-throw error in error handler for async handler try { reply.send(err) + // The following should not happen + /* c8 ignore next 3 */ } catch (err) { reply.send(err) } diff --git a/test/500s.test.js b/test/500s.test.js index 3422fcfec4c..3c915dba0a2 100644 --- a/test/500s.test.js +++ b/test/500s.test.js @@ -188,3 +188,35 @@ test('cannot set childLoggerFactory after binding', t => { } }) }) + +test('catch synchronous errors', t => { + t.plan(3) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.setErrorHandler((_, req, reply) => { + throw new Error('kaboom2') + }) + + fastify.post('/', function (req, reply) { + reply.send(new Error('kaboom')) + }) + + fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'Content-Type': 'application/json' + }, + payload: JSON.stringify({ hello: 'world' }).substring(0, 5) + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 500) + t.same(res.json(), { + error: 'Internal Server Error', + message: 'kaboom2', + statusCode: 500 + }) + }) +}) From 445e41a5f0893f999ad3c61ff78e6d2d222801d8 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 7 May 2024 13:57:47 +0100 Subject: [PATCH 0650/1295] types: request route schema might be undefined (#5394) * types: request route schema might be undefined * Update types/request.d.ts Co-authored-by: Manuel Spigolon Signed-off-by: Nico Flaig * Comment why it might be undefined / empty Signed-off-by: Nico Flaig --------- Signed-off-by: Nico Flaig Co-authored-by: Manuel Spigolon --- test/types/request.test-d.ts | 4 ++-- types/request.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 5d81eb14c00..53fd547b179 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -79,8 +79,8 @@ const getHandler: RouteHandler = function (request, _reply) { expectType['config']>(request.routeConfig) expectType['config']>(request.routeOptions.config) expectType(request.routeOptions.config) - expectType(request.routeSchema) - expectType(request.routeOptions.schema) + expectType(request.routeSchema) + expectType(request.routeOptions.schema) expectType(request.routeOptions.handler) expectType(request.routeOptions.url) diff --git a/types/request.d.ts b/types/request.d.ts index 59630ba378d..700cf9b2966 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -31,7 +31,7 @@ export interface RequestRouteOptions; routeConfig: FastifyRequestContext['config']; - routeSchema: FastifySchema + routeSchema?: FastifySchema; // it is empty for 404 requests /** in order for this to be used the user should ensure they have set the attachValidation option. */ validationError?: Error & { validation: any; validationContext: string }; From fe25981df1cd9d4fa686ae8227e4c1b3ce804cfd Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 7 May 2024 15:00:35 +0200 Subject: [PATCH 0651/1295] Bumped v4.27.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index ee2be1b2545..504443dd6b2 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '4.26.2' +const VERSION = '4.27.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index f3d78c2369e..0e7d50cd489 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "4.26.2", + "version": "4.27.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From c373df792a891e98569aec7bcb578ee50e13d134 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 7 May 2024 15:09:25 +0200 Subject: [PATCH 0652/1295] Updated README Signed-off-by: Matteo Collina --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a2ac3bc0918..a3af4c4bc55 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,8 @@ developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town. -The `main` branch refers to the Fastify `v4` release. Check out the -[`v3.x` branch](https://github.com/fastify/fastify/tree/3.x) for `v3`. - - +The `main` branch refers to the Fastify `v5` release, which is not released/LTS yet. +Check out the [`4.x` branch](https://github.com/fastify/fastify/tree/4.x) for `v4`. ### Table of Contents From 7f26c0ecd224f77b1f7c0069b2fc2c13dd943b84 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 7 May 2024 15:13:40 +0200 Subject: [PATCH 0653/1295] Bumped v5.0.0-alpha.1 Signed-off-by: Matteo Collina --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db88b52cb40..ae6f466fc29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.0.0-dev", + "version": "5.0.0-aplha.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 2839863d09d58c43a1d23f7c51e9d4515a33ab74 Mon Sep 17 00:00:00 2001 From: James Kaguru Kihuha <47499826+James-Kaguru@users.noreply.github.com> Date: Mon, 6 May 2024 22:45:55 +0300 Subject: [PATCH 0654/1295] updated the docs to match v3 requirements --- docs/Reference/TypeScript.md | 58 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 1caf8273249..2aeec92dcab 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -831,7 +831,7 @@ Constraints: `string | Buffer` #### Fastify -##### fastify<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(opts?: [FastifyServerOptions][FastifyServerOptions]): [FastifyInstance][FastifyInstance] +##### fastify< [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(opts?: [FastifyServerOptions][FastifyServerOptions]): [FastifyInstance][FastifyInstance] [src](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L19) The main Fastify API method. By default creates an HTTP server. Utilizing @@ -986,7 +986,7 @@ Type alias for `http.Server` --- -##### fastify.FastifyServerOptions<[RawServer][RawServerGeneric], [Logger][LoggerGeneric]> +##### fastify.FastifyServerOptions< [RawServer][RawServerGeneric], [Logger][LoggerGeneric]> [src](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L29) @@ -997,7 +997,7 @@ generic parameters are passed down through that method. See the main [fastify][Fastify] method type definition section for examples on instantiating a Fastify server with TypeScript. -##### fastify.FastifyInstance<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RequestGeneric][FastifyRequestGenericInterface], [Logger][LoggerGeneric]> +##### fastify.FastifyInstance< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RequestGeneric][FastifyRequestGenericInterface], [Logger][LoggerGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/instance.d.ts#L16) @@ -1020,7 +1020,7 @@ details on this interface. #### Request -##### fastify.FastifyRequest<[RequestGeneric][FastifyRequestGenericInterface], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> +##### fastify.FastifyRequest< [RequestGeneric][FastifyRequestGenericInterface], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/request.d.ts#L15) This interface contains properties of Fastify request object. The properties @@ -1120,7 +1120,7 @@ RawRequestDefaultExpression // -> http2.Http2ServerRequest #### Reply -##### fastify.FastifyReply<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> +##### fastify.FastifyReply< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/reply.d.ts#L32) This interface contains the custom properties that Fastify adds to the standard @@ -1156,7 +1156,7 @@ declare module 'fastify' { } ``` -##### fastify.RawReplyDefaultExpression<[RawServer][RawServerGeneric]> +##### fastify.RawReplyDefaultExpression< [RawServer][RawServerGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L27) Dependent on `@types/node` modules `http`, `https`, `http2` @@ -1188,19 +1188,19 @@ When creating plugins for Fastify, it is recommended to use the `fastify-plugin` module. Additionally, there is a guide to creating plugins with TypeScript and Fastify available in the Learn by Example, [Plugins](#plugins) section. -##### fastify.FastifyPluginCallback<[Options][FastifyPluginOptions]> +##### fastify.FastifyPluginCallback< [Options][FastifyPluginOptions]> [src](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L9) Interface method definition used within the [`fastify.register()`][FastifyRegister] method. -##### fastify.FastifyPluginAsync<[Options][FastifyPluginOptions]> +##### fastify.FastifyPluginAsync< [Options][FastifyPluginOptions]> [src](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L20) Interface method definition used within the [`fastify.register()`][FastifyRegister] method. -##### fastify.FastifyPlugin<[Options][FastifyPluginOptions]> +##### fastify.FastifyPlugin< [Options][FastifyPluginOptions]> [src](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L29) Interface method definition used within the @@ -1269,7 +1269,7 @@ a function that returns the previously described intersection. Check out the [Specifying Logger Types](#example-5-specifying-logger-types) example for more details on specifying a custom logger. -##### fastify.FastifyLoggerOptions<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric]> +##### fastify.FastifyLoggerOptions< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/logger.d.ts#L17) @@ -1332,7 +1332,7 @@ One of the core principles in Fastify is its routing capabilities. Most of the types defined in this section are used under-the-hood by the Fastify instance `.route` and `.get/.post/.etc` methods. -##### fastify.RouteHandlerMethod<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> +##### fastify.RouteHandlerMethod< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#L105) @@ -1342,7 +1342,7 @@ The generics parameters are passed through to these arguments. The method returns either `void` or `Promise` for synchronous and asynchronous handlers respectively. -##### fastify.RouteOptions<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> +##### fastify.RouteOptions< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#L78) @@ -1354,14 +1354,14 @@ required properties: 3. `handler` the route handler method, see [RouteHandlerMethod][] for more details -##### fastify.RouteShorthandMethod<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric]> +##### fastify.RouteShorthandMethod< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#12) An overloaded function interface for three kinds of shorthand route methods to be used in conjunction with the `.get/.post/.etc` methods. -##### fastify.RouteShorthandOptions<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> +##### fastify.RouteShorthandOptions< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#55) @@ -1369,7 +1369,7 @@ An interface that covers all of the base options for a route. Each property on this interface is optional, and it serves as the base for the RouteOptions and RouteShorthandOptionsWithHandler interfaces. -##### fastify.RouteShorthandOptionsWithHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> +##### fastify.RouteShorthandOptionsWithHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#93) @@ -1384,21 +1384,21 @@ interface `handler` which is of type RouteHandlerMethod A generic type that is either a `string` or `Buffer` -##### fastify.FastifyBodyParser<[RawBody][RawBodyGeneric], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> +##### fastify.FastifyBodyParser< [RawBody][RawBodyGeneric], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/content-type-parser.d.ts#L7) A function type definition for specifying a body parser method. Use the `RawBody` generic to specify the type of the body being parsed. -##### fastify.FastifyContentTypeParser<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> +##### fastify.FastifyContentTypeParser< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/content-type-parser.d.ts#L17) A function type definition for specifying a body parser method. Content is typed via the `RawRequest` generic. -##### fastify.AddContentTypeParser<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> +##### fastify.AddContentTypeParser< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/content-type-parser.d.ts#L46) @@ -1440,7 +1440,7 @@ This interface is passed to instance of FastifyError. #### Hooks -##### fastify.onRequestHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void +##### fastify.onRequestHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L17) @@ -1450,7 +1450,7 @@ no previous hook, the next hook will be `preParsing`. Notice: in the `onRequest` hook, request.body will always be null, because the body parsing happens before the `preHandler` hook. -##### fastify.preParsingHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void +##### fastify.preParsingHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L35) @@ -1465,21 +1465,21 @@ stream. This property is used to correctly match the request payload with the `Content-Length` header value. Ideally, this property should be updated on each received chunk. -##### fastify.preValidationHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void +##### fastify.preValidationHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L53) `preValidation` is the third hook to be executed in the request lifecycle. The previous hook was `preParsing`, the next hook will be `preHandler`. -##### fastify.preHandlerHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void +##### fastify.preHandlerHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L70) `preHandler` is the fourth hook to be executed in the request lifecycle. The previous hook was `preValidation`, the next hook will be `preSerialization`. -##### fastify.preSerializationHookHandler(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: PreSerializationPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\ | void +##### fastify.preSerializationHookHandler< PreSerializationPayload, [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: PreSerializationPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L94) @@ -1489,7 +1489,7 @@ The previous hook was `preHandler`, the next hook will be `onSend`. Note: the hook is NOT called if the payload is a string, a Buffer, a stream or null. -##### fastify.onSendHookHandler(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: OnSendPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\ | void +##### fastify.onSendHookHandler< OnSendPayload, [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: OnSendPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L114) @@ -1500,7 +1500,7 @@ next hook will be `onResponse`. Note: If you change the payload, you may only change it to a string, a Buffer, a stream, or null. -##### fastify.onResponseHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void +##### fastify.onResponseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L134) @@ -1511,7 +1511,7 @@ The onResponse hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example to gather statistics. -##### fastify.onErrorHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], error: [FastifyError][FastifyError], done: () => void): Promise\ | void +##### fastify.onErrorHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], error: [FastifyError][FastifyError], done: () => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L154) @@ -1528,7 +1528,7 @@ the default customErrorHandler always sends the error back to the user). Notice: unlike the other hooks, pass an error to the done function is not supported. -##### fastify.onRouteHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(opts: [RouteOptions][RouteOptions] & { path: string; prefix: string }): Promise\ | void +##### fastify.onRouteHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(opts: [RouteOptions][RouteOptions] & \{ path: string; prefix: string }): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L174) @@ -1536,7 +1536,7 @@ Triggered when a new route is registered. Listeners are passed a routeOptions object as the sole parameter. The interface is synchronous, and, as such, the listener does not get passed a callback -##### fastify.onRegisterHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void +##### fastify.onRegisterHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L191) @@ -1548,7 +1548,7 @@ plugin context is formed, and you want to operate in that specific context. Note: This hook will not be called if a plugin is wrapped inside fastify-plugin. -##### fastify.onCloseHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void +##### fastify.onCloseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void [src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L206) From 0bf950647d8fcaa3448ea5e712bca422b64f3f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Thu, 9 May 2024 18:53:55 +0200 Subject: [PATCH 0655/1295] chore: remove dep (#5454) --- fastify.js | 2 +- package.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index 6392fdff088..6164ad0fff1 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.0.0-dev' +const VERSION = '5.0.0-alpha.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index ae6f466fc29..8603f4a0bdb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.0.0-aplha.1", + "version": "5.0.0-alpha.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", @@ -206,7 +206,6 @@ "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", "avvio": "^8.3.0", - "fast-content-type-parse": "^1.1.0", "fast-json-stringify": "^5.14.1", "find-my-way": "^8.1.0", "light-my-request": "^5.13.0", From 8817f529b6339310abd0f4be5c5b7b12315efe23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Thu, 9 May 2024 19:03:46 +0200 Subject: [PATCH 0656/1295] add shorthand types (#5455) --- test/types/route.test-d.ts | 4 ++-- types/instance.d.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 50a42e55747..091e2b1f3c3 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -38,10 +38,10 @@ const routeHandlerWithReturnValue: RouteHandlerMethod = function (request, reply type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' | 'options' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' | -'unlock' | 'trace' | 'search' +'unlock' | 'trace' | 'search' | 'mkcalendar' | 'report' ;['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'PROPFIND', - 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', 'TRACE', 'SEARCH' + 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', 'TRACE', 'SEARCH', 'MKCALENDAR', 'REPORT' ].forEach(method => { // route method expectType(fastify().route({ diff --git a/types/instance.d.ts b/types/instance.d.ts index 41cd0395ebd..68d66a84f55 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -189,12 +189,14 @@ export interface FastifyInstance< options: RouteShorthandMethod; propfind: RouteShorthandMethod; proppatch: RouteShorthandMethod; + mkcalendar: RouteShorthandMethod; mkcol: RouteShorthandMethod; copy: RouteShorthandMethod; move: RouteShorthandMethod; lock: RouteShorthandMethod; unlock: RouteShorthandMethod; trace: RouteShorthandMethod; + report: RouteShorthandMethod; search: RouteShorthandMethod; all: RouteShorthandMethod; From d7410bd18e903b46b5d6cf37b7cacbdb60d75237 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 13 May 2024 09:25:44 +0200 Subject: [PATCH 0657/1295] fix: ci labeler (#5463) --- .github/labeler.yml | 9 ++++++--- .github/workflows/labeler.yml | 9 +++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 5c8239485a8..701f3ba18ee 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,13 +1,16 @@ # PRs that only touch the docs folder documentation: -- all: ["docs/**/*"] +- changed-files: + - any-glob-to-any-file: docs/** "github actions": -- all: [".github/workflows/*"] +- changed-files: + - any-glob-to-any-file: ".github/workflows/*" # PRs that only touch type files typescript: -- all: ["**/*[.|-]d.ts"] +- changed-files: + - any-glob-to-any-file: "**/*[.|-]d.ts" plugin: - all: ["docs/Guides/Ecosystem.md"] diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 93c98bee0c0..d310ecf26bc 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,14 +1,11 @@ name: "Pull Request Labeler" on: pull_request_target -permissions: - contents: read - pull-requests: write - jobs: label: + permissions: + contents: read + pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" From f2cb21cbc8573da2d54096d414b5362d9a323fbc Mon Sep 17 00:00:00 2001 From: Monish Date: Mon, 13 May 2024 19:03:39 +0530 Subject: [PATCH 0658/1295] docs(guides/database): correct typo (#5461) * Fixed a typo in documentation in Database.md From: `Each file an migrations folder need` To: `Each file in migrations folder need` Signed-off-by: Monish * Update docs/Guides/Database.md Co-authored-by: Frazer Smith Signed-off-by: Monish --------- Signed-off-by: Monish Co-authored-by: Frazer Smith --- docs/Guides/Database.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md index 8bee1119046..3369b33498c 100644 --- a/docs/Guides/Database.md +++ b/docs/Guides/Database.md @@ -245,7 +245,7 @@ for Postgres, MySQL, SQL Server and SQLite. For MongoDB migrations, please check #### [Postgrator](https://www.npmjs.com/package/postgrator) Postgrator is Node.js SQL migration tool that uses a directory of SQL scripts to -alter the database schema. Each file an migrations folder need to follow the +alter the database schema. Each file in a migrations folder need to follow the pattern: ` [version].[action].[optional-description].sql`. **version:** must be an incrementing number (e.g. `001` or a timestamp). From 47f23752d1563624ea3c47efde8570793dc98b55 Mon Sep 17 00:00:00 2001 From: Adam Jones Date: Tue, 14 May 2024 10:21:16 +0100 Subject: [PATCH 0659/1295] test: add tests for error handling (#5451) * Add tests for error handling * Fix t.plan counts * Fix t.plan counts --- test/encapsulated-error-handler.test.js | 215 ++++++++++++++++++++++-- 1 file changed, 201 insertions(+), 14 deletions(-) diff --git a/test/encapsulated-error-handler.test.js b/test/encapsulated-error-handler.test.js index bfd8b7d4e70..88927324af9 100644 --- a/test/encapsulated-error-handler.test.js +++ b/test/encapsulated-error-handler.test.js @@ -3,25 +3,66 @@ const { test } = require('tap') const Fastify = require('..') -test('encapsulates an error handler', async t => { +// Because of how error handlers wrap things, following the control flow can be tricky +// In this test file numbered comments indicate the order statements are expected to execute + +test('encapsulates an asynchronous error handler', async t => { t.plan(3) const fastify = Fastify() fastify.register(async function (fastify) { fastify.setErrorHandler(async function a (err) { - t.equal(err.message, 'kaboom') - throw new Error('caught') + // 3. the inner error handler catches the error, and throws a new error + t.equal(err.message, 'from_endpoint') + throw new Error('from_inner') + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_endpoint') }) - fastify.get('/encapsulated', async () => { throw new Error('kaboom') }) }) fastify.setErrorHandler(async function b (err) { - t.equal(err.message, 'caught') - throw new Error('wrapped') + // 4. the outer error handler catches the error thrown by the inner error handler + t.equal(err.message, 'from_inner') + // 5. the outer error handler throws a new error + throw new Error('from_outer') }) + // 1. the endpoint is called const res = await fastify.inject('/encapsulated') - t.equal(res.json().message, 'wrapped') + // 6. the default error handler returns the error from the outer error handler + t.equal(res.json().message, 'from_outer') +}) + +// See discussion in https://github.com/fastify/fastify/pull/5222#discussion_r1432573655 +test('encapsulates a synchronous error handler', async t => { + t.plan(3) + + const fastify = Fastify() + fastify.register(async function (fastify) { + fastify.setErrorHandler(function a (err) { + // 3. the inner error handler catches the error, and throws a new error + t.equal(err.message, 'from_endpoint') + throw new Error('from_inner') + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_endpoint') + }) + }) + + fastify.setErrorHandler(async function b (err) { + // 4. the outer error handler catches the error thrown by the inner error handler + t.equal(err.message, 'from_inner') + // 5. the outer error handler throws a new error + throw new Error('from_outer') + }) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // 6. the default error handler returns the error from the outer error handler + t.equal(res.json().message, 'from_outer') }) test('onError hook nested', async t => { @@ -30,21 +71,167 @@ test('onError hook nested', async t => { const fastify = Fastify() fastify.register(async function (fastify) { fastify.setErrorHandler(async function a (err) { - t.equal(err.message, 'kaboom') - throw new Error('caught') + // 4. the inner error handler catches the error, and throws a new error + t.equal(err.message, 'from_endpoint') + throw new Error('from_inner') + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_endpoint') }) - fastify.get('/encapsulated', async () => { throw new Error('kaboom') }) }) fastify.setErrorHandler(async function b (err) { - t.equal(err.message, 'caught') - throw new Error('wrapped') + // 5. the outer error handler catches the error thrown by the inner error handler + t.equal(err.message, 'from_inner') + // 6. the outer error handler throws a new error + throw new Error('from_outer') }) fastify.addHook('onError', async function (request, reply, err) { - t.equal(err.message, 'kaboom') + // 3. the hook receives the error + t.equal(err.message, 'from_endpoint') + }) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // 7. the default error handler returns the error from the outer error handler + t.equal(res.json().message, 'from_outer') +}) + +// See https://github.com/fastify/fastify/issues/5220 +test('encapuslates an error handler, for errors thrown in hooks', async t => { + t.plan(3) + + const fastify = Fastify() + fastify.register(async function (fastify) { + fastify.setErrorHandler(function a (err) { + // 3. the inner error handler catches the error, and throws a new error + t.equal(err.message, 'from_hook') + throw new Error('from_inner') + }) + fastify.addHook('onRequest', async () => { + // 2. the hook throws an error + throw new Error('from_hook') + }) + fastify.get('/encapsulated', async () => {}) + }) + + fastify.setErrorHandler(function b (err) { + // 4. the outer error handler catches the error thrown by the inner error handler + t.equal(err.message, 'from_inner') + // 5. the outer error handler throws a new error + throw new Error('from_outer') }) + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // 6. the default error handler returns the error from the outer error handler + t.equal(res.json().message, 'from_outer') +}) + +// See https://github.com/fastify/fastify/issues/5220 +test('encapuslates many synchronous error handlers that rethrow errors', async t => { + const DEPTH = 100 + t.plan(DEPTH + 2) + + /** + * This creates a very nested set of error handlers, that looks like: + * plugin + * - error handler + * - plugin + * - error handler + * - plugin + * ... {to DEPTH levels} + * - plugin + * - error handler + * - GET /encapsulated + */ + const createNestedRoutes = (fastify, depth) => { + if (depth < 0) { + throw new Error('Expected depth >= 0') + } else if (depth === 0) { + fastify.setErrorHandler(function a (err) { + // 3. innermost error handler catches the error, and throws a new error + t.equal(err.message, 'from_route') + throw new Error(`from_handler_${depth}`) + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_route') + }) + } else { + fastify.setErrorHandler(function d (err) { + // 4 to {DEPTH+4}. error handlers each catch errors, and then throws a new error + t.equal(err.message, `from_handler_${depth - 1}`) + throw new Error(`from_handler_${depth}`) + }) + + fastify.register(async function (fastify) { + createNestedRoutes(fastify, depth - 1) + }) + } + } + + const fastify = Fastify() + createNestedRoutes(fastify, DEPTH) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // {DEPTH+5}. the default error handler returns the error from the outermost error handler + t.equal(res.json().message, `from_handler_${DEPTH}`) +}) + +// See https://github.com/fastify/fastify/issues/5220 +// This was not failing previously, but we want to make sure the behavior continues to work in the same way across async and sync handlers +// Plus, the current setup is somewhat fragile to tweaks to wrapThenable as that's what retries (by calling res.send(err) again) +test('encapuslates many asynchronous error handlers that rethrow errors', async t => { + const DEPTH = 100 + t.plan(DEPTH + 2) + + /** + * This creates a very nested set of error handlers, that looks like: + * plugin + * - error handler + * - plugin + * - error handler + * - plugin + * ... {to DEPTH levels} + * - plugin + * - error handler + * - GET /encapsulated + */ + const createNestedRoutes = (fastify, depth) => { + if (depth < 0) { + throw new Error('Expected depth >= 0') + } else if (depth === 0) { + fastify.setErrorHandler(async function a (err) { + // 3. innermost error handler catches the error, and throws a new error + t.equal(err.message, 'from_route') + throw new Error(`from_handler_${depth}`) + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_route') + }) + } else { + fastify.setErrorHandler(async function m (err) { + // 4 to {DEPTH+4}. error handlers each catch errors, and then throws a new error + t.equal(err.message, `from_handler_${depth - 1}`) + throw new Error(`from_handler_${depth}`) + }) + + fastify.register(async function (fastify) { + createNestedRoutes(fastify, depth - 1) + }) + } + } + + const fastify = Fastify() + createNestedRoutes(fastify, DEPTH) + + // 1. the endpoint is called const res = await fastify.inject('/encapsulated') - t.equal(res.json().message, 'wrapped') + // {DEPTH+5}. the default error handler returns the error from the outermost error handler + t.equal(res.json().message, `from_handler_${DEPTH}`) }) From 102b40e62864400265909a1f5d3a092812d47b90 Mon Sep 17 00:00:00 2001 From: Michael Di Prisco Date: Wed, 15 May 2024 15:33:31 +0200 Subject: [PATCH 0660/1295] docs(reference/routes): fix example on constraints (#5468) Signed-off-by: Michael Di Prisco --- docs/Reference/Routes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index adc82ae441f..a276dd8bc4d 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -768,7 +768,7 @@ matching wildcard subdomains (or any other pattern): fastify.route({ method: 'GET', url: '/', - constraints: { host: /.*\.fastify\.io/ }, // will match any subdomain of fastify.dev + constraints: { host: /.*\.fastify\.dev/ }, // will match any subdomain of fastify.dev handler: function (request, reply) { reply.send('hello world from ' + request.headers.host) } From 6712b6272ff4a38845aa3aed94fc21703c940f51 Mon Sep 17 00:00:00 2001 From: Susan <25956407+dmkng@users.noreply.github.com> Date: Fri, 10 May 2024 12:16:32 +0200 Subject: [PATCH 0661/1295] Optimize resolving X-Forwarded-For addresses Signed-off-by: Susan <25956407+dmkng@users.noreply.github.com> --- lib/request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index 2645b35154c..7a535fd841d 100644 --- a/lib/request.js +++ b/lib/request.js @@ -50,7 +50,7 @@ function getTrustProxyFn (tp) { } if (tp === true) { // Support plain true/false - return function () { return true } + return undefined } if (typeof tp === 'number') { // Support trusting hop count From 0419cc2f746a0f867e66685b3433a77d46a3fa8c Mon Sep 17 00:00:00 2001 From: Susan <25956407+dmkng@users.noreply.github.com> Date: Fri, 10 May 2024 16:25:51 +0200 Subject: [PATCH 0662/1295] Replaced proxyAddr with proxyAddr.all Signed-off-by: Susan <25956407+dmkng@users.noreply.github.com> --- lib/request.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index 7a535fd841d..f86d45cb9ea 100644 --- a/lib/request.js +++ b/lib/request.js @@ -115,7 +115,8 @@ function buildRequestWithTrustProxy (R, trustProxy) { Object.defineProperties(_Request.prototype, { ip: { get () { - return proxyAddr(this.raw, proxyFn) + const addrs = proxyAddr.all(this.raw, proxyFn) + return addrs[addrs.length - 1] } }, ips: { From a2406e3cda63c529a1de1fa4ebe9d1867e1fbc50 Mon Sep 17 00:00:00 2001 From: Susan <25956407+dmkng@users.noreply.github.com> Date: Mon, 13 May 2024 00:11:17 +0200 Subject: [PATCH 0663/1295] Made argument absence look cleaner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gürgün Dayıoğlu Signed-off-by: Susan <25956407+dmkng@users.noreply.github.com> --- lib/request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index f86d45cb9ea..523ffd3af26 100644 --- a/lib/request.js +++ b/lib/request.js @@ -50,7 +50,7 @@ function getTrustProxyFn (tp) { } if (tp === true) { // Support plain true/false - return undefined + return null } if (typeof tp === 'number') { // Support trusting hop count From 8cc2045065123fdb5fd5ec1dfd9f90f409f066c6 Mon Sep 17 00:00:00 2001 From: Susan <25956407+dmkng@users.noreply.github.com> Date: Tue, 14 May 2024 12:17:51 +0200 Subject: [PATCH 0664/1295] Changed the comment to be more accurate Signed-off-by: Susan <25956407+dmkng@users.noreply.github.com> --- lib/request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index 523ffd3af26..5a10521baab 100644 --- a/lib/request.js +++ b/lib/request.js @@ -49,7 +49,7 @@ function getTrustProxyFn (tp) { return tp } if (tp === true) { - // Support plain true/false + // Support trusting everything return null } if (typeof tp === 'number') { From f56b7c74c536feb27fa0ee55eeb8ebd0bb9d1a6f Mon Sep 17 00:00:00 2001 From: Brian Valente Date: Mon, 11 Mar 2024 16:06:15 -0300 Subject: [PATCH 0665/1295] Fix `config` type in RouteShorthandOptions --- types/route.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/route.d.ts b/types/route.d.ts index 70c99b0fe35..f2f1dadcb7e 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -1,6 +1,6 @@ import { FastifyError } from '@fastify/error' import { ConstraintStrategy } from 'find-my-way' -import { FastifyRequestContext } from './context' +import { FastifyContextConfig } from './context' import { onErrorMetaHookHandler, onRequestAbortMetaHookHandler, onRequestMetaHookHandler, onResponseMetaHookHandler, onSendMetaHookHandler, onTimeoutMetaHookHandler, preHandlerMetaHookHandler, preParsingMetaHookHandler, preSerializationMetaHookHandler, preValidationMetaHookHandler } from './hooks' import { FastifyInstance } from './instance' import { FastifyBaseLogger, FastifyChildLoggerFactory, LogLevel } from './logger' @@ -52,7 +52,7 @@ export interface RouteShorthandOptions< serializerCompiler?: FastifySerializerCompiler; bodyLimit?: number; logLevel?: LogLevel; - config?: Omit['config'], 'url' | 'method'>; + config?: FastifyContextConfig & ContextConfig; version?: string; constraints?: RouteConstraint, prefixTrailingSlash?: 'slash'|'no-slash'|'both'; From 3ff1c54bbcd9eb8cd7243901dd83d5d751c59088 Mon Sep 17 00:00:00 2001 From: Brian Valente Date: Tue, 14 May 2024 22:45:58 -0300 Subject: [PATCH 0666/1295] Add test for `config` type in RouteShorthandOptions --- test/types/route.test-d.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 091e2b1f3c3..0f4391a2bd1 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -19,6 +19,13 @@ declare module '../../fastify' { interface FastifyContextConfig { foo: string; bar: number; + includeMessage?: boolean; + } + + interface FastifyRequest { + message: ContextConfig extends { includeMessage: true } + ? string + : null; } } @@ -36,6 +43,22 @@ const routeHandlerWithReturnValue: RouteHandlerMethod = function (request, reply return reply.send() } +fastify().get( + '/', + { config: { foo: 'bar', bar: 100, includeMessage: true } }, + (req) => { + expectType(req.message) + } +) + +fastify().get( + '/', + { config: { foo: 'bar', bar: 100, includeMessage: false } }, + (req) => { + expectType(req.message) + } +) + type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' | 'options' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' | 'unlock' | 'trace' | 'search' | 'mkcalendar' | 'report' From 30c95ed4c8d5889923b6779b665cb18c6fe55d01 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 20 May 2024 08:57:41 +0200 Subject: [PATCH 0667/1295] feat: request and reply decorators can not be a reference type (#5462) --- docs/Reference/Decorators.md | 54 +++++++++--- docs/Reference/Errors.md | 2 + docs/Reference/Warnings.md | 2 - lib/decorate.js | 5 +- lib/errors.js | 4 + lib/warnings.js | 7 -- test/decorator-namespace.test._js_ | 31 +++++++ test/decorator.test.js | 133 ++++++++++++++++++++--------- test/internals/errors.test.js | 24 ++++-- types/errors.d.ts | 1 + 10 files changed, 189 insertions(+), 74 deletions(-) create mode 100644 test/decorator-namespace.test._js_ diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 14af30a7aaa..4465ad5e9ef 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -59,8 +59,8 @@ close as possible to the value intended to be set dynamically in the future. Initialize a decorator as a `''` if the intended value is a string, and as `null` if it will be an object or a function. -Remember this example works only with value types as reference types will be -shared amongst all requests. See [decorateRequest](#decorate-request). +Remember this example works only with value types as reference types will +thrown and error during the fastify startup. See [decorateRequest](#decorate-request). See [JavaScript engine fundamentals: Shapes and Inline Caches](https://mathiasbynens.be/notes/shapes-ics) for more information on this @@ -83,7 +83,7 @@ fastify.decorate('utility', function () { }) ``` -As mentioned above, non-function values can be attached: +As mentioned above, non-function values can be attached to the server instance as: ```js fastify.decorate('conf', { @@ -177,17 +177,18 @@ fastify.decorateReply('utility', function () { Note: using an arrow function will break the binding of `this` to the Fastify `Reply` instance. -Note: using `decorateReply` will emit a warning if used with a reference type: +Note: using `decorateReply` will throw and error if used with a reference type: ```js // Don't do this fastify.decorateReply('foo', { bar: 'fizz'}) ``` -In this example, the reference of the object is shared with all the requests: -**any mutation will impact all requests, potentially creating security -vulnerabilities or memory leaks**. To achieve proper encapsulation across -requests configure a new value for each incoming request in the [`'onRequest'` -hook](./Hooks.md#onrequest). Example: +In this example, the reference of the object would be shared with all the requests +and **any mutation will impact all requests, potentially creating security +vulnerabilities or memory leaks**, so Fastify blocks it. + +To achieve proper encapsulation across requests configure a new value for each +incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). Example: ```js const fp = require('fastify-plugin') @@ -219,18 +220,20 @@ fastify.decorateRequest('utility', function () { Note: using an arrow function will break the binding of `this` to the Fastify `Request` instance. -Note: using `decorateRequest` will emit a warning if used with a reference type: +Note: using `decorateRequest` will emit an error if used with a reference type: ```js // Don't do this fastify.decorateRequest('foo', { bar: 'fizz'}) ``` -In this example, the reference of the object is shared with all the requests: -**any mutation will impact all requests, potentially creating security -vulnerabilities or memory leaks**. +In this example, the reference of the object would be shared with all the requests +and **any mutation will impact all requests, potentially creating security +vulnerabilities or memory leaks**, so Fastify blocks it. To achieve proper encapsulation across requests configure a new value for each -incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). Example: +incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). + +Example: ```js const fp = require('fastify-plugin') @@ -245,6 +248,29 @@ async function myPlugin (app) { module.exports = fp(myPlugin) ``` +The hook solution is more flexible and allows for more complex initialization +because you can add more logic to the `onRequest` hook. + +Another approach is to use the getter/setter pattern, but it requires 2 decorators: + +```js +fastify.decorateRequest('my_decorator_holder') // define the holder +fastify.decorateRequest('user', { + getter () { + this.my_decorator_holder ??= {} // initialize the holder + return this.my_decorator_holder + } +}) + +fastify.get('/', async function (req, reply) { + req.user.access = 'granted' + // other code +}) +``` + +This ensures that the `user` property is always unique for each +request. + See [`decorate`](#decorate) for information about the `dependencies` parameter. #### `hasDecorator(name)` diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 9d5a0b00ef6..56c0992a8f6 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -36,6 +36,7 @@ - [FST_ERR_DEC_DEPENDENCY_INVALID_TYPE](#fst_err_dec_dependency_invalid_type) - [FST_ERR_DEC_MISSING_DEPENDENCY](#fst_err_dec_missing_dependency) - [FST_ERR_DEC_AFTER_START](#fst_err_dec_after_start) + - [FST_ERR_DEC_REFERENCE_TYPE](#fst_err_dec_reference_type) - [FST_ERR_HOOK_INVALID_TYPE](#fst_err_hook_invalid_type) - [FST_ERR_HOOK_INVALID_HANDLER](#fst_err_hook_invalid_handler) - [FST_ERR_HOOK_INVALID_ASYNC_HANDLER](#fst_err_hook_invalid_async_handler) @@ -308,6 +309,7 @@ Below is a table with all the error codes that Fastify uses. | FST_ERR_DEC_DEPENDENCY_INVALID_TYPE | The dependencies of decorator must be of type `Array`. | Use an array for the dependencies. | [#3090](https://github.com/fastify/fastify/pull/3090) | | FST_ERR_DEC_MISSING_DEPENDENCY | The decorator cannot be registered due to a missing dependency. | Register the missing dependency. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_DEC_AFTER_START | The decorator cannot be added after start. | Add the decorator before starting the server. | [#2128](https://github.com/fastify/fastify/pull/2128) | +| FST_ERR_DEC_REFERENCE_TYPE | The decorator cannot be a reference type. | Define the decorator with a getter/setter interface or an empty decorator with a hook. | [#5462](https://github.com/fastify/fastify/pull/5462) | | FST_ERR_HOOK_INVALID_TYPE | The hook name must be a string. | Use a string for the hook name. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_HOOK_INVALID_HANDLER | The hook callback must be a function. | Use a function for the hook callback. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_HOOK_INVALID_ASYNC_HANDLER | Async function has too many arguments. Async hooks should not use the `done` argument. | Remove the `done` argument from the async hook. | [#4367](https://github.com/fastify/fastify/pull/4367) | diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 4c4b6404b0d..45b32781b6b 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -9,7 +9,6 @@ - [FSTWRN002](#FSTWRN002) - [Fastify Deprecation Codes](#fastify-deprecation-codes) - [FSTDEP005](#FSTDEP005) - - [FSTDEP006](#FSTDEP006) - [FSTDEP007](#FSTDEP007) - [FSTDEP008](#FSTDEP008) - [FSTDEP009](#FSTDEP009) @@ -73,7 +72,6 @@ Deprecation codes are further supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | | FSTDEP005 | You are accessing the deprecated `request.connection` property. | Use `request.socket`. | [#2594](https://github.com/fastify/fastify/pull/2594) | -| FSTDEP006 | You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. | Do not use Arrays/Objects as values when decorating Request/Reply. | [#2688](https://github.com/fastify/fastify/pull/2688) | | FSTDEP007 | You are trying to set a HEAD route using `exposeHeadRoute` route flag when a sibling route is already set. | Remove `exposeHeadRoutes` or explicitly set `exposeHeadRoutes` to `false` | [#2700](https://github.com/fastify/fastify/pull/2700) | | FSTDEP008 | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | | FSTDEP009 | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | diff --git a/lib/decorate.js b/lib/decorate.js index 3fe7cc7b3f4..1ff41276b78 100644 --- a/lib/decorate.js +++ b/lib/decorate.js @@ -13,11 +13,10 @@ const { FST_ERR_DEC_ALREADY_PRESENT, FST_ERR_DEC_MISSING_DEPENDENCY, FST_ERR_DEC_AFTER_START, + FST_ERR_DEC_REFERENCE_TYPE, FST_ERR_DEC_DEPENDENCY_INVALID_TYPE } = require('./errors') -const { FSTDEP006 } = require('./warnings') - function decorate (instance, name, fn, dependencies) { if (Object.prototype.hasOwnProperty.call(instance, name)) { throw new FST_ERR_DEC_ALREADY_PRESENT(name) @@ -58,7 +57,7 @@ function decorateConstructor (konstructor, name, fn, dependencies) { function checkReferenceType (name, fn) { if (typeof fn === 'object' && fn && !(typeof fn.getter === 'function' || typeof fn.setter === 'function')) { - FSTDEP006(name) + throw new FST_ERR_DEC_REFERENCE_TYPE(name, typeof fn) } } diff --git a/lib/errors.js b/lib/errors.js index d1df67e4961..3e7a3f9512b 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -151,6 +151,10 @@ const codes = { 'FST_ERR_DEC_AFTER_START', "The decorator '%s' has been added after start!" ), + FST_ERR_DEC_REFERENCE_TYPE: createError( + 'FST_ERR_DEC_REFERENCE_TYPE', + "The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead." + ), /** * hooks diff --git a/lib/warnings.js b/lib/warnings.js index ec31b642108..6c47a6bdeac 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -5,7 +5,6 @@ const { createDeprecation, createWarning } = require('process-warning') /** * Deprecation codes: * - FSTDEP005 - * - FSTDEP006 * - FSTDEP007 * - FSTDEP008 * - FSTDEP009 @@ -26,11 +25,6 @@ const FSTDEP005 = createDeprecation({ message: 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.' }) -const FSTDEP006 = createDeprecation({ - code: 'FSTDEP006', - message: 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s' -}) - const FSTDEP007 = createDeprecation({ code: 'FSTDEP007', message: 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.' @@ -107,7 +101,6 @@ const FSTSEC001 = createWarning({ module.exports = { FSTDEP005, - FSTDEP006, FSTDEP007, FSTDEP008, FSTDEP009, diff --git a/test/decorator-namespace.test._js_ b/test/decorator-namespace.test._js_ new file mode 100644 index 00000000000..5f8cbf76c01 --- /dev/null +++ b/test/decorator-namespace.test._js_ @@ -0,0 +1,31 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const t = require('tap') +const test = t.test +const Fastify = require('..') +const fp = require('fastify-plugin') + +test('plugin namespace', async t => { + t.plan(2) + const app = Fastify() + + await app.register(async function plugin (app, opts) { + app.decorate('utility', function () { + return 'utility' + }) + + app.get('/', function (req, reply) { + // ! here the plugin would use app.utility() + // ! the plugin does not know about the namespace + reply.send({ utility: app.utility() }) + }) + }, { namepace: 'foo' }) + + // ! but outside the plugin the decorator would be app.foo.utility() + t.ok(app.foo.utility) + + const res = await app.inject('/') + t.same(res.json(), { utility: 'utility' }) +}) diff --git a/test/decorator.test.js b/test/decorator.test.js index f16ba772fb2..50ff2809d6c 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -8,7 +8,6 @@ const Fastify = require('..') const fp = require('fastify-plugin') const sget = require('simple-get').concat const symbols = require('../lib/symbols.js') -const proxyquire = require('proxyquire') test('server methods should exist', t => { t.plan(2) @@ -789,49 +788,44 @@ test('decorate* should throw if called after ready', async t => { await fastify.close() }) -test('decorate* should emit warning if an array is passed', t => { - t.plan(1) +test('decorate* should emit error if an array is passed', t => { + t.plan(2) - function onWarning (name) { - t.equal(name, 'test_array') + const fastify = Fastify() + try { + fastify.decorateRequest('test_array', []) + t.fail('should not decorate') + } catch (err) { + t.same(err.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.same(err.message, "The decorator 'test_array' of type 'object' is a reference type. Use the { getter, setter } interface instead.") } +}) - const decorate = proxyquire('../lib/decorate', { - './warnings': { - FSTDEP006: onWarning - } - }) - const fastify = proxyquire('..', { './lib/decorate.js': decorate })() - fastify.decorateRequest('test_array', []) +test('server.decorate should not emit error if reference type is passed', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.decorate('test_array', []) + fastify.decorate('test_object', {}) + await fastify.ready() + t.pass('Done') }) test('decorate* should emit warning if object type is passed', t => { - t.plan(1) + t.plan(2) - function onWarning (name) { - t.equal(name, 'test_object') + const fastify = Fastify() + try { + fastify.decorateRequest('test_object', { foo: 'bar' }) + t.fail('should not decorate') + } catch (err) { + t.same(err.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.same(err.message, "The decorator 'test_object' of type 'object' is a reference type. Use the { getter, setter } interface instead.") } - - const decorate = proxyquire('../lib/decorate', { - './warnings': { - FSTDEP006: onWarning - } - }) - const fastify = proxyquire('..', { './lib/decorate.js': decorate })() - fastify.decorateRequest('test_object', { foo: 'bar' }) }) test('decorate* should not emit warning if object with getter/setter is passed', t => { - function onWarning (warning) { - t.fail('Should not call a warn') - } - - const decorate = proxyquire('../lib/decorate', { - './warnings': { - FSTDEP006: onWarning - } - }) - const fastify = proxyquire('..', { './lib/decorate.js': decorate })() + const fastify = Fastify() fastify.decorateRequest('test_getter_setter', { setter (val) { @@ -844,17 +838,74 @@ test('decorate* should not emit warning if object with getter/setter is passed', t.end('Done') }) -test('decorate* should not emit warning if string,bool,numbers are passed', t => { - function onWarning (warning) { - t.fail('Should not call a warn') - } +test('decorateRequest with getter/setter can handle encapsulation', async t => { + t.plan(24) - const decorate = proxyquire('../lib/decorate', { - './warnings': { - FSTDEP006: onWarning + const fastify = Fastify({ logger: true }) + + fastify.decorateRequest('test_getter_setter_holder') + fastify.decorateRequest('test_getter_setter', { + getter () { + this.test_getter_setter_holder ??= {} + return this.test_getter_setter_holder } }) - const fastify = proxyquire('..', { './lib/decorate.js': decorate })() + + fastify.get('/', async function (req, reply) { + t.same(req.test_getter_setter, {}, 'a getter') + req.test_getter_setter.a = req.id + t.same(req.test_getter_setter, { a: req.id }) + }) + + fastify.addHook('onResponse', async function hook (req, reply) { + t.same(req.test_getter_setter, { a: req.id }) + }) + + await Promise.all([ + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)) + ]) +}) + +test('decorateRequest with getter/setter can handle encapsulation with arrays', async t => { + t.plan(24) + + const fastify = Fastify({ logger: true }) + + fastify.decorateRequest('array_holder') + fastify.decorateRequest('my_array', { + getter () { + this.array_holder ??= [] + return this.array_holder + } + }) + + fastify.get('/', async function (req, reply) { + t.same(req.my_array, []) + req.my_array.push(req.id) + t.same(req.my_array, [req.id]) + }) + + fastify.addHook('onResponse', async function hook (req, reply) { + t.same(req.my_array, [req.id]) + }) + + await Promise.all([ + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)), + fastify.inject('/').then(res => t.same(res.statusCode, 200)) + ]) +}) + +test('decorate* should not emit error if string,bool,numbers are passed', t => { + const fastify = Fastify() fastify.decorateRequest('test_str', 'foo') fastify.decorateRequest('test_bool', true) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index f9fd346cf42..570391e8d41 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -test('should expose 83 errors', t => { +test('should expose 84 errors', t => { t.plan(1) const exportedKeys = Object.keys(errors) let counter = 0 @@ -14,11 +14,11 @@ test('should expose 83 errors', t => { counter++ } } - t.equal(counter, 83) + t.equal(counter, 84) }) test('ensure name and codes of Errors are identical', t => { - t.plan(83) + t.plan(84) const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -247,6 +247,16 @@ test('FST_ERR_DEC_AFTER_START', t => { t.ok(error instanceof Error) }) +test('FST_ERR_DEC_REFERENCE_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_REFERENCE_TYPE() + t.equal(error.name, 'FastifyError') + t.equal(error.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.equal(error.message, "The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead.") + t.equal(error.statusCode, 500) + t.ok(error instanceof Error) +}) + test('FST_ERR_HOOK_INVALID_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_HOOK_INVALID_TYPE() @@ -868,7 +878,7 @@ test('FST_ERR_ERROR_HANDLER_NOT_FN', t => { }) test('Ensure that all errors are in Errors.md TOC', t => { - t.plan(83) + t.plan(84) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -880,7 +890,7 @@ test('Ensure that all errors are in Errors.md TOC', t => { }) test('Ensure that non-existing errors are not in Errors.md TOC', t => { - t.plan(83) + t.plan(84) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = / {4}- \[([A-Z0-9_]+)\]\(#[a-z0-9_]+\)/g @@ -893,7 +903,7 @@ test('Ensure that non-existing errors are not in Errors.md TOC', t => { }) test('Ensure that all errors are in Errors.md documented', t => { - t.plan(83) + t.plan(84) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -905,7 +915,7 @@ test('Ensure that all errors are in Errors.md documented', t => { }) test('Ensure that non-existing errors are not in Errors.md documented', t => { - t.plan(83) + t.plan(84) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /([0-9a-zA-Z_]+)<\/a>/g diff --git a/types/errors.d.ts b/types/errors.d.ts index d529bb66ac2..20f31d74055 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -24,6 +24,7 @@ export type FastifyErrorCodes = Record< 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE' | 'FST_ERR_DEC_MISSING_DEPENDENCY' | 'FST_ERR_DEC_AFTER_START' | +'FST_ERR_DEC_REFERENCE_TYPE' | 'FST_ERR_HOOK_INVALID_TYPE' | 'FST_ERR_HOOK_INVALID_HANDLER' | 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER' | From 572f8dafd12916f245e6644fc1f7a687156578cd Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 20 May 2024 09:05:06 +0200 Subject: [PATCH 0668/1295] docs: update indentation on type providers section (#5474) --- docs/Reference/Type-Providers.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 0f81be2c274..f44d8203623 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -37,9 +37,8 @@ $ npm i @fastify/type-provider-json-schema-to-ts ``` ```typescript -import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' - import fastify from 'fastify' +import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' const server = fastify().withTypeProvider() @@ -72,11 +71,10 @@ $ npm i @fastify/type-provider-typebox ``` ```typescript +import fastify from 'fastify' import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox' import { Type } from '@sinclair/typebox' -import fastify from 'fastify' - const server = fastify().withTypeProvider() server.get('/route', { From 9ac27e0d7c8456cc685ac040e1d326ed0de10446 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 21 May 2024 20:33:35 +0200 Subject: [PATCH 0669/1295] feat: log all available addresses if listening host is 0.0.0.0 (#5476) --- lib/server.js | 52 ++++++++++++++++++++++++++++++++++--------- test/listen.1.test.js | 20 +++++------------ 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/lib/server.js b/lib/server.js index aecfddfa761..1a6c86cb9e7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -3,6 +3,7 @@ const http = require('node:http') const https = require('node:https') const dns = require('node:dns') +const os = require('node:os') const { kState, kOptions, kServerBindings } = require('./symbols') const { onListenHookRunner } = require('./hooks') @@ -296,24 +297,53 @@ function getServerInstance (options, httpHandler) { } return server } +/** + * Inspects the provided `server.address` object and returns a + * normalized list of IP address strings. Normalization in this + * case refers to mapping wildcard `0.0.0.0` to the list of IP + * addresses the wildcard refers to. + * + * @see https://nodejs.org/docs/latest/api/net.html#serveraddress + * + * @param {object} A server address object as described in the + * linked docs. + * + * @returns {string[]} + */ +function getAddresses (address) { + if (address.address === '0.0.0.0') { + return Object.values(os.networkInterfaces()).flatMap((iface) => { + return iface.filter((iface) => iface.family === 'IPv4') + }).sort((iface) => { + // Order the interfaces so that internal ones come first + /* c8 ignore next 1 */ + return iface.internal ? -1 : 1 + }).map((iface) => { return iface.address }) + } + return [address.address] +} function logServerAddress (server, listenTextResolver) { - let address = server.address() - const isUnixSocket = typeof address === 'string' - /* istanbul ignore next */ + let addresses + const isUnixSocket = typeof server.address() === 'string' if (!isUnixSocket) { - if (address.address.indexOf(':') === -1) { - address = address.address + ':' + address.port + if (server.address().address.indexOf(':') === -1) { + // IPv4 + addresses = getAddresses(server.address()).map((address) => address + ':' + server.address().port) } else { - address = '[' + address.address + ']:' + address.port + // IPv6 + addresses = ['[' + server.address().address + ']:' + server.address().port] } + + addresses = addresses.map((address) => ('http' + (this[kOptions].https ? 's' : '') + '://') + address) + } else { + addresses = [server.address()] } - /* istanbul ignore next */ - address = (isUnixSocket ? '' : ('http' + (this[kOptions].https ? 's' : '') + '://')) + address - const serverListeningText = listenTextResolver(address) - this.log.info(serverListeningText) - return address + for (const address of addresses) { + this.log.info(listenTextResolver(address)) + } + return addresses[0] } function http2 () { diff --git a/test/listen.1.test.js b/test/listen.1.test.js index 370792a76af..9b5aeb8e59d 100644 --- a/test/listen.1.test.js +++ b/test/listen.1.test.js @@ -29,25 +29,15 @@ test('Async/await listen with arguments', async t => { t.fail('should not be deprecated') }) - t.plan(1) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) const addr = await fastify.listen({ port: 0, host: '0.0.0.0' }) const address = fastify.server.address() - t.equal(addr, `http://${address.address}:${address.port}`) -}) - -test('Promise listen with arguments', t => { - process.on('warning', () => { - t.fail('should not be deprecated') - }) - - t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0, host: '0.0.0.0' }).then(addr => { - const address = fastify.server.address() - t.equal(addr, `http://${address.address}:${address.port}`) + t.equal(addr, `http://127.0.0.1:${address.port}`) + t.same(address, { + address: '0.0.0.0', + family: 'IPv4', + port: address.port }) }) From a00af2f9bf2802b482ad8507df5ab7c0b45662c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sat, 25 May 2024 18:33:48 +0200 Subject: [PATCH 0670/1295] chore: remove node 18 from CI (#5481) --- .github/workflows/benchmark-parser.yml | 10 +--------- .github/workflows/benchmark.yml | 10 +--------- .github/workflows/ci-alternative-runtime.yml | 5 +---- .github/workflows/ci.yml | 5 +---- .github/workflows/integration-alternative-runtimes.yml | 5 +---- .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- 7 files changed, 8 insertions(+), 33 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 5891b26a048..b8aea74788f 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -11,15 +11,13 @@ jobs: permissions: contents: read outputs: - PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} PR-BENCH-22: ${{ steps.benchmark-pr.outputs.BENCH_RESULT22 }} - MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} MAIN-BENCH-22: ${{ steps.benchmark-main.outputs.BENCH_RESULT22 }} strategy: matrix: - node-version: [18, 20, 22] + node-version: [20, 22] steps: - uses: actions/checkout@v4 with: @@ -75,12 +73,6 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | - **Node**: 18 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} - - --- - **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index c574dd74942..b8333ab1abb 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -11,15 +11,13 @@ jobs: permissions: contents: read outputs: - PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} PR-BENCH-22: ${{ steps.benchmark-pr.outputs.BENCH_RESULT22 }} - MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} MAIN-BENCH-22: ${{ steps.benchmark-main.outputs.BENCH_RESULT22 }} strategy: matrix: - node-version: [18, 20, 22] + node-version: [20, 22] steps: - uses: actions/checkout@v4 with: @@ -75,12 +73,6 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | - **Node**: 18 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} - - --- - **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index b894cc49136..27bf7d1b755 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -26,12 +26,9 @@ jobs: contents: read strategy: matrix: - node-version: [18, 20] + node-version: [20] os: [macos-latest, ubuntu-latest, windows-latest] include: - - runtime: nsolid - node-version: 18 - nsolid-version: 5 - runtime: nsolid node-version: 20 nsolid-version: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4aef47bf480..d19768984a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,11 +111,8 @@ jobs: strategy: matrix: - node-version: [18, 20, 22] + node-version: [20, 22] os: [macos-latest, ubuntu-latest, windows-latest] - exclude: - - os: macos-latest - node-version: 16 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index 967e4a751da..1c69bb7e8ee 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -25,12 +25,9 @@ jobs: matrix: os: [ubuntu-latest] runtime: [nsolid] - node-version: [18, 20] + node-version: [20] pnpm-version: [8] include: - - nsolid-version: 5 - node-version: 18 - runtime: nsolid - nsolid-version: 5 node-version: 20 runtime: nsolid diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index d890cb6c127..21b5ba7b785 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: - node-version: [18, 20, 22] + node-version: [20, 22] os: [ubuntu-latest] pnpm-version: [8] diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 22b45d88c16..f44dc88a7d4 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [18, 20, 22] + node-version: [20, 22] os: [ubuntu-latest] pnpm-version: [8] @@ -48,7 +48,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [18, 20, 22] + node-version: [20, 22] os: [ubuntu-latest] steps: From 2e40edaabe34e99a2f34b16655dd9cec985e8f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 26 May 2024 09:21:53 +0200 Subject: [PATCH 0671/1295] feat: writeEarlyHints (#5480) --- docs/Reference/Reply.md | 23 ++++++++ lib/reply.js | 5 ++ test/reply-earlyHints.test.js | 98 +++++++++++++++++++++++++++++++++++ test/types/reply.test-d.ts | 1 + types/reply.d.ts | 3 +- 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 test/reply-earlyHints.test.js diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index bbaf4f510ee..7b1135c4adf 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -58,6 +58,8 @@ since the request was received by Fastify. - `.getHeaders()` - Gets a shallow copy of all current response headers. - `.removeHeader(key)` - Remove the value of a previously set header. - `.hasHeader(name)` - Determine if a header has been set. +- `.writeEarlyHints(hints, callback)` - Sends early hints to the user + while the response is being prepared. - `.trailer(key, function)` - Sets a response trailer. - `.hasTrailer(key)` - Determine if a trailer has been set. - `.removeTrailer(key)` - Remove the value of a previously set trailer. @@ -243,6 +245,27 @@ reply.getHeader('x-foo') // undefined Returns a boolean indicating if the specified header has been set. +### .writeEarlyHints(hints, callback) + + +Sends early hints to the client. Early hints allow the client to +start processing resources before the final response is sent. +This can improve performance by allowing the client to preload +or preconnect to resources while the server is still generating the response. + +The hints parameter is an object containing the early hint key-value pairs. + +Example: + +```js +reply.writeEarlyHints({ + Link: '; rel=preload; as=style' +}); +``` + +The optional callback parameter is a function that will be called +once the hint is sent or if an error occurs. + ### .trailer(key, function) diff --git a/lib/reply.js b/lib/reply.js index 33c47951613..015b71b0987 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -137,6 +137,11 @@ Object.defineProperties(Reply.prototype, { } }) +Reply.prototype.writeEarlyHints = function (hints, callback) { + this.raw.writeEarlyHints(hints, callback) + return this +} + Reply.prototype.hijack = function () { this[kReplyHijacked] = true return this diff --git a/test/reply-earlyHints.test.js b/test/reply-earlyHints.test.js new file mode 100644 index 00000000000..48228208d82 --- /dev/null +++ b/test/reply-earlyHints.test.js @@ -0,0 +1,98 @@ +'use strict' + +const Fastify = require('..') +const { test } = require('tap') +const http = require('http') +const http2 = require('http2') + +const testResBody = 'Hello, world!' + +test('sends early hints', (t) => { + t.plan(6) + + const fastify = Fastify({ + logger: false + }) + + fastify.get('/', async (request, reply) => { + reply.writeEarlyHints({ + link: '; rel=preload; as=style' + }, () => { + t.pass('callback called') + }) + + return testResBody + }) + + fastify.listen({ port: 0 }, (err, address) => { + t.error(err) + + const req = http.get(address) + + req.on('information', (res) => { + t.equal(res.statusCode, 103) + t.equal(res.headers.link, '; rel=preload; as=style') + }) + + req.on('response', (res) => { + t.equal(res.statusCode, 200) + + let data = '' + res.on('data', (chunk) => { + data += chunk + }) + + res.on('end', () => { + t.equal(data, testResBody) + fastify.close(t.end) + }) + }) + }) +}) + +test('sends early hints (http2)', (t) => { + t.plan(6) + + const fastify = Fastify({ + http2: true, + logger: false + }) + + fastify.get('/', async (request, reply) => { + reply.writeEarlyHints({ + link: '; rel=preload; as=style' + }) + + return testResBody + }) + + fastify.listen({ port: 0 }, (err, address) => { + t.error(err) + + const client = http2.connect(address) + const req = client.request() + + req.on('headers', (headers) => { + t.not(headers, undefined) + t.equal(headers[':status'], 103) + t.equal(headers.link, '; rel=preload; as=style') + }) + + req.on('response', (headers) => { + t.equal(headers[':status'], 200) + }) + + let data = '' + req.on('data', (chunk) => { + data += chunk + }) + + req.on('end', () => { + t.equal(data, testResBody) + client.close() + fastify.close(t.end) + }) + + req.end() + }) +}) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 92318b33c8c..fd1f01daf29 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -22,6 +22,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType(reply.elapsedTime) expectType(reply.statusCode) expectType(reply.sent) + expectType<(hints: Record, callback?: (() => void) | undefined) => void>(reply.writeEarlyHints) expectType<((payload?: unknown) => FastifyReply)>(reply.send) expectAssignable<(key: string, value: any) => FastifyReply>(reply.header) expectAssignable<(values: {[key: string]: any}) => FastifyReply>(reply.headers) diff --git a/types/reply.d.ts b/types/reply.d.ts index f660a50a5e0..7c18b78a76c 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -57,7 +57,8 @@ export interface FastifyReply< getHeaders(): Record; removeHeader(key: HttpHeader): FastifyReply; hasHeader(key: HttpHeader): boolean; - // Note: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param + writeEarlyHints(hints: Record, callback?: () => void): void; + // TODO: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param redirect(statusCode: number, url: string): FastifyReply; redirect(url: string): FastifyReply; hijack(): FastifyReply; From 24af949dd92ae2a1adbb2db313121cfba7f1345f Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 26 May 2024 09:52:19 +0200 Subject: [PATCH 0672/1295] docs: update indentation and code snippet in the routes section (#5482) --- docs/Reference/Routes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index a276dd8bc4d..46b4a3dbd5e 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -486,14 +486,14 @@ const route = { schema: {}, } -fastify.register(function(app, _, done) { +fastify.register(function (app, _, done) { app.get('/users', () => {}) app.route(route) done() }, { prefix: '/v1' }) // global route prefix -await fastify.listen({ port: 0 }) +await fastify.listen({ port: 3000 }) ``` ### Route Prefixing and fastify-plugin @@ -817,8 +817,8 @@ const secret = { > const Fastify = require('fastify') > > const fastify = Fastify({ -> frameworkErrors: function(err, res, res) { -> if(err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) { +> frameworkErrors: function (err, res, res) { +> if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) { > res.code(400) > return res.send("Invalid header provided") > } else { From 67f25aaa58c7f6aaf26400ae1740a615389be8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 26 May 2024 12:10:09 +0200 Subject: [PATCH 0673/1295] refactor: change `reply.redirect()` signature (#5483) * feat: change `reply.redirect()` signature * feat: change `reply.redirect()` signature * docs * docs * update message * fix deprecation * update message --- docs/Reference/Reply.md | 12 ++++++------ docs/Reference/Warnings.md | 2 ++ lib/reply.js | 14 ++++++++++---- lib/warnings.js | 8 ++++++++ test/internals/reply.test.js | 36 +++++++++++++++++++++++++++++++++--- test/types/reply.test-d.ts | 2 +- types/reply.d.ts | 8 +++++--- 7 files changed, 65 insertions(+), 17 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 7b1135c4adf..33445554c35 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -17,7 +17,7 @@ - [.trailer(key, function)](#trailerkey-function) - [.hasTrailer(key)](#hastrailerkey) - [.removeTrailer(key)](#removetrailerkey) - - [.redirect([code ,] dest)](#redirectcode--dest) + - [.redirect(dest, [code ,])](#redirectdest--code) - [.callNotFound()](#callnotfound) - [.getResponseTime()](#getresponsetime) - [.type(contentType)](#typecontenttype) @@ -64,8 +64,8 @@ since the request was received by Fastify. - `.hasTrailer(key)` - Determine if a trailer has been set. - `.removeTrailer(key)` - Remove the value of a previously set trailer. - `.type(value)` - Sets the header `Content-Type`. -- `.redirect([code,] dest)` - Redirect to the specified URL, the status code is - optional (default to `302`). +- `.redirect(dest, [code,])` - Redirect to the specified URL, the status code is + optional (defaults to `302`). - `.callNotFound()` - Invokes the custom not found handler. - `.serialize(payload)` - Serializes the specified payload using the default JSON serializer or using the custom serializer (if one is set) and returns the @@ -322,7 +322,7 @@ reply.getTrailer('server-timing') // undefined ``` -### .redirect([code ,] dest) +### .redirect(dest, [code ,]) Redirects a request to the specified URL, the status code is optional, default @@ -343,7 +343,7 @@ reply.redirect('/home') Example (no `reply.code()` call) sets status code to `303` and redirects to `/home` ```js -reply.redirect(303, '/home') +reply.redirect('/home', 303) ``` Example (`reply.code()` call) sets status code to `303` and redirects to `/home` @@ -353,7 +353,7 @@ reply.code(303).redirect('/home') Example (`reply.code()` call) sets status code to `302` and redirects to `/home` ```js -reply.code(303).redirect(302, '/home') +reply.code(303).redirect('/home', 302) ``` ### .callNotFound() diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 45b32781b6b..ca55f1c4139 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -21,6 +21,7 @@ - [FSTDEP018](#FSTDEP018) - [FSTDEP019](#FSTDEP019) - [FSTDEP020](#FSTDEP020) + - [FSTDEP021](#FSTDEP021) ## Warnings @@ -84,3 +85,4 @@ Deprecation codes are further supported by the Node.js CLI options: | FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | | FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | | FSTDEP020 | You are using the deprecated `reply.getReponseTime()` method. | Use the `reply.elapsedTime` property instead. | [#5263](https://github.com/fastify/fastify/pull/5263) | +| FSTDEP021 | The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. | [#5483](https://github.com/fastify/fastify/pull/5483) | diff --git a/lib/reply.js b/lib/reply.js index 015b71b0987..4291970b13d 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -55,7 +55,7 @@ const { FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') -const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020 } = require('./warnings') +const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020, FSTDEP021 } = require('./warnings') const toString = Object.prototype.toString @@ -462,9 +462,15 @@ Reply.prototype.type = function (type) { return this } -Reply.prototype.redirect = function (code, url) { - if (typeof code === 'string') { - url = code +Reply.prototype.redirect = function (url, code) { + if (typeof url === 'number') { + FSTDEP021() + const temp = code + code = url + url = temp + } + + if (!code) { code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302 } diff --git a/lib/warnings.js b/lib/warnings.js index 6c47a6bdeac..51dc87e86d4 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -16,6 +16,8 @@ const { createDeprecation, createWarning } = require('process-warning') * - FSTDEP017 * - FSTDEP018 * - FSTDEP019 + * - FSTDEP020 + * - FSTDEP021 * - FSTWRN001 * - FSTSEC001 */ @@ -85,6 +87,11 @@ const FSTDEP020 = createDeprecation({ message: 'You are using the deprecated "reply.getResponseTime()" method. Use the "reply.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.' }) +const FSTDEP021 = createDeprecation({ + code: 'FSTDEP021', + message: 'The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`' +}) + const FSTWRN001 = createWarning({ name: 'FastifyWarning', code: 'FSTWRN001', @@ -113,6 +120,7 @@ module.exports = { FSTDEP018, FSTDEP019, FSTDEP020, + FSTDEP021, FSTWRN001, FSTSEC001 } diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 3bb5e561e55..b06a8e2077a 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -19,7 +19,7 @@ const { } = require('../../lib/symbols') const fs = require('node:fs') const path = require('node:path') -const { FSTDEP010, FSTDEP019, FSTDEP020 } = require('../../lib/warnings') +const { FSTDEP010, FSTDEP019, FSTDEP020, FSTDEP021 } = require('../../lib/warnings') const agent = new http.Agent({ keepAlive: false }) @@ -250,7 +250,7 @@ test('within an instance', t => { }) fastify.get('/redirect-code', function (req, reply) { - reply.redirect(301, '/') + reply.redirect('/', 301) }) fastify.get('/redirect-code-before-call', function (req, reply) { @@ -258,7 +258,7 @@ test('within an instance', t => { }) fastify.get('/redirect-code-before-call-overwrite', function (req, reply) { - reply.code(307).redirect(302, '/') + reply.code(307).redirect('/', 302) }) fastify.get('/custom-serializer', function (req, reply) { @@ -2094,6 +2094,36 @@ test('redirect to an invalid URL should not crash the server', async t => { await fastify.close() }) +test('redirect with deprecated signature should warn', t => { + t.plan(4) + + process.removeAllListeners('warning') + process.on('warning', onWarning) + function onWarning (warning) { + t.equal(warning.name, 'DeprecationWarning') + t.equal(warning.code, FSTDEP021.code) + } + + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.redirect(302, '/new') + }) + + fastify.get('/new', (req, reply) => { + reply.send('new') + }) + + fastify.inject({ method: 'GET', url: '/' }, (err, res) => { + t.error(err) + t.pass() + + process.removeListener('warning', onWarning) + }) + + FSTDEP021.emitted = false +}) + test('invalid response headers should not crash the server', async t => { const fastify = Fastify() fastify.route({ diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index fd1f01daf29..505dc45f0c9 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -30,7 +30,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectAssignable<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders) expectAssignable<(key: string) => FastifyReply>(reply.removeHeader) expectAssignable<(key: string) => boolean>(reply.hasHeader) - expectType<{(statusCode: number, url: string): FastifyReply; (url: string): FastifyReply }>(reply.redirect) + expectType<{(statusCode: number, url: string): FastifyReply;(url: string, statusCode?: number): FastifyReply;}>(reply.redirect) expectType<() => FastifyReply>(reply.hijack) expectType<() => void>(reply.callNotFound) // Test reply.getResponseTime() deprecation diff --git a/types/reply.d.ts b/types/reply.d.ts index 7c18b78a76c..2dfa52f894e 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -57,10 +57,12 @@ export interface FastifyReply< getHeaders(): Record; removeHeader(key: HttpHeader): FastifyReply; hasHeader(key: HttpHeader): boolean; - writeEarlyHints(hints: Record, callback?: () => void): void; - // TODO: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param + /** + * @deprecated The `reply.redirect()` method has a new signature: `reply.reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. + */ redirect(statusCode: number, url: string): FastifyReply; - redirect(url: string): FastifyReply; + redirect(url: string, statusCode?: number): FastifyReply; + writeEarlyHints(hints: Record, callback?: () => void): void; hijack(): FastifyReply; callNotFound(): void; /** From 6502098408b93ed1ef0f8fc3dc85c85dc8b266b8 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 27 May 2024 09:17:31 +0200 Subject: [PATCH 0674/1295] docs: update indentation and code snippet in the type providers section (#5485) --- docs/Reference/Type-Providers.md | 41 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index f44d8203623..e90babefe86 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -43,22 +43,20 @@ import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts const server = fastify().withTypeProvider() server.get('/route', { - schema: { - querystring: { - type: 'object', - properties: { - foo: { type: 'number' }, - bar: { type: 'string' }, - }, - required: ['foo', 'bar'] - } + schema: { + querystring: { + type: 'object', + properties: { + foo: { type: 'number' }, + bar: { type: 'string' }, + }, + required: ['foo', 'bar'] } - + } }, (request, reply) => { - // type Query = { foo: number, bar: string } - - const { foo, bar } = request.query // type safe! + // type Query = { foo: number, bar: string } + const { foo, bar } = request.query // type safe! }) ``` @@ -78,17 +76,16 @@ import { Type } from '@sinclair/typebox' const server = fastify().withTypeProvider() server.get('/route', { - schema: { - querystring: Type.Object({ - foo: Type.Number(), - bar: Type.String() - }) - } + schema: { + querystring: Type.Object({ + foo: Type.Number(), + bar: Type.String() + }) + } }, (request, reply) => { - // type Query = { foo: number, bar: string } - - const { foo, bar } = request.query // type safe! + // type Query = { foo: number, bar: string } + const { foo, bar } = request.query // type safe! }) ``` From 7b11fc36dd5765d6b57143d251c923fd802947bd Mon Sep 17 00:00:00 2001 From: Thomas Hunter II Date: Fri, 31 May 2024 07:07:52 -0700 Subject: [PATCH 0675/1295] feat: emit diagnostics_channel events upon routing request (#5252) * feat: emit diagnostics_channel upon handling request * use TracingChannel#hasSubscribers * update dc-polyfill to fix a hang in c8 lib --- docs/Reference/Hooks.md | 62 ++++++++++++---- fastify.js | 14 ++-- lib/handleRequest.js | 72 +++++++++++++----- lib/wrapThenable.js | 68 +++++++++++------ package.json | 1 + test/diagnostics-channel/404.test.js | 57 ++++++++++++++ .../async-delay-request.test.js | 74 +++++++++++++++++++ .../diagnostics-channel/async-request.test.js | 72 ++++++++++++++++++ .../error-before-handler.test.js | 36 +++++++++ .../diagnostics-channel/error-request.test.js | 61 +++++++++++++++ test/diagnostics-channel/error-status.test.js | 39 ++++++++++ .../init.test.js} | 22 ++---- .../sync-delay-request.test.js | 58 +++++++++++++++ .../sync-request-reply.test.js | 58 +++++++++++++++ test/diagnostics-channel/sync-request.test.js | 61 +++++++++++++++ test/internals/handleRequest.test.js | 6 +- 16 files changed, 682 insertions(+), 79 deletions(-) create mode 100644 test/diagnostics-channel/404.test.js create mode 100644 test/diagnostics-channel/async-delay-request.test.js create mode 100644 test/diagnostics-channel/async-request.test.js create mode 100644 test/diagnostics-channel/error-before-handler.test.js create mode 100644 test/diagnostics-channel/error-request.test.js create mode 100644 test/diagnostics-channel/error-status.test.js rename test/{diagnostics-channel.test.js => diagnostics-channel/init.test.js} (70%) create mode 100644 test/diagnostics-channel/sync-delay-request.test.js create mode 100644 test/diagnostics-channel/sync-request-reply.test.js create mode 100644 test/diagnostics-channel/sync-request.test.js diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index a9d7170e872..a5eecf55206 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -829,18 +829,17 @@ consider creating a custom [Plugin](./Plugins.md) instead. ## Diagnostics Channel Hooks > **Note:** The `diagnostics_channel` is currently experimental on Node.js, so -> its API is subject to change even in semver-patch releases of Node.js. For -> versions of Node.js supported by Fastify where `diagnostics_channel` is -> unavailable, the hook will use the -> [polyfill](https://www.npmjs.com/package/diagnostics_channel) if it is -> available. Otherwise, this feature will not be present. - -Currently, one -[`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html) publish -event, `'fastify.initialization'`, happens at initialization time. The Fastify -instance is passed into the hook as a property of the object passed in. At this -point, the instance can be interacted with to add hooks, plugins, routes, or any -other sort of modification. +> its API is subject to change even in semver-patch releases of Node.js. As some +> versions of Node.js are supported by Fastify where `diagnostics_channel` is +> unavailable, or with an incomplete feature set, the hook uses the +> [dc-polyfill](https://www.npmjs.com/package/dc-polyfill) package to provide a +> polyfill. + +One [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html) +publish event, `'fastify.initialization'`, happens at initialization time. The +Fastify instance is passed into the hook as a property of the object passed in. +At this point, the instance can be interacted with to add hooks, plugins, +routes, or any other sort of modification. For example, a tracing package might do something like the following (which is, of course, a simplification). This would be in a file loaded in the @@ -849,13 +848,13 @@ tools first" fashion. ```js const tracer = /* retrieved from elsewhere in the package */ -const dc = require('node:diagnostics_channel') +const dc = require('node:diagnostics_channel') // or require('dc-polyfill') const channel = dc.channel('fastify.initialization') const spans = new WeakMap() channel.subscribe(function ({ fastify }) { fastify.addHook('onRequest', (request, reply, done) => { - const span = tracer.startSpan('fastify.request') + const span = tracer.startSpan('fastify.request.handler') spans.set(request, span) done() }) @@ -867,3 +866,38 @@ channel.subscribe(function ({ fastify }) { }) }) ``` + +Five other events are published on a per-request basis following the +[Tracing Channel](https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel) +nomenclature. The list of the channel names and the event they receive is: + +- `tracing:fastify.request.handler:start`: Always fires + - `{ request: Request, reply: Reply, route: { url, method } }` +- `tracing:fastify.request.handler:end`: Always fires + - `{ request: Request, reply: Reply, route: { url, method }, async: Bool }` +- `tracing:fastify.request.handler:asyncStart`: Fires for promise/async handlers + - `{ request: Request, reply: Reply, route: { url, method } }` +- `tracing:fastify.request.handler:asyncEnd`: Fires for promise/async handlers + - `{ request: Request, reply: Reply, route: { url, method } }` +- `tracing:fastify.request.handler:error`: Fires when an error occurs + - `{ request: Request, reply: Reply, route: { url, method }, error: Error }` + +The object instance remains the same for all events associated with a given +request. All payloads include a `request` and `reply` property which are an +instance of Fastify's `Request` and `Reply` instances. They also include a +`route` property which is an object with the matched `url` pattern (e.g. +`/collection/:id`) and the `method` HTTP method (e.g. `GET`). The `:start` and +`:end` events always fire for requests. If a request handler is an `async` +function or one that returns a `Promise` then the `:asyncStart` and `:asyncEnd` +events also fire. Finally, the `:error` event contains an `error` property +associated with the request's failure. + +These events can be received like so: + +```js +const dc = require('node:diagnostics_channel') // or require('dc-polyfill') +const channel = dc.channel('tracing:fastify.request.handler:start') +channel.subscribe((msg) => { + console.log(msg.request, msg.reply) +}) +``` diff --git a/fastify.js b/fastify.js index 6164ad0fff1..299991e9278 100644 --- a/fastify.js +++ b/fastify.js @@ -4,6 +4,7 @@ const VERSION = '5.0.0-alpha.1' const Avvio = require('avvio') const http = require('node:http') +const diagnostics = require('dc-polyfill') let lightMyRequest const { @@ -77,6 +78,8 @@ const { const { buildErrorHandler } = require('./lib/error-handler.js') +const initChannel = diagnostics.channel('fastify.initialization') + function defaultBuildPrettyMeta (route) { // return a shallow copy of route's sanitized context @@ -540,15 +543,8 @@ function fastify (options) { // Delay configuring clientError handler so that it can access fastify state. server.on('clientError', options.clientErrorHandler.bind(fastify)) - try { - const dc = require('node:diagnostics_channel') - const initChannel = dc.channel('fastify.initialization') - if (initChannel.hasSubscribers) { - initChannel.publish({ fastify }) - } - } catch (e) { - // This only happens if `diagnostics_channel` isn't available, i.e. earlier - // versions of Node.js. In that event, we don't care, so ignore the error. + if (initChannel.hasSubscribers) { + initChannel.publish({ fastify }) } // Older nodejs versions may not have asyncDispose diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 45d766a8c13..6b0744b1a00 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -1,14 +1,18 @@ 'use strict' const { bodylessMethods, bodyMethods } = require('./httpMethods') +const diagnostics = require('dc-polyfill') const { validate: validateSchema } = require('./validation') const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') const wrapThenable = require('./wrapThenable') const { kReplyIsError, - kRouteContext + kRouteContext, + kFourOhFourContext } = require('./symbols') +const channels = diagnostics.tracingChannel('fastify.request.handler') + function handleRequest (err, request, reply) { if (reply.sent === true) return if (err != null) { @@ -119,28 +123,62 @@ function validationCompleted (request, reply, validationErr) { function preHandlerCallback (err, request, reply) { if (reply.sent) return - if (err != null) { - reply[kReplyIsError] = true - reply.send(err) - return + const context = request[kRouteContext] + + if (!channels.hasSubscribers || context[kFourOhFourContext] === null) { + preHandlerCallbackInner(err, request, reply) + } else { + const store = { + request, + reply, + async: false, + route: { + url: context.config.url, + method: context.config.method + } + } + channels.start.runStores(store, preHandlerCallbackInner, undefined, err, request, reply, store) } +} - let result +function preHandlerCallbackInner (err, request, reply, store) { + const context = request[kRouteContext] try { - result = request[kRouteContext].handler(request, reply) - } catch (err) { - reply[kReplyIsError] = true - reply.send(err) - return - } + if (err != null) { + reply[kReplyIsError] = true + reply.send(err) + if (store) { + store.error = err + channels.error.publish(store) + } + return + } - if (result !== undefined) { - if (result !== null && typeof result.then === 'function') { - wrapThenable(result, reply) - } else { - reply.send(result) + let result + + try { + result = context.handler(request, reply) + } catch (err) { + if (store) { + store.error = err + channels.error.publish(store) + } + + reply[kReplyIsError] = true + reply.send(err) + return + } + + if (result !== undefined) { + if (result !== null && typeof result.then === 'function') { + wrapThenable(result, reply, store) + } else { + reply.send(result) + } } + } finally { + if (store) channels.end.publish(store) } } diff --git a/lib/wrapThenable.js b/lib/wrapThenable.js index abb75bdd03a..d9283fceab6 100644 --- a/lib/wrapThenable.js +++ b/lib/wrapThenable.js @@ -5,44 +5,68 @@ const { kReplyHijacked } = require('./symbols') -function wrapThenable (thenable, reply) { +const diagnostics = require('dc-polyfill') +const channels = diagnostics.tracingChannel('fastify.request.handler') + +function wrapThenable (thenable, reply, store) { + if (store) store.async = true thenable.then(function (payload) { if (reply[kReplyHijacked] === true) { return } - // this is for async functions that are using reply.send directly - // - // since wrap-thenable will be called when using reply.send directly - // without actual return. the response can be sent already or - // the request may be terminated during the reply. in this situation, - // it require an extra checking of request.aborted to see whether - // the request is killed by client. - if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) { - // we use a try-catch internally to avoid adding a catch to another - // promise, increase promise perf by 10% - try { - reply.send(payload) - } catch (err) { - reply[kReplyIsError] = true - reply.send(err) + if (store) { + channels.asyncStart.publish(store) + } + + try { + // this is for async functions that are using reply.send directly + // + // since wrap-thenable will be called when using reply.send directly + // without actual return. the response can be sent already or + // the request may be terminated during the reply. in this situation, + // it require an extra checking of request.aborted to see whether + // the request is killed by client. + if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) { + // we use a try-catch internally to avoid adding a catch to another + // promise, increase promise perf by 10% + try { + reply.send(payload) + } catch (err) { + reply[kReplyIsError] = true + reply.send(err) + } + } + } finally { + if (store) { + channels.asyncEnd.publish(store) } } }, function (err) { - if (reply.sent === true) { - reply.log.error({ err }, 'Promise errored, but reply.sent = true was set') - return + if (store) { + store.error = err + channels.error.publish(store) // note that error happens before asyncStart + channels.asyncStart.publish(store) } - reply[kReplyIsError] = true - - // try-catch allow to re-throw error in error handler for async handler try { + if (reply.sent === true) { + reply.log.error({ err }, 'Promise errored, but reply.sent = true was set') + return + } + + reply[kReplyIsError] = true + reply.send(err) // The following should not happen /* c8 ignore next 3 */ } catch (err) { + // try-catch allow to re-throw error in error handler for async handler reply.send(err) + } finally { + if (store) { + channels.asyncEnd.publish(store) + } } }) } diff --git a/package.json b/package.json index 8603f4a0bdb..34854b14b6d 100644 --- a/package.json +++ b/package.json @@ -206,6 +206,7 @@ "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", "avvio": "^8.3.0", + "dc-polyfill": "^0.1.6", "fast-json-stringify": "^5.14.1", "find-my-way": "^8.1.0", "light-my-request": "^5.13.0", diff --git a/test/diagnostics-channel/404.test.js b/test/diagnostics-channel/404.test.js new file mode 100644 index 00000000000..5fc2ac2d4e8 --- /dev/null +++ b/test/diagnostics-channel/404.test.js @@ -0,0 +1,57 @@ +'use strict' + +const t = require('tap') +const diagnostics = require('dc-polyfill') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../..') +const { getServerUrl } = require('../helper') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel sync events fire in expected order', t => { + t.plan(9) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.equal(callOrder++, 0) + firstEncounteredMessage = msg + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(callOrder++, 1) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + reply.callNotFound() + } + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 404) + }) + }) +}) diff --git a/test/diagnostics-channel/async-delay-request.test.js b/test/diagnostics-channel/async-delay-request.test.js new file mode 100644 index 00000000000..1e8677722cb --- /dev/null +++ b/test/diagnostics-channel/async-delay-request.test.js @@ -0,0 +1,74 @@ +'use strict' + +const t = require('tap') +const diagnostics = require('dc-polyfill') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../..') +const { getServerUrl } = require('../helper') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel async events fire in expected order', t => { + t.plan(19) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.equal(callOrder++, 0) + firstEncounteredMessage = msg + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.equal(callOrder++, 1) + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(msg, firstEncounteredMessage) + t.equal(msg.async, true) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:asyncStart', (msg) => { + t.equal(callOrder++, 2) + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:asyncEnd', (msg) => { + t.equal(callOrder++, 3) + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: async function (req, reply) { + setImmediate(() => reply.send({ hello: 'world' })) + return reply + } + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) diff --git a/test/diagnostics-channel/async-request.test.js b/test/diagnostics-channel/async-request.test.js new file mode 100644 index 00000000000..9e6000b9b5b --- /dev/null +++ b/test/diagnostics-channel/async-request.test.js @@ -0,0 +1,72 @@ +'use strict' + +const t = require('tap') +const diagnostics = require('dc-polyfill') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../..') +const { getServerUrl } = require('../helper') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel async events fire in expected order', t => { + t.plan(18) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.equal(callOrder++, 0) + firstEncounteredMessage = msg + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.equal(callOrder++, 1) + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:asyncStart', (msg) => { + t.equal(callOrder++, 2) + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:asyncEnd', (msg) => { + t.equal(callOrder++, 3) + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: async function (req, reply) { + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) diff --git a/test/diagnostics-channel/error-before-handler.test.js b/test/diagnostics-channel/error-before-handler.test.js new file mode 100644 index 00000000000..0d59c75e60d --- /dev/null +++ b/test/diagnostics-channel/error-before-handler.test.js @@ -0,0 +1,36 @@ +'use strict' + +const t = require('tap') +const diagnostics = require('dc-polyfill') +const test = t.test +require('../../lib/hooks').onSendHookRunner = function Stub () {} +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') +const symbols = require('../../lib/symbols.js') +const { preHandlerCallback } = require('../../lib/handleRequest')[Symbol.for('internals')] + +test('diagnostics channel handles an error before calling context handler', t => { + t.plan(3) + let callOrder = 0 + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.equal(callOrder++, 0) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.equal(callOrder++, 1) + t.equal(msg.error.message, 'oh no') + }) + + const error = new Error('oh no') + const request = new Request() + const reply = new Reply({}, request) + request[symbols.kRouteContext] = { + config: { + url: '/foo', + method: 'GET' + } + } + + preHandlerCallback(error, request, reply) +}) diff --git a/test/diagnostics-channel/error-request.test.js b/test/diagnostics-channel/error-request.test.js new file mode 100644 index 00000000000..7963e267bde --- /dev/null +++ b/test/diagnostics-channel/error-request.test.js @@ -0,0 +1,61 @@ +'use strict' + +const t = require('tap') +const diagnostics = require('dc-polyfill') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../..') +const { getServerUrl } = require('../helper') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel events report on errors', t => { + t.plan(14) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.equal(callOrder++, 0) + firstEncounteredMessage = msg + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(callOrder++, 2) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.ok(msg.error instanceof Error) + t.equal(callOrder++, 1) + t.equal(msg.error.message, 'borked') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + throw new Error('borked') + } + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 500) + }) + }) +}) diff --git a/test/diagnostics-channel/error-status.test.js b/test/diagnostics-channel/error-status.test.js new file mode 100644 index 00000000000..6047b625f79 --- /dev/null +++ b/test/diagnostics-channel/error-status.test.js @@ -0,0 +1,39 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('../..') +const statusCodes = require('node:http').STATUS_CODES +const diagnostics = require('dc-polyfill') + +test('Error.status property support', t => { + t.plan(4) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + const err = new Error('winter is coming') + err.status = 418 + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.equal(msg.error.message, 'winter is coming') + }) + + fastify.get('/', () => { + return Promise.reject(err) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.error(error) + t.equal(res.statusCode, 418) + t.same( + { + error: statusCodes['418'], + message: err.message, + statusCode: 418 + }, + JSON.parse(res.payload) + ) + }) +}) diff --git a/test/diagnostics-channel.test.js b/test/diagnostics-channel/init.test.js similarity index 70% rename from test/diagnostics-channel.test.js rename to test/diagnostics-channel/init.test.js index dc2ed8b4b40..e2ab45e0a9b 100644 --- a/test/diagnostics-channel.test.js +++ b/test/diagnostics-channel/init.test.js @@ -9,7 +9,7 @@ test('diagnostics_channel when present and subscribers', t => { let fastifyInHook - const dc = { + const diagnostics = { channel (name) { t.equal(name, 'fastify.initialization') return { @@ -23,8 +23,8 @@ test('diagnostics_channel when present and subscribers', t => { '@noCallThru': true } - const fastify = proxyquire('../fastify', { - 'node:diagnostics_channel': dc + const fastify = proxyquire('../../fastify', { + 'dc-polyfill': diagnostics })() t.equal(fastifyInHook, fastify) }) @@ -32,7 +32,7 @@ test('diagnostics_channel when present and subscribers', t => { test('diagnostics_channel when present and no subscribers', t => { t.plan(1) - const dc = { + const diagnostics = { channel (name) { t.equal(name, 'fastify.initialization') return { @@ -45,17 +45,7 @@ test('diagnostics_channel when present and no subscribers', t => { '@noCallThru': true } - proxyquire('../fastify', { - 'node:diagnostics_channel': dc + proxyquire('../../fastify', { + 'dc-polyfill': diagnostics })() }) - -test('diagnostics_channel when not present', t => { - t.plan(1) - - t.doesNotThrow(() => { - proxyquire('../fastify', { - 'node:diagnostics_channel': null - })() - }) -}) diff --git a/test/diagnostics-channel/sync-delay-request.test.js b/test/diagnostics-channel/sync-delay-request.test.js new file mode 100644 index 00000000000..29e7442180a --- /dev/null +++ b/test/diagnostics-channel/sync-delay-request.test.js @@ -0,0 +1,58 @@ +'use strict' + +const t = require('tap') +const diagnostics = require('dc-polyfill') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../..') +const { getServerUrl } = require('../helper') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel sync events fire in expected order', t => { + t.plan(10) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.equal(callOrder++, 0) + firstEncounteredMessage = msg + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(callOrder++, 1) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + setImmediate(() => reply.send({ hello: 'world' })) + } + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) diff --git a/test/diagnostics-channel/sync-request-reply.test.js b/test/diagnostics-channel/sync-request-reply.test.js new file mode 100644 index 00000000000..66efbad0c34 --- /dev/null +++ b/test/diagnostics-channel/sync-request-reply.test.js @@ -0,0 +1,58 @@ +'use strict' + +const t = require('tap') +const diagnostics = require('dc-polyfill') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../..') +const { getServerUrl } = require('../helper') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel sync events fire in expected order', t => { + t.plan(10) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.equal(callOrder++, 0) + firstEncounteredMessage = msg + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(callOrder++, 1) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) diff --git a/test/diagnostics-channel/sync-request.test.js b/test/diagnostics-channel/sync-request.test.js new file mode 100644 index 00000000000..749177464e8 --- /dev/null +++ b/test/diagnostics-channel/sync-request.test.js @@ -0,0 +1,61 @@ +'use strict' + +const t = require('tap') +const diagnostics = require('dc-polyfill') +const test = t.test +const sget = require('simple-get').concat +const Fastify = require('../..') +const { getServerUrl } = require('../helper') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel sync events fire in expected order', t => { + t.plan(13) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.equal(callOrder++, 0) + firstEncounteredMessage = msg + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.ok(msg.route) + t.equal(msg.route.url, '/:id') + t.equal(msg.route.method, 'GET') + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.ok(msg.request instanceof Request) + t.ok(msg.reply instanceof Reply) + t.equal(callOrder++, 1) + t.equal(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/:id', + handler: function (req, reply) { + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) t.error(err) + + t.teardown(() => { fastify.close() }) + + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/7' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) + }) +}) diff --git a/test/internals/handleRequest.test.js b/test/internals/handleRequest.test.js index f423d9df9d4..1c0e742f2f8 100644 --- a/test/internals/handleRequest.test.js +++ b/test/internals/handleRequest.test.js @@ -94,7 +94,11 @@ test('handler function - reply', t => { preValidation: [], preHandler: [], onSend: [], - onError: [] + onError: [], + config: { + url: '', + method: '' + } } buildSchema(context, schemaValidator) internals.handler({ [kRouteContext]: context }, new Reply(res, { [kRouteContext]: context })) From 2d15107a47bce8a65f3c40303844bd6a53a73991 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:39:58 +0000 Subject: [PATCH 0676/1295] chore: Bump pnpm/action-setup from 3 to 4 (#5492) Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 3 to 4. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v3...v4) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/integration-alternative-runtimes.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index 1c69bb7e8ee..588a045dc2e 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -43,7 +43,7 @@ jobs: nsolid-version: ${{ matrix.nsolid-version }} - name: Install Pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@v4 with: version: ${{ matrix.pnpm-version }} diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 21b5ba7b785..52d11ef7ad3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -38,7 +38,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install Pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@v4 with: version: ${{ matrix.pnpm-version }} diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index f44dc88a7d4..25d77e54f88 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -30,7 +30,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install with pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@v4 with: version: ${{ matrix.pnpm-version }} From a00f3ca91d4bea348e3dd71dfeb2edf017285aaf Mon Sep 17 00:00:00 2001 From: Cangit Date: Mon, 3 Jun 2024 01:09:39 +0200 Subject: [PATCH 0677/1295] chore: remove unnecessary eslint override (#5493) in decorate.js --- lib/decorate.js | 5 +---- test/internals/decorator.test.js | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/decorate.js b/lib/decorate.js index 1ff41276b78..26a3b539d79 100644 --- a/lib/decorate.js +++ b/lib/decorate.js @@ -1,7 +1,5 @@ 'use strict' -/* eslint no-prototype-builtins: 0 */ - const { kReply, kRequest, @@ -101,8 +99,7 @@ function checkDependencies (instance, name, deps) { throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name) } - // eslint-disable-next-line no-var - for (var i = 0; i !== deps.length; ++i) { + for (let i = 0; i !== deps.length; ++i) { if (!checkExistence(instance, deps[i])) { throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i]) } diff --git a/test/internals/decorator.test.js b/test/internals/decorator.test.js index db0b207667e..8a6cd1433d7 100644 --- a/test/internals/decorator.test.js +++ b/test/internals/decorator.test.js @@ -1,7 +1,5 @@ 'use strict' -/* eslint no-prototype-builtins: 0 */ - const t = require('tap') const test = t.test const decorator = require('../../lib/decorate') From a9a55682d54e5597bb0cf65187b1f6b3ac9e71a0 Mon Sep 17 00:00:00 2001 From: Alessio Napolitano <121714208+alenap93@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:56:45 +0200 Subject: [PATCH 0678/1295] docs(ecosystem): Add fastify-kysely plugin (#5475) * docs(ecosystem): Add fastify-kysely plugin * fix alphabetical order --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 7f1acf9ecc0..afc84ba4297 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -447,6 +447,8 @@ section. middlewares into Fastify plugins - [`fastify-kubernetes`](https://github.com/greguz/fastify-kubernetes) Fastify Kubernetes client plugin. +- [`fastify-kysely`](https://github.com/alenap93/fastify-kysely) Fastify + plugin for supporting Kysely type-safe query builder. - [`fastify-language-parser`](https://github.com/lependu/fastify-language-parser) Fastify plugin to parse request language. - [`fastify-lcache`](https://github.com/denbon05/fastify-lcache) From 4be64681b4bd78c4c1ae011b18b52640d1e54b34 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:41:37 +0800 Subject: [PATCH 0679/1295] chore: update tap@19 (#5471) * chore: update tap@18 * chore: tap@19 * chore: remove c8 * chore: remove ignore --- .c8rc.json | 8 --- .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- .taprc | 12 ++-- build/build-error-serializer.js | 5 +- build/build-validation.js | 3 +- lib/configValidator.js | 3 +- lib/error-serializer.js | 96 ++++++++++++++++++++++++------ package.json | 17 +++--- test/internals/reply.test.js | 4 +- 10 files changed, 101 insertions(+), 51 deletions(-) delete mode 100644 .c8rc.json diff --git a/.c8rc.json b/.c8rc.json deleted file mode 100644 index abfd9f3b0b0..00000000000 --- a/.c8rc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "exclude": [ - "lib/configValidator.js", - "lib/error-serializer.js", - "build/build-error-serializer.js", - "test/*" - ] -} diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 255131a7f7c..a13c3655bab 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -33,7 +33,7 @@ jobs: if: ${{ success() }} with: name: coverage-report-nix - path: ./coverage/lcov-report/ + path: ./.tap/report/ # Here, we verify the coverage thresholds so that this workflow can pass # or fail and stop further workflows if this one fails. diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 98b3af4f6a5..a64a0e26cb2 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -33,7 +33,7 @@ jobs: if: ${{ success() }} with: name: coverage-report-win - path: ./coverage/lcov-report/ + path: ./.tap/report/ # Here, we verify the coverage thresholds so that this workflow can pass # or fail and stop further workflows if this one fails. diff --git a/.taprc b/.taprc index 570f42180f0..fac1cebf508 100644 --- a/.taprc +++ b/.taprc @@ -1,11 +1,7 @@ -ts: false -jsx: false -flow: false -# the coverage is performed by c8 -check-coverage: false -coverage: false -node-arg: --allow-natives-syntax +# vim: set filetype=yaml : +node-arg: + - '--allow-natives-syntax' -files: +include: - 'test/**/*.test.js' - 'test/**/*.test.mjs' diff --git a/build/build-error-serializer.js b/build/build-error-serializer.js index 95f8c3fd16e..d7a0cd2e3c7 100644 --- a/build/build-error-serializer.js +++ b/build/build-error-serializer.js @@ -18,10 +18,12 @@ const code = FJS({ const file = path.join(__dirname, '..', 'lib', 'error-serializer.js') const moduleCode = `// This file is autogenerated by build/build-error-serializer.js, do not edit -/* istanbul ignore file */ +/* c8 ignore start */ ${code} +/* c8 ignore stop */ ` +/* c8 ignore start */ if (require.main === module) { fs.writeFileSync(file, moduleCode) console.log(`Saved ${file} file successfully`) @@ -30,3 +32,4 @@ if (require.main === module) { code: moduleCode } } +/* c8 ignore stop */ diff --git a/build/build-validation.js b/build/build-validation.js index 38d312faa84..4631e7b9e9a 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -9,10 +9,11 @@ const factory = AjvStandaloneCompiler({ readMode: false, storeFunction (routeOpts, schemaValidationCode) { const moduleCode = `// This file is autogenerated by ${__filename.replace(__dirname, 'build')}, do not edit -/* istanbul ignore file */ +/* c8 ignore start */ ${schemaValidationCode} module.exports.defaultInitOptions = ${JSON.stringify(defaultInitOptions)} +/* c8 ignore stop */ ` const file = path.join(__dirname, '..', 'lib', 'configValidator.js') diff --git a/lib/configValidator.js b/lib/configValidator.js index 5235b416a2d..77394823c7b 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -1,5 +1,5 @@ // This file is autogenerated by build/build-validation.js, do not edit -/* istanbul ignore file */ +/* c8 ignore start */ "use strict"; module.exports = validate10; module.exports.default = validate10; @@ -1153,3 +1153,4 @@ return errors === 0; module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false} +/* c8 ignore stop */ diff --git a/lib/error-serializer.js b/lib/error-serializer.js index b6ba2db79b0..71fb87b9ce1 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -1,11 +1,9 @@ // This file is autogenerated by build/build-error-serializer.js, do not edit -/* istanbul ignore file */ +/* c8 ignore start */ 'use strict' - const { dependencies } = require('fast-json-stringify/lib/standalone') - - const { Serializer, Validator } = dependencies + const Serializer = require('fast-json-stringify/lib/serializer') const serializerState = {"mode":"standalone"} const serializer = Serializer.restoreFromState(serializerState) @@ -15,6 +13,18 @@ module.exports = function anonymous(validator,serializer ) { + const JSON_STR_BEGIN_OBJECT = '{' + const JSON_STR_END_OBJECT = '}' + const JSON_STR_BEGIN_ARRAY = '[' + const JSON_STR_END_ARRAY = ']' + const JSON_STR_COMMA = ',' + const JSON_STR_COLONS = ':' + const JSON_STR_QUOTE = '"' + const JSON_STR_EMPTY_OBJECT = JSON_STR_BEGIN_OBJECT + JSON_STR_END_OBJECT + const JSON_STR_EMPTY_ARRAY = JSON_STR_BEGIN_ARRAY + JSON_STR_END_ARRAY + const JSON_STR_EMPTY_STRING = JSON_STR_QUOTE + JSON_STR_QUOTE + const JSON_STR_NULL = 'null' + // # @@ -23,36 +33,83 @@ ? input.toJSON() : input - if (obj === null) return '{}' + if (obj === null) return JSON_STR_EMPTY_OBJECT - let json = '{' + let value +let json = JSON_STR_BEGIN_OBJECT let addComma = false - if (obj["statusCode"] !== undefined) { - !addComma && (addComma = true) || (json += ',') + value = obj["statusCode"] + if (value !== undefined) { + !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"statusCode\":" - json += serializer.asNumber(obj["statusCode"]) + json += serializer.asNumber(value) } - if (obj["code"] !== undefined) { - !addComma && (addComma = true) || (json += ',') + value = obj["code"] + if (value !== undefined) { + !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"code\":" - json += serializer.asString(obj["code"]) + + if (typeof value !== 'string') { + if (value === null) { + json += JSON_STR_EMPTY_STRING + } else if (value instanceof Date) { + json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE + } else if (value instanceof RegExp) { + json += serializer.asString(value.source) + } else { + json += serializer.asString(value.toString()) + } + } else { + json += serializer.asString(value) + } + } - if (obj["error"] !== undefined) { - !addComma && (addComma = true) || (json += ',') + value = obj["error"] + if (value !== undefined) { + !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"error\":" - json += serializer.asString(obj["error"]) + + if (typeof value !== 'string') { + if (value === null) { + json += JSON_STR_EMPTY_STRING + } else if (value instanceof Date) { + json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE + } else if (value instanceof RegExp) { + json += serializer.asString(value.source) + } else { + json += serializer.asString(value.toString()) + } + } else { + json += serializer.asString(value) + } + } - if (obj["message"] !== undefined) { - !addComma && (addComma = true) || (json += ',') + value = obj["message"] + if (value !== undefined) { + !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"message\":" - json += serializer.asString(obj["message"]) + + if (typeof value !== 'string') { + if (value === null) { + json += JSON_STR_EMPTY_STRING + } else if (value instanceof Date) { + json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE + } else if (value instanceof RegExp) { + json += serializer.asString(value.source) + } else { + json += serializer.asString(value.toString()) + } + } else { + json += serializer.asString(value) + } + } - return json + '}' + return json + JSON_STR_END_OBJECT } @@ -60,3 +117,4 @@ let addComma = false return main }(validator, serializer) +/* c8 ignore stop */ diff --git a/package.json b/package.json index 34854b14b6d..dbc02ada41c 100644 --- a/package.json +++ b/package.json @@ -11,24 +11,24 @@ "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "coverage": "npm run unit -- --coverage-report=html", - "coverage:ci": "c8 --reporter=lcov tap --coverage-report=html --no-browser --no-check-coverage", - "coverage:ci-check-coverage": "c8 check-coverage --branches 100 --functions 100 --lines 100 --statements 100", + "coverage:ci": "tap --coverage-report=html --coverage-report=lcov --allow-incomplete-coverage", + "coverage:ci-check-coverage": "tap replay", "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown", "lint:fix": "standard --fix && npm run lint:typescript:fix", "lint:markdown": "markdownlint-cli2", "lint:standard": "standard | snazzy", "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", "lint:typescript:fix": "npm run lint:typescript -- --fix", - "prepublishOnly": "cross-env PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity", + "prepublishOnly": "cross-env PREPUBLISH=true tap --allow-incomplete-coverage test/build/**.test.js && npm run test:validator:integrity", "test": "npm run lint && npm run unit && npm run test:typescript", - "test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript", + "test:ci": "npm run unit -- --coverage-report=lcovonly && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", "test:typescript": "tsc test/types/import.ts && tsd", - "test:watch": "npm run unit -- --watch --cov --no-coverage-report --reporter=terse", - "unit": "c8 tap", + "test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse", + "unit": "tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", - "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap", + "unit:report": "tap --coverage-report=html --coverage-report=cobertura | tee out.tap", "citgm": "tap --jobs=1 --timeout=120" }, "repository": { @@ -167,7 +167,6 @@ "ajv-merge-patch": "^5.0.1", "autocannon": "^7.15.0", "branch-comparer": "^1.1.0", - "c8": "^9.1.0", "concurrently": "^8.2.2", "cross-env": "^7.0.3", "eslint": "^8.57.0", @@ -193,7 +192,7 @@ "snazzy": "^9.0.0", "split2": "^4.2.0", "standard": "^17.1.0", - "tap": "^16.3.9", + "tap": "^19.0.0", "tsd": "^0.31.0", "typescript": "^5.4.5", "undici": "^6.13.0", diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index b06a8e2077a..6df7e546635 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -1415,9 +1415,9 @@ test('.statusCode is getter and setter', t => { const fastify = Fastify() fastify.get('/', function (req, reply) { - t.ok(reply.statusCode, 200, 'default status value') + t.equal(reply.statusCode, 200, 'default status value') reply.statusCode = 418 - t.ok(reply.statusCode, 418) + t.equal(reply.statusCode, 418) reply.send() }) From c44ba740d21900cef3b5708481f8719b98793923 Mon Sep 17 00:00:00 2001 From: codershiba <155646804+codershiba@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:45:19 +0530 Subject: [PATCH 0680/1295] chore: Remove `reply.getResponseTime()` (#5490) Signed-off-by: codershiba <155646804+codershiba@users.noreply.github.com> --- docs/Reference/Reply.md | 18 -------- docs/Reference/Warnings.md | 2 - lib/reply.js | 9 +--- lib/warnings.js | 7 ---- test/internals/reply.test.js | 80 ++---------------------------------- test/types/reply.test-d.ts | 2 - types/reply.d.ts | 4 -- 7 files changed, 5 insertions(+), 117 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 33445554c35..9d82d134059 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -92,8 +92,6 @@ since the request was received by Fastify. from Node core. - `.log` - The logger instance of the incoming request. - `.request` - The incoming request. -- `.getResponseTime()` - Deprecated, returns the amount of time passed -since the request was received by Fastify. - `.context` - Deprecated, access the [Request's context](./Request.md) property. ```js @@ -366,22 +364,6 @@ hook specified in [`setNotFoundHandler`](./Server.md#set-not-found-handler). reply.callNotFound() ``` -### .getResponseTime() - - -Invokes the custom response time getter to calculate the amount of time passed -since the request was received by Fastify. - -Note that unless this function is called in the [`onResponse` -hook](./Hooks.md#onresponse) it will always return `0`. - -```js -const milliseconds = reply.getResponseTime() -``` - -*Note: This method is deprecated and will be removed in `fastify@5`. -Use the [.elapsedTime](#elapsedtime) property instead.* - ### .type(contentType) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index ca55f1c4139..72c2a1baccb 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -20,7 +20,6 @@ - [FSTDEP017](#FSTDEP017) - [FSTDEP018](#FSTDEP018) - [FSTDEP019](#FSTDEP019) - - [FSTDEP020](#FSTDEP020) - [FSTDEP021](#FSTDEP021) @@ -84,5 +83,4 @@ Deprecation codes are further supported by the Node.js CLI options: | FSTDEP017 | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) | | FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | | FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | -| FSTDEP020 | You are using the deprecated `reply.getReponseTime()` method. | Use the `reply.elapsedTime` property instead. | [#5263](https://github.com/fastify/fastify/pull/5263) | | FSTDEP021 | The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. | [#5483](https://github.com/fastify/fastify/pull/5483) | diff --git a/lib/reply.js b/lib/reply.js index 4291970b13d..1b76b828b92 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -55,7 +55,7 @@ const { FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') -const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020, FSTDEP021 } = require('./warnings') +const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP021 } = require('./warnings') const toString = Object.prototype.toString @@ -482,13 +482,6 @@ Reply.prototype.callNotFound = function () { return this } -// TODO: should be removed in fastify@5 -Reply.prototype.getResponseTime = function () { - FSTDEP020() - - return this.elapsedTime -} - // Make reply a thenable, so it could be used with async/await. // See // - https://github.com/fastify/fastify/issues/1864 for the discussions diff --git a/lib/warnings.js b/lib/warnings.js index 51dc87e86d4..e4b606ac732 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -16,7 +16,6 @@ const { createDeprecation, createWarning } = require('process-warning') * - FSTDEP017 * - FSTDEP018 * - FSTDEP019 - * - FSTDEP020 * - FSTDEP021 * - FSTWRN001 * - FSTSEC001 @@ -82,11 +81,6 @@ const FSTDEP019 = createDeprecation({ message: 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.' }) -const FSTDEP020 = createDeprecation({ - code: 'FSTDEP020', - message: 'You are using the deprecated "reply.getResponseTime()" method. Use the "reply.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.' -}) - const FSTDEP021 = createDeprecation({ code: 'FSTDEP021', message: 'The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`' @@ -119,7 +113,6 @@ module.exports = { FSTDEP017, FSTDEP018, FSTDEP019, - FSTDEP020, FSTDEP021, FSTWRN001, FSTSEC001 diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 6df7e546635..3996d8a7659 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -19,7 +19,7 @@ const { } = require('../../lib/symbols') const fs = require('node:fs') const path = require('node:path') -const { FSTDEP010, FSTDEP019, FSTDEP020, FSTDEP021 } = require('../../lib/warnings') +const { FSTDEP010, FSTDEP019, FSTDEP021 } = require('../../lib/warnings') const agent = new http.Agent({ keepAlive: false }) @@ -36,7 +36,7 @@ const doGet = function (url) { } test('Once called, Reply should return an object with methods', t => { - t.plan(16) + t.plan(15) const response = { res: 'res' } const context = { config: { onSend: [] }, schema: {} } const request = { [kRouteContext]: context, [kPublicRouteContext]: { config: context.config, schema: context.schema } } @@ -49,7 +49,6 @@ test('Once called, Reply should return an object with methods', t => { t.equal(typeof reply.status, 'function') t.equal(typeof reply.header, 'function') t.equal(typeof reply.serialize, 'function') - t.equal(typeof reply.getResponseTime, 'function') t.equal(typeof reply[kReplyHeaders], 'object') t.same(reply.raw, response) t.equal(reply[kRouteContext], context) @@ -1572,82 +1571,11 @@ test('should not throw error when attempting to set reply.sent if the underlinin }) }) -test('reply.getResponseTime() should return 0 before the timer is initialised on the reply by setting up response listeners', t => { +test('reply.elapsedTime should return 0 before the timer is initialised on the reply by setting up response listeners', t => { t.plan(1) const response = { statusCode: 200 } const reply = new Reply(response, null) - t.equal(reply.getResponseTime(), 0) -}) - -test('reply.getResponseTime() should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', t => { - t.plan(1) - const fastify = Fastify() - fastify.route({ - method: 'GET', - url: '/', - handler: (req, reply) => { - reply.send('hello world') - } - }) - - fastify.addHook('onResponse', (req, reply) => { - t.ok(reply.getResponseTime() > 0) - t.end() - }) - - fastify.inject({ method: 'GET', url: '/' }) -}) - -test('should emit deprecation warning when trying to use reply.getResponseTime() and should return the time since a request started while inflight', t => { - t.plan(5) - const fastify = Fastify() - fastify.route({ - method: 'GET', - url: '/', - handler: (req, reply) => { - reply.send('hello world') - } - }) - - process.removeAllListeners('warning') - process.on('warning', onWarning) - function onWarning (warning) { - t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, FSTDEP020.code) - } - - fastify.addHook('preValidation', (req, reply, done) => { - t.equal(reply.getResponseTime(), reply.getResponseTime()) - done() - }) - - fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.pass() - - process.removeListener('warning', onWarning) - }) - - FSTDEP020.emitted = false -}) - -test('reply.getResponseTime() should return the same value after a request is finished', t => { - t.plan(1) - const fastify = Fastify() - fastify.route({ - method: 'GET', - url: '/', - handler: (req, reply) => { - reply.send('hello world') - } - }) - - fastify.addHook('onResponse', (req, reply) => { - t.equal(reply.getResponseTime(), reply.getResponseTime()) - t.end() - }) - - fastify.inject({ method: 'GET', url: '/' }) + t.equal(reply.elapsedTime, 0) }) test('reply.elapsedTime should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', t => { diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 505dc45f0c9..b6dea2f4898 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -33,8 +33,6 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<{(statusCode: number, url: string): FastifyReply;(url: string, statusCode?: number): FastifyReply;}>(reply.redirect) expectType<() => FastifyReply>(reply.hijack) expectType<() => void>(reply.callNotFound) - // Test reply.getResponseTime() deprecation - expectDeprecated(reply.getResponseTime) expectType<(contentType: string) => FastifyReply>(reply.type) expectType<(fn: (payload: any) => string) => FastifyReply>(reply.serializer) expectType<(payload: any) => string | ArrayBuffer | Buffer>(reply.serialize) diff --git a/types/reply.d.ts b/types/reply.d.ts index 2dfa52f894e..99e141ea4cf 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -65,10 +65,6 @@ export interface FastifyReply< writeEarlyHints(hints: Record, callback?: () => void): void; hijack(): FastifyReply; callNotFound(): void; - /** - * @deprecated Use the Reply#elapsedTime property instead - */ - getResponseTime(): number; type(contentType: string): FastifyReply; serializer(fn: (payload: any) => string): FastifyReply; serialize(payload: any): string | ArrayBuffer | Buffer; From c33cc90802b46570139430999cf290b85feb1856 Mon Sep 17 00:00:00 2001 From: Cangit Date: Fri, 7 Jun 2024 05:55:04 +0200 Subject: [PATCH 0681/1295] chore: remove unused test (#5496) * chore: remove unused (and failing) test * add version check to test * Update test/close-pipelining.test.js Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> Signed-off-by: Cangit * lint: remove semver pkg --------- Signed-off-by: Cangit Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --- test/close-pipelining.test.js | 36 ++--------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index 1c9451314b3..ecac0ab51ed 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -4,7 +4,6 @@ const t = require('tap') const test = t.test const Fastify = require('..') const { Client } = require('undici') -const semver = require('semver') test('Should return 503 while closing - pipelining', async t => { const fastify = Fastify({ @@ -36,39 +35,8 @@ test('Should return 503 while closing - pipelining', async t => { await instance.close() }) -const isNodeVersionGte1819 = semver.gte(process.version, '18.19.0') -test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v18.19.x', { skip: isNodeVersionGte1819 }, async t => { - const fastify = Fastify({ - return503OnClosing: false, - forceCloseConnections: false - }) - - fastify.get('/', (req, reply) => { - fastify.close() - reply.send({ hello: 'world' }) - }) - - await fastify.listen({ port: 0 }) - - const instance = new Client('http://localhost:' + fastify.server.address().port, { - pipelining: 2 - }) - - const codes = [200, 200, 200] - const responses = await Promise.all([ - instance.request({ path: '/', method: 'GET' }), - instance.request({ path: '/', method: 'GET' }), - instance.request({ path: '/', method: 'GET' }) - ]) - const actual = responses.map(r => r.statusCode) - - t.same(actual, codes) - - await instance.close() -}) - -test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip Node < v18.19.x', { skip: !isNodeVersionGte1819 }, async t => { - // Since Node v18, we will always invoke server.closeIdleConnections() +test('Should close the socket abruptly - pipelining - return503OnClosing: false', async t => { + // Since Node v20, we will always invoke server.closeIdleConnections() // therefore our socket will be closed const fastify = Fastify({ return503OnClosing: false, From dda569960ef2d0961a887ead0e63f4e8671a920e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Dewitte?= Date: Fri, 7 Jun 2024 15:03:08 +0200 Subject: [PATCH 0682/1295] chore: readyListener can be async on server.ready() (#5501) * type: readyListener can be async on server.ready() * fix lint warning var -> const --- test/types/instance.test-d.ts | 6 ++++++ types/instance.d.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 75d32b0040e..60e3f053cdd 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -237,6 +237,12 @@ expectAssignable(server.ready()) expectAssignable(server.ready((err) => { expectType(err) })) +expectAssignable(server.ready(async (err) => { + expectType(err) +})) +expectAssignable[0]>(async (err) => { + expectType(err) +}) expectAssignable(server.routing({} as RawRequestDefaultExpression, {} as RawReplyDefaultExpression)) diff --git a/types/instance.d.ts b/types/instance.d.ts index 68d66a84f55..93e12229d92 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -168,7 +168,7 @@ export interface FastifyInstance< listen(callback: (err: Error | null, address: string) => void): void; ready(): FastifyInstance & PromiseLike; - ready(readyListener: (err: Error | null) => void): FastifyInstance; + ready(readyListener: (err: Error | null) => void | Promise): FastifyInstance; register: FastifyRegister & PromiseLike>; From e277d9e2b8bc486124af818a0b513587d6c4d46f Mon Sep 17 00:00:00 2001 From: Cangit Date: Sun, 9 Jun 2024 10:26:57 +0200 Subject: [PATCH 0683/1295] chore: test deprecation cleanup (#5510) --- lib/request.js | 6 +---- package.json | 1 - test/404s.test.js | 20 ++++++--------- test/close.test.js | 42 +------------------------------ test/decorator.test.js | 2 -- test/esm/index.test.js | 14 ++--------- test/inject.test.js | 3 +-- test/maxRequestsPerSocket.test.js | 7 ++---- test/plugin.1.test.js | 6 ++--- test/plugin.2.test.js | 2 -- test/plugin.3.test.js | 2 -- test/plugin.4.test.js | 2 -- test/register.test.js | 6 ++--- test/schema-special-usage.test.js | 2 +- test/server.test.js | 3 +-- test/stream.4.test.js | 42 ------------------------------- test/web-api.test.js | 6 ----- 17 files changed, 21 insertions(+), 145 deletions(-) diff --git a/lib/request.js b/lib/request.js index 5a10521baab..0d966094b0f 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,7 +1,6 @@ 'use strict' const proxyAddr = require('proxy-addr') -const semver = require('semver') const { FSTDEP005, FSTDEP012, @@ -239,10 +238,7 @@ Object.defineProperties(Request.prototype, { }, connection: { get () { - /* istanbul ignore next */ - if (semver.gte(process.versions.node, '13.0.0')) { - FSTDEP005() - } + FSTDEP005() return this.raw.connection } }, diff --git a/package.json b/package.json index dbc02ada41c..31d46a0ab66 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,6 @@ "fast-json-body": "^1.1.0", "fastify-plugin": "^4.5.1", "fluent-json-schema": "^4.2.1", - "form-data": "^4.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", "joi": "^17.12.3", diff --git a/test/404s.test.js b/test/404s.test.js index 362c25379ec..8ec7789ace7 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -6,7 +6,6 @@ const fp = require('fastify-plugin') const sget = require('simple-get').concat const errors = require('http-errors') const split = require('split2') -const FormData = require('form-data') const Fastify = require('..') const { getServerUrl } = require('./helper') @@ -68,21 +67,18 @@ test('default 404', t => { }) }) - test('using post method and multipart/formdata', t => { + test('using post method and multipart/formdata', async t => { t.plan(3) - const form = FormData() - form.append('test-field', 'just some field') + const form = new FormData() + form.set('test-field', 'just some field') - sget({ + const response = await fetch(getServerUrl(fastify) + '/notSupported', { method: 'POST', - url: getServerUrl(fastify) + '/notSupported', - body: form, - json: false - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') + body: form }) + t.equal(response.status, 404) + t.equal(response.statusText, 'Not Found') + t.equal(response.headers.get('content-type'), 'application/json; charset=utf-8') }) }) }) diff --git a/test/close.test.js b/test/close.test.js index 03817d6b1a6..856d655f0da 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -5,7 +5,6 @@ const http = require('node:http') const { test } = require('tap') const Fastify = require('..') const { Client } = require('undici') -const semver = require('semver') const split = require('split2') const { sleep } = require('./helper') @@ -204,46 +203,7 @@ test('Should return error while closing (callback) - injection', t => { }) }) -const isNodeVersionGte1819 = semver.gte(process.version, '18.19.0') -test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node >= v18.19.x', { skip: isNodeVersionGte1819 }, t => { - const fastify = Fastify({ - return503OnClosing: false, - forceCloseConnections: false - }) - - fastify.get('/', (req, reply) => { - fastify.close() - reply.send({ hello: 'world' }) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - - const port = fastify.server.address().port - const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') - - client.once('data', data => { - t.match(data.toString(), /Connection:\s*keep-alive/i) - t.match(data.toString(), /200 OK/i) - - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') - - client.once('data', data => { - t.match(data.toString(), /Connection:\s*close/i) - t.match(data.toString(), /200 OK/i) - - // Test that fastify closes the TCP connection - client.once('close', () => { - t.end() - }) - }) - }) - }) - }) -}) - -test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v18.19.x', { skip: !isNodeVersionGte1819 }, t => { +test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false', t => { t.plan(4) const fastify = Fastify({ return503OnClosing: false, diff --git a/test/decorator.test.js b/test/decorator.test.js index 50ff2809d6c..f32ae1b4823 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -1,7 +1,5 @@ 'use strict' -/* eslint no-prototype-builtins: 0 */ - const t = require('tap') const test = t.test const Fastify = require('..') diff --git a/test/esm/index.test.js b/test/esm/index.test.js index ef1ae056e5a..f1cf2a72c53 100644 --- a/test/esm/index.test.js +++ b/test/esm/index.test.js @@ -1,18 +1,8 @@ 'use strict' -const t = require('tap') -const semver = require('semver') - -if (semver.lt(process.versions.node, '14.13.0')) { - t.skip('Skip named exports because Node version < 14.13.0') -} else { - // Node v8 throw a `SyntaxError: Unexpected token import` - // even if this branch is never touch in the code, - // by using `eval` we can avoid this issue. - // eslint-disable-next-line - new Function('module', 'return import(module)')('./named-exports.mjs').catch((err) => { +import('./named-exports.mjs') + .catch(err => { process.nextTick(() => { throw err }) }) -} diff --git a/test/inject.test.js b/test/inject.test.js index 1158d8ecb7e..e7e66afc7eb 100644 --- a/test/inject.test.js +++ b/test/inject.test.js @@ -5,7 +5,6 @@ const test = t.test const Stream = require('node:stream') const util = require('node:util') const Fastify = require('..') -const FormData = require('form-data') const { Readable } = require('node:stream') test('inject should exist', t => { @@ -343,7 +342,7 @@ test('inject a multipart request using form-body', t => { }) const form = new FormData() - form.append('my_field', 'my value') + form.set('my_field', 'my value') fastify.inject({ method: 'POST', diff --git a/test/maxRequestsPerSocket.test.js b/test/maxRequestsPerSocket.test.js index ea98e4a619e..a345067f4e7 100644 --- a/test/maxRequestsPerSocket.test.js +++ b/test/maxRequestsPerSocket.test.js @@ -2,12 +2,9 @@ const net = require('node:net') const { test } = require('tap') -const semver = require('semver') const Fastify = require('../fastify') -const skip = semver.lt(process.versions.node, '16.10.0') - -test('maxRequestsPerSocket on node version >= 16.10.0', { skip }, t => { +test('maxRequestsPerSocket', t => { t.plan(8) const fastify = Fastify({ maxRequestsPerSocket: 2 }) @@ -48,7 +45,7 @@ test('maxRequestsPerSocket on node version >= 16.10.0', { skip }, t => { }) }) -test('maxRequestsPerSocket zero should behave same as null', { skip }, t => { +test('maxRequestsPerSocket zero should behave same as null', t => { t.plan(10) const fastify = Fastify({ maxRequestsPerSocket: 0 }) diff --git a/test/plugin.1.test.js b/test/plugin.1.test.js index ffd39c4763c..d537fb1e00d 100644 --- a/test/plugin.1.test.js +++ b/test/plugin.1.test.js @@ -1,7 +1,5 @@ 'use strict' -/* eslint no-prototype-builtins: 0 */ - const t = require('tap') const test = t.test const Fastify = require('../fastify') @@ -117,8 +115,8 @@ test('fastify.register with fastify-plugin should provide access to external fas instance.register((i, o, n) => n(), p => { t.notOk(p === instance || p === fastify) - t.ok(instance.isPrototypeOf(p)) - t.ok(fastify.isPrototypeOf(p)) + t.ok(Object.prototype.isPrototypeOf.call(instance, p)) + t.ok(Object.prototype.isPrototypeOf.call(fastify, p)) t.ok(p.global) }) diff --git a/test/plugin.2.test.js b/test/plugin.2.test.js index 1fad6c29b92..1bfbccc4720 100644 --- a/test/plugin.2.test.js +++ b/test/plugin.2.test.js @@ -1,7 +1,5 @@ 'use strict' -/* eslint no-prototype-builtins: 0 */ - const t = require('tap') const test = t.test const Fastify = require('../fastify') diff --git a/test/plugin.3.test.js b/test/plugin.3.test.js index 71cf8a7b088..3218e1359e9 100644 --- a/test/plugin.3.test.js +++ b/test/plugin.3.test.js @@ -1,7 +1,5 @@ 'use strict' -/* eslint no-prototype-builtins: 0 */ - const t = require('tap') const test = t.test const Fastify = require('../fastify') diff --git a/test/plugin.4.test.js b/test/plugin.4.test.js index 6ebeb8e325a..3ae666fdb9b 100644 --- a/test/plugin.4.test.js +++ b/test/plugin.4.test.js @@ -1,7 +1,5 @@ 'use strict' -/* eslint no-prototype-builtins: 0 */ - const t = require('tap') const test = t.test const Fastify = require('../fastify') diff --git a/test/register.test.js b/test/register.test.js index 99c95746b0e..f75ded87f5d 100644 --- a/test/register.test.js +++ b/test/register.test.js @@ -1,7 +1,5 @@ 'use strict' -/* eslint no-prototype-builtins: 0 */ - const t = require('tap') const test = t.test const sget = require('simple-get').concat @@ -14,7 +12,7 @@ test('register', t => { fastify.register(function (instance, opts, done) { t.not(instance, fastify) - t.ok(fastify.isPrototypeOf(instance)) + t.ok(Object.prototype.isPrototypeOf.call(fastify, instance)) t.equal(typeof opts, 'object') t.equal(typeof done, 'function') @@ -27,7 +25,7 @@ test('register', t => { fastify.register(function (instance, opts, done) { t.not(instance, fastify) - t.ok(fastify.isPrototypeOf(instance)) + t.ok(Object.prototype.isPrototypeOf.call(fastify, instance)) t.equal(typeof opts, 'object') t.equal(typeof done, 'function') diff --git a/test/schema-special-usage.test.js b/test/schema-special-usage.test.js index bc8ae4634d6..b3f341ede17 100644 --- a/test/schema-special-usage.test.js +++ b/test/schema-special-usage.test.js @@ -704,7 +704,7 @@ test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t => }) test('The default schema compilers should not be called when overwritten by the user', async t => { - const Fastify = t.mock('../', { + const Fastify = t.mockRequire('../', { '@fastify/ajv-compiler': () => { t.fail('The default validator compiler should not be called') }, diff --git a/test/server.test.js b/test/server.test.js index 40b1c705247..745ce4391a2 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -4,7 +4,6 @@ const t = require('tap') const test = t.test const Fastify = require('..') const sget = require('simple-get').concat -const semver = require('semver') const undici = require('undici') test('listen should accept null port', t => { @@ -88,7 +87,7 @@ test('Test for hostname and port', t => { }) }) -test('abort signal', { skip: semver.lt(process.version, '16.0.0') }, t => { +test('abort signal', t => { t.test('listen should not start server', t => { t.plan(2) function onClose (instance, done) { diff --git a/test/stream.4.test.js b/test/stream.4.test.js index 53afff27d30..0a6101b845f 100644 --- a/test/stream.4.test.js +++ b/test/stream.4.test.js @@ -3,16 +3,12 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fs = require('node:fs') const errors = require('http-errors') const JSONStream = require('JSONStream') -const send = require('send') const Readable = require('node:stream').Readable const split = require('split2') -const semver = require('semver') const Fastify = require('..') const { kDisableRequestLogging } = require('../lib/symbols.js') -const { getServerUrl } = require('./helper') test('Destroying streams prematurely should call abort method', t => { t.plan(7) @@ -183,41 +179,3 @@ test('return a 404 if the stream emits a 404 error', t => { }) }) }) - -test('should support send module 200 and 404', { skip: semver.gte(process.versions.node, '17.0.0') }, t => { - t.plan(8) - const fastify = Fastify() - - fastify.get('/', function (req, reply) { - const stream = send(req.raw, __filename) - reply.code(200).send(stream) - }) - - fastify.get('/error', function (req, reply) { - const stream = send(req.raw, 'non-existing-file') - reply.code(200).send(stream) - }) - - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - const url = getServerUrl(fastify) - - sget(url, function (err, response, data) { - t.error(err) - t.equal(response.headers['content-type'], 'application/javascript; charset=UTF-8') - t.equal(response.statusCode, 200) - - fs.readFile(__filename, (err, expected) => { - t.error(err) - t.equal(expected.toString(), data.toString()) - }) - }) - - sget(url + '/error', function (err, response) { - t.error(err) - t.equal(response.statusCode, 404) - }) - }) -}) diff --git a/test/web-api.test.js b/test/web-api.test.js index 6c32279410c..4be5d6d5d2d 100644 --- a/test/web-api.test.js +++ b/test/web-api.test.js @@ -4,15 +4,9 @@ const t = require('tap') const test = t.test const Fastify = require('../fastify') const fs = require('node:fs') -const semver = require('semver') const { Readable } = require('node:stream') const { fetch: undiciFetch } = require('undici') -if (semver.lt(process.versions.node, '18.0.0')) { - t.skip('Response or ReadableStream not available, skipping test') - process.exit(0) -} - test('should response with a ReadableStream', async (t) => { t.plan(2) From cc918c2eb998123c54f250d3acb531f083787f84 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 9 Jun 2024 22:44:18 +0300 Subject: [PATCH 0684/1295] chore: Migrate to neostandard (#5509) * migrate to neostandard Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * Update eslint.config.js Co-authored-by: Pelle Wessman Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Signed-off-by: Matteo Collina Co-authored-by: Pelle Wessman --- .eslintrc | 4 --- .github/scripts/lint-ecosystem.js | 8 ++--- README.md | 2 +- eslint.config.js | 27 ++++++++++++++++ examples/typescript-server.ts | 28 ++++++++--------- fastify.d.ts | 22 ++++++------- lib/reply.js | 3 +- lib/server.js | 2 +- package.json | 33 +++++--------------- test/build/error-serializer.test.js | 6 ++-- test/hooks.test.js | 4 --- test/reply-error.test.js | 2 -- test/route-hooks.test.js | 1 - test/types/fastify.test-d.ts | 1 - test/types/hooks.test-d.ts | 1 + test/types/import.ts | 1 + test/types/instance.test-d.ts | 3 +- test/types/logger.test-d.ts | 3 +- test/types/plugin.test-d.ts | 1 + test/types/reply.test-d.ts | 22 +++++++------ test/types/request.test-d.ts | 8 ++--- test/types/route.test-d.ts | 1 + test/types/schema.test-d.ts | 4 +-- test/types/serverFactory.test-d.ts | 2 +- test/types/type-provider.test-d.ts | 8 ++--- test/types/using.test-d.ts | 5 ++- types/.eslintrc.json | 48 ----------------------------- types/content-type-parser.d.ts | 6 ++-- types/context.d.ts | 1 - types/hooks.d.ts | 12 ++++---- types/instance.d.ts | 16 +++++----- types/logger.d.ts | 4 +-- types/plugin.d.ts | 6 ++-- types/reply.d.ts | 12 ++++---- types/request.d.ts | 6 ++-- types/route.d.ts | 4 +-- types/schema.d.ts | 2 +- types/serverFactory.d.ts | 4 +-- types/type-provider.d.ts | 7 ++--- types/utils.d.ts | 36 +++++++++++----------- 40 files changed, 160 insertions(+), 206 deletions(-) delete mode 100644 .eslintrc create mode 100644 eslint.config.js delete mode 100644 types/.eslintrc.json diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 7c7b9ae4c12..00000000000 --- a/.eslintrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "root": true, - "extends": "standard" -} diff --git a/.github/scripts/lint-ecosystem.js b/.github/scripts/lint-ecosystem.js index 946d432e0ec..217839a2d3f 100644 --- a/.github/scripts/lint-ecosystem.js +++ b/.github/scripts/lint-ecosystem.js @@ -25,7 +25,7 @@ async function runCheck () { const failures = [] const successes = [] - const moduleNameRegex = /^\- \[\`(.+)\`\]/ + const moduleNameRegex = /^- \[`(.+)`\]/ let lineNumber = 0 let modules = [] let grouping = 'core' @@ -82,11 +82,11 @@ async function runCheck () { async function handleResults (scriptLibs, results) { const { core } = scriptLibs - const { failures, successes } = results; - const isError = !!failures.length; + const { failures, successes } = results + const isError = !!failures.length await core.summary - .addHeading(isError ? `❌ Ecosystem.md Lint (${failures.length} error${failures.length === 1 ? '' : 's' })` : '✅ Ecosystem Lint (no errors found)') + .addHeading(isError ? `❌ Ecosystem.md Lint (${failures.length} error${failures.length === 1 ? '' : 's'})` : '✅ Ecosystem Lint (no errors found)') .addTable([ [ { data: 'Status', header: true }, diff --git a/README.md b/README.md index a3af4c4bc55..7d4e1a4c477 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml) [![Web SIte](https://github.com/fastify/fastify/workflows/website/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml) -[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/) +[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585)
diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000000..9f9205da1e6 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,27 @@ +'use strict' + +const neo = require('neostandard') + +module.exports = [ + { + ignores: [ + 'lib/configValidator.js', + 'lib/error-serializer.js', + 'test/same-shape.test.js' + ] + }, + ...neo({ + ts: true + }), + { + rules: { + '@stylistic/comma-dangle': ['error', { + arrays: 'never', + objects: 'never', + imports: 'never', + exports: 'never', + functions: 'never' + }] + } + } +] diff --git a/examples/typescript-server.ts b/examples/typescript-server.ts index f8d3c2d9671..8e976ea6bd4 100644 --- a/examples/typescript-server.ts +++ b/examples/typescript-server.ts @@ -10,8 +10,8 @@ * node examples/typescript-server.js */ -import fastify, { FastifyInstance, RouteShorthandOptions } from '../fastify'; -import { Server, IncomingMessage, ServerResponse } from 'http'; +import fastify, { FastifyInstance, RouteShorthandOptions } from '../fastify' +import { Server, IncomingMessage, ServerResponse } from 'http' // Create an http server. We pass the relevant typings for our http version used. // By passing types we get correctly typed access to the underlying http objects in routes. @@ -20,7 +20,7 @@ const server: FastifyInstance< Server, IncomingMessage, ServerResponse -> = fastify({ logger: true }); +> = fastify({ logger: true }) // Define interfaces for our request. We can create these automatically // off our JSON Schema files (See TypeScript.md) but for the purpose of this @@ -53,7 +53,7 @@ const opts: RouteShorthandOptions = { } } } -}; +} // Add our route handler with correct types server.post<{ @@ -62,18 +62,18 @@ server.post<{ Headers: PingHeaders; Body: PingBody; }>('/ping/:bar', opts, (request, reply) => { - console.log(request.query); // this is of type `PingQuerystring` - console.log(request.params); // this is of type `PingParams` - console.log(request.headers); // this is of type `PingHeaders` - console.log(request.body); // this is of type `PingBody` - reply.code(200).send({ pong: 'it worked!' }); -}); + console.log(request.query) // this is of type `PingQuerystring` + console.log(request.params) // this is of type `PingParams` + console.log(request.headers) // this is of type `PingHeaders` + console.log(request.body) // this is of type `PingBody` + reply.code(200).send({ pong: 'it worked!' }) +}) // Start your server server.listen({ port: 8080 }, (err, address) => { if (err) { - console.error(err); - process.exit(1); + console.error(err) + process.exit(1) } - console.log(`server listening on ${address}`) -}); + console.log(`server listening on ${address}`) +}) diff --git a/fastify.d.ts b/fastify.d.ts index 67071d4f401..fb07516a595 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -35,7 +35,7 @@ declare module '@fastify/error' { type Fastify = typeof fastify declare namespace fastify { - export const errorCodes: FastifyErrorCodes; + export const errorCodes: FastifyErrorCodes export type FastifyHttp2SecureOptions< Server extends http2.Http2SecureServer, @@ -167,7 +167,7 @@ declare namespace fastify { /** * @deprecated use {@link FastifySchemaValidationError} */ - export type ValidationResult = FastifySchemaValidationError; + export type ValidationResult = FastifySchemaValidationError /* Export additional types */ export type { @@ -187,7 +187,7 @@ declare namespace fastify { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, // './types/hooks' FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory' FastifyTypeProvider, FastifyTypeProviderDefault, // './types/type-provider' - FastifyErrorCodes, // './types/errors' + FastifyErrorCodes // './types/errors' } // named export // import { plugin } from 'plugin' @@ -211,32 +211,32 @@ declare function fastify< Request extends RawRequestDefaultExpression = RawRequestDefaultExpression, Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts: fastify.FastifyHttp2SecureOptions): FastifyInstance & PromiseLike> + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> (opts: fastify.FastifyHttp2SecureOptions): FastifyInstance & PromiseLike> declare function fastify< Server extends http2.Http2Server, Request extends RawRequestDefaultExpression = RawRequestDefaultExpression, Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts: fastify.FastifyHttp2Options): FastifyInstance & PromiseLike> + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> (opts: fastify.FastifyHttp2Options): FastifyInstance & PromiseLike> declare function fastify< Server extends https.Server, Request extends RawRequestDefaultExpression = RawRequestDefaultExpression, Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts: fastify.FastifyHttpsOptions): FastifyInstance & PromiseLike> + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> (opts: fastify.FastifyHttpsOptions): FastifyInstance & PromiseLike> declare function fastify< Server extends http.Server, Request extends RawRequestDefaultExpression = RawRequestDefaultExpression, Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ->(opts?: fastify.FastifyHttpOptions): FastifyInstance & PromiseLike> + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> (opts?: fastify.FastifyHttpOptions): FastifyInstance & PromiseLike> // CJS export // const fastify = require('fastify') diff --git a/lib/reply.js b/lib/reply.js index 1b76b828b92..26baf7c7b9f 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -908,9 +908,8 @@ function buildReply (R) { this[kReplyEndTime] = undefined this.log = log - // eslint-disable-next-line no-var var prop - // eslint-disable-next-line no-var + for (var i = 0; i < props.length; i++) { prop = props[i] this[prop.key] = prop.value diff --git a/lib/server.js b/lib/server.js index 1a6c86cb9e7..7ccbaea86f2 100644 --- a/lib/server.js +++ b/lib/server.js @@ -315,8 +315,8 @@ function getAddresses (address) { return Object.values(os.networkInterfaces()).flatMap((iface) => { return iface.filter((iface) => iface.family === 'IPv4') }).sort((iface) => { + /* c8 ignore next 2 */ // Order the interfaces so that internal ones come first - /* c8 ignore next 1 */ return iface.internal ? -1 : 1 }).map((iface) => { return iface.address }) } diff --git a/package.json b/package.json index 31d46a0ab66..c7447a3a0d5 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,10 @@ "coverage": "npm run unit -- --coverage-report=html", "coverage:ci": "tap --coverage-report=html --coverage-report=lcov --allow-incomplete-coverage", "coverage:ci-check-coverage": "tap replay", - "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown", - "lint:fix": "standard --fix && npm run lint:typescript:fix", + "lint": "npm run lint:eslint", + "lint:fix": "eslint --fix", "lint:markdown": "markdownlint-cli2", - "lint:standard": "standard | snazzy", - "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", - "lint:typescript:fix": "npm run lint:typescript -- --fix", + "lint:eslint": "eslint", "prepublishOnly": "cross-env PREPUBLISH=true tap --allow-incomplete-coverage test/build/**.test.js && npm run test:validator:integrity", "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run unit -- --coverage-report=lcovonly && npm run test:typescript", @@ -153,13 +151,12 @@ } ], "devDependencies": { - "@eslint/js": "^9.1.1", "@fastify/pre-commit": "^2.1.0", "@sinclair/typebox": "^0.32.22", "@sinonjs/fake-timers": "^11.2.2", + "@stylistic/eslint-plugin": "^2.1.0", + "@stylistic/eslint-plugin-js": "^2.1.0", "@types/node": "^20.12.7", - "@typescript-eslint/eslint-plugin": "^7.7.0", - "@typescript-eslint/parser": "^7.7.0", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -169,12 +166,7 @@ "branch-comparer": "^1.1.0", "concurrently": "^8.2.2", "cross-env": "^7.0.3", - "eslint": "^8.57.0", - "eslint-config-standard": "^17.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-n": "^16.6.2", - "eslint-plugin-promise": "^6.1.1", + "eslint": "^9.0.0", "fast-json-body": "^1.1.0", "fastify-plugin": "^4.5.1", "fluent-json-schema": "^4.2.1", @@ -184,13 +176,12 @@ "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", "markdownlint-cli2": "^0.13.0", + "neostandard": "^0.7.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", "send": "^0.18.0", "simple-get": "^4.0.1", - "snazzy": "^9.0.0", "split2": "^4.2.0", - "standard": "^17.1.0", "tap": "^19.0.0", "tsd": "^0.31.0", "typescript": "^5.4.5", @@ -216,16 +207,6 @@ "semver": "^7.6.0", "toad-cache": "^3.7.0" }, - "standard": { - "ignore": [ - "lib/configValidator.js", - "lib/error-serializer.js", - "fastify.d.ts", - "types/*", - "test/types/*", - "test/same-shape.test.js" - ] - }, "tsd": { "directory": "test/types" } diff --git a/test/build/error-serializer.test.js b/test/build/error-serializer.test.js index 4db32ae8127..d14a428c542 100644 --- a/test/build/error-serializer.test.js +++ b/test/build/error-serializer.test.js @@ -4,6 +4,7 @@ const t = require('tap') const test = t.test const fs = require('node:fs') const path = require('node:path') +const { loadESLint } = require('eslint') const { code } = require('../../build/build-error-serializer') @@ -15,8 +16,9 @@ test('check generated code syntax', async (t) => { t.plan(1) // standard is a esm, we import it like this - const { default: standard } = await import('standard') - const result = await standard.lintText(code) + const Eslint = await loadESLint({ useFlatConfig: true }) + const eslint = new Eslint() + const result = await eslint.lintText(code) // if there are any invalid syntax // fatal count will be greater than 0 diff --git a/test/hooks.test.js b/test/hooks.test.js index 3ee6c8d80a8..bb4ede541f1 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3216,8 +3216,6 @@ test('reply.send should throw if undefined error is thrown', t => { }) test('reply.send should throw if undefined error is thrown at preParsing hook', t => { - /* eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}] */ - t.plan(3) const fastify = Fastify() @@ -3245,8 +3243,6 @@ test('reply.send should throw if undefined error is thrown at preParsing hook', }) test('reply.send should throw if undefined error is thrown at onSend hook', t => { - /* eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}] */ - t.plan(3) const fastify = Fastify() diff --git a/test/reply-error.test.js b/test/reply-error.test.js index 2db6ef4ff67..fd2865e12d4 100644 --- a/test/reply-error.test.js +++ b/test/reply-error.test.js @@ -603,7 +603,6 @@ test('error handler is triggered when a string is thrown from sync handler', t = const payload = 'error' fastify.get('/', function (req, reply) { - // eslint-disable-next-line no-throw-literal throw throwable }) @@ -666,7 +665,6 @@ test('should trigger error handlers if a sync route throws any non-error object' const payload = 'error' fastify.get('/', function async (req, reply) { - // eslint-disable-next-line no-throw-literal throw throwable }) diff --git a/test/route-hooks.test.js b/test/route-hooks.test.js index efef7553ced..422a3aef23c 100644 --- a/test/route-hooks.test.js +++ b/test/route-hooks.test.js @@ -255,7 +255,6 @@ function testBeforeHandlerHook (hook) { fastify.get('/', { [hook]: async () => { - // eslint-disable-next-line no-throw-literal throw myError } }, (req, reply) => { diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 75d4f8e617e..fa5e29c2aad 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -18,7 +18,6 @@ import * as http from 'http' import * as https from 'https' import * as http2 from 'http2' import { expectType, expectError, expectAssignable, expectNotAssignable } from 'tsd' -import { FastifyLoggerInstance } from '../../types/logger' import { Socket } from 'net' // FastifyInstance diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index ebacee9d65a..1d2964e48e8 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -14,6 +14,7 @@ import fastify, { RegisterOptions, RouteOptions, // preClose hook types should be exported correctly https://github.com/fastify/fastify/pull/5335 + /* eslint-disable @typescript-eslint/no-unused-vars */ preCloseAsyncHookHandler, preCloseHookHandler } from '../../fastify' diff --git a/test/types/import.ts b/test/types/import.ts index 7ce901fb712..303ec4d6ccf 100644 --- a/test/types/import.ts +++ b/test/types/import.ts @@ -1 +1,2 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { FastifyListenOptions, FastifyLogFn } from '../../fastify' diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 60e3f053cdd..780f88072eb 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -1,4 +1,4 @@ -import { expectAssignable, expectDeprecated, expectError, expectNotDeprecated, expectType } from 'tsd' +import { expectAssignable, expectError, expectNotDeprecated, expectType } from 'tsd' import fastify, { FastifyBaseLogger, FastifyBodyParser, @@ -12,7 +12,6 @@ import fastify, { import { HookHandlerDoneFunction } from '../../types/hooks' import { FastifyReply } from '../../types/reply' import { FastifyRequest } from '../../types/request' -import { DefaultRoute } from '../../types/route' import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema' import { AddressInfo } from 'net' import { Bindings, ChildLoggerOptions } from '../../types/logger' diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index f7e92780f1d..9165698611f 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -5,8 +5,7 @@ import fastify, { FastifyLoggerInstance, FastifyRequest, FastifyReply, - FastifyBaseLogger, - FastifyInstance + FastifyBaseLogger } from '../../fastify' import { Server, IncomingMessage, ServerResponse } from 'http' import * as fs from 'fs' diff --git a/test/types/plugin.test-d.ts b/test/types/plugin.test-d.ts index 566ec6682b3..42a469033e0 100644 --- a/test/types/plugin.test-d.ts +++ b/test/types/plugin.test-d.ts @@ -61,6 +61,7 @@ expectAssignable>(httpsServer.register(testPluginOptsWith expectAssignable>(httpsServer.register(testPluginOptsWithType, { prefix: '/test' })) expectAssignable>(httpsServer.register(testPluginOptsWithTypeAsync, { prefix: '/test' })) +/* eslint-disable @typescript-eslint/no-unused-vars */ async function testAsync (): Promise { await httpsServer .register(testPluginOpts) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index b6dea2f4898..e668c604775 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,6 +1,6 @@ import { Buffer } from 'buffer' -import { expectAssignable, expectDeprecated, expectError, expectType } from 'tsd' -import fastify, { FastifyReplyContext, FastifyReply, FastifyRequest, FastifySchema, FastifySchemaCompiler, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' +import { expectAssignable, expectError, expectType } from 'tsd' +import fastify, { FastifyReplyContext, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' import { FastifyInstance } from '../../types/instance' import { FastifyLoggerInstance } from '../../types/logger' import { ResolveReplyTypeWithRouteGeneric } from '../../types/reply' @@ -25,12 +25,12 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<(hints: Record, callback?: (() => void) | undefined) => void>(reply.writeEarlyHints) expectType<((payload?: unknown) => FastifyReply)>(reply.send) expectAssignable<(key: string, value: any) => FastifyReply>(reply.header) - expectAssignable<(values: {[key: string]: any}) => FastifyReply>(reply.headers) + expectAssignable<(values: { [key: string]: any }) => FastifyReply>(reply.headers) expectAssignable<(key: string) => number | string | string[] | undefined>(reply.getHeader) expectAssignable<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders) expectAssignable<(key: string) => FastifyReply>(reply.removeHeader) expectAssignable<(key: string) => boolean>(reply.hasHeader) - expectType<{(statusCode: number, url: string): FastifyReply;(url: string, statusCode?: number): FastifyReply;}>(reply.redirect) + expectType<{ (statusCode: number, url: string): FastifyReply;(url: string, statusCode?: number): FastifyReply; }>(reply.redirect) expectType<() => FastifyReply>(reply.hijack) expectType<() => void>(reply.callNotFound) expectType<(contentType: string) => FastifyReply>(reply.type) @@ -42,10 +42,10 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<(key: string) => FastifyReply>(reply.removeTrailer) expectType(reply.server) expectAssignable<((httpStatus: string) => DefaultSerializationFunction | undefined)>(reply.getSerializationFunction) - expectAssignable<((schema: {[key: string]: unknown}) => DefaultSerializationFunction | undefined)>(reply.getSerializationFunction) - expectAssignable<((schema: {[key: string]: unknown}, httpStatus?: string) => DefaultSerializationFunction)>(reply.compileSerializationSchema) - expectAssignable<((input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string) => unknown)>(reply.serializeInput) - expectAssignable<((input: {[key: string]: unknown}, httpStatus: string) => unknown)>(reply.serializeInput) + expectAssignable<((schema: { [key: string]: unknown }) => DefaultSerializationFunction | undefined)>(reply.getSerializationFunction) + expectAssignable<((schema: { [key: string]: unknown }, httpStatus?: string) => DefaultSerializationFunction)>(reply.compileSerializationSchema) + expectAssignable<((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown)>(reply.serializeInput) + expectAssignable<((input: { [key: string]: unknown }, httpStatus: string) => unknown)>(reply.serializeInput) } interface ReplyPayload { @@ -164,10 +164,12 @@ server.get('get-invalid-http-codes-reply-error', async fu }) }) +/* eslint-disable @typescript-eslint/no-unused-vars */ const httpHeaderHandler: RouteHandlerMethod = function (_request, reply) { // accept is a header provided by @types/node reply.getHeader('accept') - reply.getHeaders().accept // eslint-disable-line no-unused-expressions + /* eslint-disable @typescript-eslint/no-unused-expressions */ + reply.getHeaders().accept reply.hasHeader('accept') reply.header('accept', 'test') reply.headers({ accept: 'test' }) @@ -176,7 +178,7 @@ const httpHeaderHandler: RouteHandlerMethod = function (_request, reply) { // x-fastify-test is not a header provided by @types/node // and should not result in a typing error reply.getHeader('x-fastify-test') - reply.getHeaders()['x-fastify-test'] // eslint-disable-line no-unused-expressions + reply.getHeaders()['x-fastify-test'] reply.hasHeader('x-fastify-test') reply.header('x-fastify-test', 'test') reply.headers({ 'x-fastify-test': 'test' }) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index dfd4b787020..18ec2cc5b7b 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -54,7 +54,7 @@ type CustomRequest = FastifyRequest<{ }> type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' -type ExpectedGetValidationFunction = (input: {[key: string]: unknown}) => boolean +type ExpectedGetValidationFunction = (input: { [key: string]: unknown }) => boolean interface CustomLoggerInterface extends FastifyLoggerInstance { foo: FastifyLogFn; // custom severity logger method @@ -96,9 +96,9 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.validationError) expectType(request.server) expectAssignable<(httpPart: HTTPRequestPart) => ExpectedGetValidationFunction>(request.getValidationFunction) - expectAssignable<(schema: {[key: string]: unknown}) => ExpectedGetValidationFunction>(request.getValidationFunction) - expectAssignable<(input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) - expectAssignable<(input: {[key: string]: unknown}, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) + expectAssignable<(schema: { [key: string]: unknown }) => ExpectedGetValidationFunction>(request.getValidationFunction) + expectAssignable<(input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) + expectAssignable<(input: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) } const getHandlerWithCustomLogger: RouteHandlerMethod = function (request, _reply) { diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 0f4391a2bd1..c7d10d9db4d 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -22,6 +22,7 @@ declare module '../../fastify' { includeMessage?: boolean; } + /* eslint-disable @typescript-eslint/no-unused-vars */ interface FastifyRequest { message: ContextConfig extends { includeMessage: true } ? string diff --git a/test/types/schema.test-d.ts b/test/types/schema.test-d.ts index 801fdb11fe3..fb3f13cc3dd 100644 --- a/test/types/schema.test-d.ts +++ b/test/types/schema.test-d.ts @@ -70,7 +70,7 @@ expectAssignable(server.setSerializerCompiler(server.setSerializerCompiler CustomType; } diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index a2778ed21bb..c1091b48119 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -907,7 +907,7 @@ expectError(server.withTypeProvider().get( // Reply Type Override // ------------------------------------------------------------------- -expectAssignable(server.withTypeProvider().get<{Reply: boolean}>( +expectAssignable(server.withTypeProvider().get<{ Reply: boolean }>( '/', { schema: { @@ -927,7 +927,7 @@ expectAssignable(server.withTypeProvider().get<{Reply: b // Reply Type Override (Different Content-types) // ------------------------------------------------------------------- -expectAssignable(server.withTypeProvider().get<{Reply: boolean}>( +expectAssignable(server.withTypeProvider().get<{ Reply: boolean }>( '/', { schema: { @@ -955,7 +955,7 @@ expectAssignable(server.withTypeProvider().get<{Reply: b // Reply Type Return Override // ------------------------------------------------------------------- -expectAssignable(server.withTypeProvider().get<{Reply: boolean}>( +expectAssignable(server.withTypeProvider().get<{ Reply: boolean }>( '/', { schema: { @@ -975,7 +975,7 @@ expectAssignable(server.withTypeProvider().get<{Reply: b // Reply Type Return Override (Different Content-types) // ------------------------------------------------------------------- -expectAssignable(server.withTypeProvider().get<{Reply: boolean}>( +expectAssignable(server.withTypeProvider().get<{ Reply: boolean }>( '/', { schema: { diff --git a/test/types/using.test-d.ts b/test/types/using.test-d.ts index 6b2997b0130..72252d47fe7 100644 --- a/test/types/using.test-d.ts +++ b/test/types/using.test-d.ts @@ -1,4 +1,4 @@ -import { expectAssignable, expectType } from 'tsd' +import { expectAssignable } from 'tsd' import fastify, { FastifyInstance } from '../../fastify' async function hasSymbolDisposeWithUsing () { @@ -12,3 +12,6 @@ async function hasSymbolDispose () { expectAssignable(app) expectAssignable(app.close) } + +hasSymbolDisposeWithUsing() +hasSymbolDispose() diff --git a/types/.eslintrc.json b/types/.eslintrc.json deleted file mode 100644 index e0f08e65932..00000000000 --- a/types/.eslintrc.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "standard" - ], - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "env": { "node": true }, - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module", - "project": "./types/tsconfig.eslint.json", - "createDefaultProgram": true - }, - "rules": { - "no-console": "off", - "@typescript-eslint/indent": ["error", 2], - "semi": ["error", "never"], - "import/export": "off" // this errors on multiple exports (overload interfaces) - }, - "overrides": [ - { - "files": ["*.d.ts","*.test-d.ts"], - "rules": { - "@typescript-eslint/no-explicit-any": "off" - } - }, - { - "files": ["*.test-d.ts"], - "rules": { - "no-unused-vars": "off", - "n/handle-callback-err": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-misused-promises": ["error", { - "checksVoidReturn": false - }] - }, - "globals": { - "NodeJS": "readonly" - } - } - ] -} diff --git a/types/content-type-parser.d.ts b/types/content-type-parser.d.ts index 631093fd31b..c4c7839bcee 100644 --- a/types/content-type-parser.d.ts +++ b/types/content-type-parser.d.ts @@ -15,7 +15,7 @@ export type FastifyBodyParser< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > = ((request: FastifyRequest, rawBody: RawBody, done: ContentTypeParserDoneFunction) => void) | ((request: FastifyRequest, rawBody: RawBody) => Promise) @@ -27,7 +27,7 @@ export type FastifyContentTypeParser< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > = ((request: FastifyRequest, payload: RawRequest) => Promise) | ((request: FastifyRequest, payload: RawRequest, done: ContentTypeParserDoneFunction) => void) @@ -39,7 +39,7 @@ export interface AddContentTypeParser< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( contentType: string | string[] | RegExp, diff --git a/types/context.d.ts b/types/context.d.ts index 242b70d3709..01f66d807e0 100644 --- a/types/context.d.ts +++ b/types/context.d.ts @@ -1,7 +1,6 @@ import { FastifyRouteConfig } from './route' import { ContextConfigDefault } from './utils' -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface FastifyContextConfig { } diff --git a/types/hooks.d.ts b/types/hooks.d.ts index ef824ae3194..f17f799c971 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -724,7 +724,7 @@ export interface onReadyHookHandler< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( this: FastifyInstance, @@ -737,7 +737,7 @@ export interface onReadyAsyncHookHandler< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( this: FastifyInstance, @@ -752,7 +752,7 @@ export interface onListenHookHandler< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( this: FastifyInstance, @@ -765,7 +765,7 @@ export interface onListenAsyncHookHandler< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( this: FastifyInstance, @@ -779,7 +779,7 @@ export interface onCloseHookHandler< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( instance: FastifyInstance, @@ -807,7 +807,7 @@ export interface preCloseHookHandler< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( this: FastifyInstance, diff --git a/types/instance.d.ts b/types/instance.d.ts index 93e12229d92..41723500419 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -30,7 +30,7 @@ export interface PrintRoutesOptions { includeHooks?: boolean } -type AsyncFunction = (...args: any) => Promise; +type AsyncFunction = (...args: any) => Promise export interface FastifyListenOptions { /** @@ -96,7 +96,7 @@ type GetterSetter = T | { type DecorationMethod = { < // Need to disable "no-use-before-define" to maintain backwards compatibility, as else decorate would suddenly mean something new - // eslint-disable-next-line no-use-before-define + T extends (P extends keyof This ? This[P] : unknown), P extends string | symbol = string | symbol >(property: P, @@ -109,7 +109,7 @@ type DecorationMethod = { (property: string | symbol): Return; - (property: string | symbol, value: null|undefined, dependencies: string[]): Return; + (property: string | symbol, value: null | undefined, dependencies: string[]): Return; } /** @@ -120,7 +120,7 @@ export interface FastifyInstance< RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, - TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { server: RawServer; pluginName: string; @@ -142,7 +142,7 @@ export interface FastifyInstance< close(closeListener: () => void): undefined; /** Alias for {@linkcode FastifyInstance.close()} */ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - type only available for @types/node >=17 or typescript >= 5.2 [Symbol.asyncDispose](): Promise; @@ -177,7 +177,7 @@ export interface FastifyInstance< route< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, - const SchemaCompiler extends FastifySchema = FastifySchema, + const SchemaCompiler extends FastifySchema = FastifySchema >(opts: RouteOptions): FastifyInstance; delete: RouteShorthandMethod; @@ -203,13 +203,13 @@ export interface FastifyInstance< hasRoute< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, + SchemaCompiler extends FastifySchema = FastifySchema >(opts: Pick, 'method' | 'url' | 'constraints'>): boolean; findRoute< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, - SchemaCompiler extends FastifySchema = FastifySchema, + SchemaCompiler extends FastifySchema = FastifySchema >(opts: Pick, 'method' | 'url' | 'constraints'>): Omit, 'store'>; // addHook: overloads diff --git a/types/logger.d.ts b/types/logger.d.ts index 15fcbc3fa23..70e3f5ee297 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -43,7 +43,7 @@ export type PinoLoggerOptions = pino.LoggerOptions export type ResSerializerReply< RawServer extends RawServerBase, RawReply extends FastifyReply -> = Partial & Pick; +> = Partial & Pick /** * Fastify Custom Logger options. @@ -51,7 +51,7 @@ export type ResSerializerReply< export interface FastifyLoggerOptions< RawServer extends RawServerBase = RawServerDefault, RawRequest extends FastifyRequest, FastifySchema, FastifyTypeProvider> = FastifyRequest, FastifySchema, FastifyTypeProviderDefault>, - RawReply extends FastifyReply, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProvider> = FastifyReply, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault>, + RawReply extends FastifyReply, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProvider> = FastifyReply, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault> > { serializers?: { req?: (req: RawRequest) => { diff --git a/types/plugin.d.ts b/types/plugin.d.ts index f628a152820..797b9cac156 100644 --- a/types/plugin.d.ts +++ b/types/plugin.d.ts @@ -14,7 +14,7 @@ export type FastifyPluginCallback< Options extends FastifyPluginOptions = Record, Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger, + Logger extends FastifyBaseLogger = FastifyBaseLogger > = ( instance: FastifyInstance, RawReplyDefaultExpression, Logger, TypeProvider>, opts: Options, @@ -30,11 +30,11 @@ export type FastifyPluginAsync< Options extends FastifyPluginOptions = Record, Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, - Logger extends FastifyBaseLogger = FastifyBaseLogger, + Logger extends FastifyBaseLogger = FastifyBaseLogger > = ( instance: FastifyInstance, RawReplyDefaultExpression, Logger, TypeProvider>, opts: Options -) => Promise; +) => Promise /** * Generic plugin type. diff --git a/types/reply.d.ts b/types/reply.d.ts index 99e141ea4cf..5bf3f46ed18 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -18,7 +18,7 @@ type ReplyTypeConstrainer, never> ? Code extends keyof RouteGenericReply ? RouteGenericReply[Code] : CodeToReplyKey extends keyof RouteGenericReply ? RouteGenericReply[CodeToReplyKey] : unknown : - RouteGenericReply; + RouteGenericReply export type ResolveReplyTypeWithRouteGeneric, SchemaCompiler extends FastifySchema = FastifySchema, @@ -69,11 +69,11 @@ export interface FastifyReply< serializer(fn: (payload: any) => string): FastifyReply; serialize(payload: any): string | ArrayBuffer | Buffer; // Serialization Methods - getSerializationFunction(httpStatus: string, contentType?: string): ((payload: {[key: string]: unknown}) => string) | undefined; - getSerializationFunction(schema: {[key: string]: unknown}): ((payload: {[key: string]: unknown}) => string) | undefined; - compileSerializationSchema(schema: {[key: string]: unknown}, httpStatus?: string, contentType?: string): (payload: {[key: string]: unknown}) => string; - serializeInput(input: {[key: string]: unknown}, schema: {[key: string]: unknown}, httpStatus?: string, contentType?: string): string; - serializeInput(input: {[key: string]: unknown}, httpStatus: string, contentType?: string): unknown; + getSerializationFunction(httpStatus: string, contentType?: string): ((payload: { [key: string]: unknown }) => string) | undefined; + getSerializationFunction(schema: { [key: string]: unknown }): ((payload: { [key: string]: unknown }) => string) | undefined; + compileSerializationSchema(schema: { [key: string]: unknown }, httpStatus?: string, contentType?: string): (payload: { [key: string]: unknown }) => string; + serializeInput(input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string, contentType?: string): string; + serializeInput(input: { [key: string]: unknown }, httpStatus: string, contentType?: string): unknown; then(fulfilled: () => void, rejected: (err: Error) => void): void; trailer: ( key: string, diff --git a/types/request.d.ts b/types/request.d.ts index b2e4e38ec53..a6af0103278 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -89,9 +89,9 @@ export interface FastifyRequest, error: FastifyError, @@ -183,4 +183,4 @@ export type RouteHandler< export type DefaultRoute = ( req: Request, res: Reply, -) => void; +) => void diff --git a/types/schema.d.ts b/types/schema.d.ts index fc01ac0241b..12f88178daa 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -44,7 +44,7 @@ export type FastifySchemaCompiler = (routeSchema: FastifyRouteSchemaDef) = export type FastifySerializerCompiler = (routeSchema: FastifyRouteSchemaDef) => (data: any) => string -export interface FastifySchemaControllerOptions{ +export interface FastifySchemaControllerOptions { bucket?: (parentSchemas?: unknown) => { add(schema: unknown): FastifyInstance; getSchema(schemaId: string): unknown; diff --git a/types/serverFactory.d.ts b/types/serverFactory.d.ts index 569bfc10760..c4d72b82526 100644 --- a/types/serverFactory.d.ts +++ b/types/serverFactory.d.ts @@ -9,8 +9,8 @@ export type FastifyServerFactoryHandler< RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression > = RawServer extends http.Server | https.Server ? - (request: http.IncomingMessage & RawRequest, response: http.ServerResponse & RawReply) => void : - (request: http2.Http2ServerRequest & RawRequest, response: http2.Http2ServerResponse & RawReply) => void + (request: http.IncomingMessage & RawRequest, response: http.ServerResponse & RawReply) => void : + (request: http2.Http2ServerRequest & RawRequest, response: http2.Http2ServerResponse & RawReply) => void export interface FastifyServerFactory< RawServer extends RawServerBase = RawServerDefault diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 85de1445bd7..901e7117e3e 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -11,7 +11,6 @@ export interface FastifyTypeProvider { readonly output: unknown, } -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface FastifyTypeProviderDefault extends FastifyTypeProvider {} export type CallTypeProvider = (F & { input: I })['output'] @@ -65,7 +64,7 @@ type ResolveReplyFromSchemaCompiler } extends infer Result ? Result[keyof Result] : unknown) : CallTypeProvider -} extends infer Result ? Result[keyof Result] : unknown; +} extends infer Result ? Result[keyof Result] : unknown // The target reply type. This type is inferenced on fastify 'replies' via generic argument assignment export type FastifyReplyType = Reply @@ -83,13 +82,13 @@ export type ResolveFastifyReplyType = ResolveFastifyReplyType< TypeProvider, SchemaCompiler, RouteGeneric > extends infer Return ? - (Return | void | Promise) + (Return | void | Promise) // review: support both async and sync return types // (Promise | Return | Promise | void) : unknown diff --git a/types/utils.d.ts b/types/utils.d.ts index fe7a84f4286..e7897f52c82 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -24,7 +24,7 @@ export type RawServerDefault = http.Server * The default request type based on the server type. Utilizes generic constraining. */ export type RawRequestDefaultExpression< - RawServer extends RawServerBase = RawServerDefault, + RawServer extends RawServerBase = RawServerDefault > = RawServer extends http.Server | https.Server ? http.IncomingMessage : RawServer extends http2.Http2Server | http2.Http2SecureServer ? http2.Http2ServerRequest : never @@ -50,41 +50,41 @@ export type ReplyDefault = unknown * Helpers for determining the type of the response payload based on the code */ -type StringAsNumber = T extends `${infer N extends number}` ? N : never; -type CodeClasses = 1 | 2 | 3 | 4 | 5; -type Digit = 0 |1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; -type HttpCodes = StringAsNumber<`${CodeClasses}${Digit}${Digit}`>; -type HttpKeys = HttpCodes | `${Digit}xx`; +type StringAsNumber = T extends `${infer N extends number}` ? N : never +type CodeClasses = 1 | 2 | 3 | 4 | 5 +type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 +type HttpCodes = StringAsNumber<`${CodeClasses}${Digit}${Digit}`> +type HttpKeys = HttpCodes | `${Digit}xx` export type StatusCodeReply = { - // eslint-disable-next-line no-unused-vars + [Key in HttpKeys]?: unknown; -}; +} // weird TS quirk: https://stackoverflow.com/questions/58977876/generic-conditional-type-resolves-to-never-when-the-generic-type-is-set-to-never export type ReplyKeysToCodes = [Key] extends [never] ? number : Key extends HttpCodes ? Key : Key extends `${infer X extends CodeClasses}xx` ? - StringAsNumber<`${X}${Digit}${Digit}`> : number; + StringAsNumber<`${X}${Digit}${Digit}`> : number export type CodeToReplyKey = `${Code}` extends `${infer FirstDigit extends CodeClasses}${number}` ? `${FirstDigit}xx` - : never; + : never export type RecordKeysToLowercase = Input extends Record ? { - [Key in keyof Input as Key extends string - ? Lowercase - : Key - ]: Input[Key]; - } - : Input; + [Key in keyof Input as Key extends string + ? Lowercase + : Key + ]: Input[Key]; + } + : Input type OmitIndexSignature = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; -}; +} /** * HTTP header strings * Use this type only for input values, not for output values. */ -export type HttpHeader = keyof OmitIndexSignature | (string & Record); +export type HttpHeader = keyof OmitIndexSignature | (string & Record) From 613841c4348b8cd04b37ece292756d05936992f8 Mon Sep 17 00:00:00 2001 From: SMNBLMRR <105361460+SMNBLMRR@users.noreply.github.com> Date: Sun, 9 Jun 2024 23:20:38 +0200 Subject: [PATCH 0685/1295] fix: hasRoute method comparison with case insensitive (#5508) * fix:hasRoute method comparison with case insensitive * fix:rename variable inside hasRoute method and update test case * fix normalizedMethod with optional chaining and nullish coalescing --- lib/route.js | 3 ++- test/has-route.test.js | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/route.js b/lib/route.js index 9bb23d91c7a..4e4ad0f2d13 100644 --- a/lib/route.js +++ b/lib/route.js @@ -152,8 +152,9 @@ function buildRouting (options) { } function hasRoute ({ options }) { + const normalizedMethod = options.method?.toUpperCase() ?? '' return router.hasRoute( - options.method, + normalizedMethod, options.url || '', options.constraints ) diff --git a/test/has-route.test.js b/test/has-route.test.js index 56cade122f8..76b2862b580 100644 --- a/test/has-route.test.js +++ b/test/has-route.test.js @@ -5,7 +5,7 @@ const test = t.test const Fastify = require('../fastify') test('hasRoute', t => { - t.plan(4) + t.plan(5) const test = t.test const fastify = Fastify() @@ -74,4 +74,20 @@ test('hasRoute', t => { url: '/example/:file(^\\d+).png' }), true) }) + + test('hasRoute - finds a route even if method is not uppercased', t => { + t.plan(1) + fastify.route({ + method: 'GET', + url: '/equal', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + + t.equal(fastify.hasRoute({ + method: 'get', + url: '/equal' + }), true) + }) }) From caaeb9fc3a928bb4df4c636468553df135d82edb Mon Sep 17 00:00:00 2001 From: Roberto Bianchi Date: Tue, 11 Jun 2024 11:54:21 +0200 Subject: [PATCH 0686/1295] feat(types): Introduce SafePromiseLike (#5506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(types): Introduce SafePromiseLike Signed-off-by: Roberto Bianchi * Update doc Signed-off-by: Roberto Bianchi * Update brand property Signed-off-by: Roberto Bianchi * Update types/type-provider.d.ts Co-authored-by: Josh Goldberg ✨ Signed-off-by: Roberto Bianchi * Update docs/Reference/TypeScript.md Co-authored-by: Josh Goldberg ✨ Signed-off-by: Roberto Bianchi * Fix link Signed-off-by: Roberto Bianchi * Update docs/Reference/TypeScript.md Co-authored-by: Josh Goldberg ✨ Signed-off-by: Roberto Bianchi * Add safePromiseLike tests Signed-off-by: Roberto Bianchi * Add line break Signed-off-by: Roberto Bianchi * fix lint Signed-off-by: Roberto Bianchi * Update docs/Reference/TypeScript.md Co-authored-by: Josh Goldberg ✨ Signed-off-by: Roberto Bianchi * Update docs/Reference/TypeScript.md Co-authored-by: Josh Goldberg ✨ Signed-off-by: Roberto Bianchi --------- Signed-off-by: Roberto Bianchi Signed-off-by: Roberto Bianchi Co-authored-by: Josh Goldberg ✨ --- docs/Reference/TypeScript.md | 17 +++++++++++++++++ fastify.d.ts | 12 ++++++------ test/types/fastify.test-d.ts | 23 +++++++++++++---------- test/types/plugin.test-d.ts | 6 ++++-- test/types/request.test-d.ts | 15 ++++++++++++--- test/types/type-provider.test-d.ts | 14 +++++++++++++- types/instance.d.ts | 9 +++++---- types/schema.d.ts | 4 ++-- types/type-provider.d.ts | 9 +++++++++ 9 files changed, 81 insertions(+), 28 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 2aeec92dcab..b1634dd042a 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -661,6 +661,23 @@ However, there are a couple of suggestions to help improve this experience: - Make sure the `no-unused-vars` rule is enabled in [ESLint](https://eslint.org/docs/rules/no-unused-vars) and any imported plugin are actually being loaded. +- In case you've the `@typescript-eslint/no-floating-promises` enabled, +please double-check that your ESLint configuration includes a `allowForKnownSafePromises` +property as described on the [`typescript-eslint no-floating-promises allowForKnownSafePromises +documentation`](https://typescript-eslint.io/rules/no-floating-promises/#allowforknownsafepromises): +``` +{ + "rules": { + "@typescript-eslint/no-floating-promises": ["error", { + "allowForKnownSafePromises": [ + { "from": "package", "name": "FastifyInstance", "package": "fastify" }, + { "from": "package", "name": "FastifyReply", "package": "fastify" }, + { "from": "package", "name": "SafePromiseLike", "package": "fastify" }, + ] + }] + } +} +``` - Use a module such as [depcheck](https://www.npmjs.com/package/depcheck) or [npm-check](https://www.npmjs.com/package/npm-check) to verify plugin dependencies are being used somewhere in your project. diff --git a/fastify.d.ts b/fastify.d.ts index fb07516a595..9cb85b72477 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -22,7 +22,7 @@ import { FastifyRequest, RequestGenericInterface } from './types/request' import { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route' import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema' import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' -import { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider' +import { FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike } from './types/type-provider' import { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils' declare module '@fastify/error' { @@ -186,7 +186,7 @@ declare namespace fastify { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault, // './types/utils' DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, // './types/hooks' FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory' - FastifyTypeProvider, FastifyTypeProviderDefault, // './types/type-provider' + FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike, // './types/type-provider' FastifyErrorCodes // './types/errors' } // named export @@ -212,7 +212,7 @@ declare function fastify< Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault -> (opts: fastify.FastifyHttp2SecureOptions): FastifyInstance & PromiseLike> +> (opts: fastify.FastifyHttp2SecureOptions): FastifyInstance & SafePromiseLike> declare function fastify< Server extends http2.Http2Server, @@ -220,7 +220,7 @@ declare function fastify< Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault -> (opts: fastify.FastifyHttp2Options): FastifyInstance & PromiseLike> +> (opts: fastify.FastifyHttp2Options): FastifyInstance & SafePromiseLike> declare function fastify< Server extends https.Server, @@ -228,7 +228,7 @@ declare function fastify< Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault -> (opts: fastify.FastifyHttpsOptions): FastifyInstance & PromiseLike> +> (opts: fastify.FastifyHttpsOptions): FastifyInstance & SafePromiseLike> declare function fastify< Server extends http.Server, @@ -236,7 +236,7 @@ declare function fastify< Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression, Logger extends FastifyBaseLogger = FastifyBaseLogger, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault -> (opts?: fastify.FastifyHttpOptions): FastifyInstance & PromiseLike> +> (opts?: fastify.FastifyHttpOptions): FastifyInstance & SafePromiseLike> // CJS export // const fastify = require('fastify') diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index fa5e29c2aad..455af87d4f2 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -11,7 +11,8 @@ import fastify, { RawRequestDefaultExpression, RouteGenericInterface, FastifyErrorCodes, - FastifyError + FastifyError, + SafePromiseLike } from '../../fastify' import { ErrorObject as AjvErrorObject } from 'ajv' import * as http from 'http' @@ -22,18 +23,20 @@ import { Socket } from 'net' // FastifyInstance // http server -expectType & PromiseLike>>(fastify()) -expectType & PromiseLike>>(fastify({})) -expectType & PromiseLike>>(fastify({ http: {} })) +expectError & Promise>>(fastify()) +expectAssignable & PromiseLike>>(fastify()) +expectType & SafePromiseLike>>(fastify()) +expectType & SafePromiseLike>>(fastify({})) +expectType & SafePromiseLike>>(fastify({ http: {} })) // https server -expectType & PromiseLike>>(fastify({ https: {} })) -expectType & PromiseLike>>(fastify({ https: null })) +expectType & SafePromiseLike>>(fastify({ https: {} })) +expectType & SafePromiseLike>>(fastify({ https: null })) // http2 server -expectType & PromiseLike>>(fastify({ http2: true, http2SessionTimeout: 1000 })) -expectType & PromiseLike>>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 })) +expectType & SafePromiseLike>>(fastify({ http2: true, http2SessionTimeout: 1000 })) +expectType & SafePromiseLike>>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 })) expectType(fastify({ http2: true, https: {} }).inject()) -expectType & PromiseLike>>(fastify({ schemaController: {} })) -expectType & PromiseLike>>( +expectType & SafePromiseLike>>(fastify({ schemaController: {} })) +expectType & SafePromiseLike>>( fastify({ schemaController: { compilersFactory: {} diff --git a/test/types/plugin.test-d.ts b/test/types/plugin.test-d.ts index 42a469033e0..410c7fa3f3b 100644 --- a/test/types/plugin.test-d.ts +++ b/test/types/plugin.test-d.ts @@ -1,4 +1,4 @@ -import fastify, { FastifyInstance, FastifyPluginOptions } from '../../fastify' +import fastify, { FastifyInstance, FastifyPluginOptions, SafePromiseLike } from '../../fastify' import * as http from 'http' import * as https from 'https' import { expectType, expectError, expectAssignable } from 'tsd' @@ -42,7 +42,9 @@ expectAssignable(fastify().register(async function (instance, o expectError(fastify().register(function (instance, opts, done) { }, { logLevel: '' })) // must use a valid logLevel const httpsServer = fastify({ https: {} }) -expectType & PromiseLike>>(httpsServer) +expectError & Promise>>(httpsServer) +expectAssignable & PromiseLike>>(httpsServer) +expectType & SafePromiseLike>>(httpsServer) // Chainable httpsServer diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 18ec2cc5b7b..09f96a3cc35 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -1,4 +1,4 @@ -import { expectAssignable, expectType } from 'tsd' +import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { ContextConfigDefault, FastifyRequestContext, @@ -12,7 +12,8 @@ import fastify, { RequestBodyDefault, RequestGenericInterface, RouteHandler, - RouteHandlerMethod + RouteHandlerMethod, + SafePromiseLike } from '../../fastify' import { FastifyInstance } from '../../types/instance' import { FastifyLoggerInstance } from '../../types/logger' @@ -156,9 +157,17 @@ const customLogger: CustomLoggerInterface = { } const serverWithCustomLogger = fastify({ loggerInstance: customLogger }) -expectType< +expectError< +FastifyInstance +& Promise> +>(serverWithCustomLogger) +expectAssignable< FastifyInstance & PromiseLike> >(serverWithCustomLogger) +expectType< +FastifyInstance +& SafePromiseLike> +>(serverWithCustomLogger) serverWithCustomLogger.get('/get', getHandlerWithCustomLogger) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index c1091b48119..c93feaf06c0 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -4,7 +4,8 @@ import fastify, { FastifyRequest, FastifyReply, FastifyInstance, - FastifyError + FastifyError, + SafePromiseLike } from '../../fastify' import { expectAssignable, expectError, expectType } from 'tsd' import { IncomingHttpHeaders } from 'http' @@ -1069,3 +1070,14 @@ expectAssignable(server.withTypeProvider().get( expectType<'handler-auxiliary'>(req.body) } )) + +// ------------------------------------------------------------------- +// SafePromiseLike +// ------------------------------------------------------------------- +const safePromiseLike = { + then: new Promise(resolve => resolve('')).then, + __linterBrands: 'SafePromiseLike' as const +} +expectAssignable>(safePromiseLike) +expectAssignable>(safePromiseLike) +expectError>(safePromiseLike) diff --git a/types/instance.d.ts b/types/instance.d.ts index 41723500419..68ef39f75bd 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -19,7 +19,8 @@ import { } from './schema' import { FastifyTypeProvider, - FastifyTypeProviderDefault + FastifyTypeProviderDefault, + SafePromiseLike } from './type-provider' import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' @@ -135,7 +136,7 @@ export interface FastifyInstance< getSchema(schemaId: string): unknown; getSchemas(): Record; - after(): FastifyInstance & PromiseLike; + after(): FastifyInstance & SafePromiseLike; after(afterListener: (err: Error | null) => void): FastifyInstance; close(): Promise; @@ -167,10 +168,10 @@ export interface FastifyInstance< listen(opts?: FastifyListenOptions): Promise; listen(callback: (err: Error | null, address: string) => void): void; - ready(): FastifyInstance & PromiseLike; + ready(): FastifyInstance & SafePromiseLike; ready(readyListener: (err: Error | null) => void | Promise): FastifyInstance; - register: FastifyRegister & PromiseLike>; + register: FastifyRegister & SafePromiseLike>; routing(req: RawRequest, res: RawReply): void; diff --git a/types/schema.d.ts b/types/schema.d.ts index 12f88178daa..ebed58500a1 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -1,6 +1,6 @@ import { ValidatorFactory } from '@fastify/ajv-compiler' import { SerializerFactory } from '@fastify/fast-json-stringify-compiler' -import { FastifyInstance } from '../fastify' +import { FastifyInstance, SafePromiseLike } from '../fastify' /** * Schemas in Fastify follow the JSON-Schema standard. For this reason * we have opted to not ship strict schema based types. Instead we provide @@ -33,7 +33,7 @@ export interface FastifySchemaValidationError { } export interface FastifyValidationResult { - (data: any): boolean | PromiseLike | { error?: Error, value?: any } + (data: any): boolean | SafePromiseLike | { error?: Error, value?: any } errors?: FastifySchemaValidationError[] | null; } diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 901e7117e3e..8c5409f7de5 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -92,3 +92,12 @@ RouteGeneric // review: support both async and sync return types // (Promise | Return | Promise | void) : unknown + +/** + * This branded type is needed to indicate APIs that return Promise-likes which can + * safely "float" (not have rejections handled by calling code). + * + * Please refer to the following Github issue for more info: + * https://github.com/fastify/fastify/issues/5498 + */ +export type SafePromiseLike = PromiseLike & { __linterBrands: 'SafePromiseLike' } From 8a6628847be4d051b68710fc095ac91a336c8826 Mon Sep 17 00:00:00 2001 From: Aadit Olkar <63646058+aadito123@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:50:05 +0800 Subject: [PATCH 0687/1295] auxilliary hook handler type fix + test fix (#5517) --- test/types/type-provider.test-d.ts | 4 +-- types/route.d.ts | 50 +++++++++++++++--------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index c93feaf06c0..25dea1672f1 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -1053,7 +1053,7 @@ expectAssignable(server.withTypeProvider().get( // Handlers: Auxiliary // ------------------------------------------------------------------- -interface AuxiliaryHandlerProvider extends FastifyTypeProvider { output: 'handler-auxiliary' } +interface AuxiliaryHandlerProvider extends FastifyTypeProvider { output: this['input'] } // Auxiliary handlers are likely shared for multiple routes and thus should infer as unknown due to potential varying parameters function auxiliaryHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction): void { @@ -1064,7 +1064,7 @@ expectAssignable(server.withTypeProvider().get( '/', { onRequest: auxiliaryHandler, - schema: { body: null } + schema: { body: 'handler-auxiliary' } }, (req) => { expectType<'handler-auxiliary'>(req.body) diff --git a/types/route.d.ts b/types/route.d.ts index 8c2496b4f35..5248db29eab 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -48,8 +48,8 @@ export interface RouteShorthandOptions< attachValidation?: boolean; exposeHeadRoute?: boolean; - validatorCompiler?: FastifySchemaCompiler; - serializerCompiler?: FastifySerializerCompiler; + validatorCompiler?: FastifySchemaCompiler>; + serializerCompiler?: FastifySerializerCompiler>; bodyLimit?: number; logLevel?: LogLevel; config?: FastifyContextConfig & ContextConfig; @@ -59,33 +59,33 @@ export interface RouteShorthandOptions< errorHandler?: ( this: FastifyInstance, error: FastifyError, - request: FastifyRequest, - reply: FastifyReply + request: FastifyRequest, TypeProvider, ContextConfig, Logger>, + reply: FastifyReply, TypeProvider> ) => void; childLoggerFactory?: FastifyChildLoggerFactory; schemaErrorFormatter?: SchemaErrorFormatter; // hooks - onRequest?: onRequestMetaHookHandler - | onRequestMetaHookHandler[]; - preParsing?: preParsingMetaHookHandler - | preParsingMetaHookHandler[]; - preValidation?: preValidationMetaHookHandler - | preValidationMetaHookHandler[]; - preHandler?: preHandlerMetaHookHandler - | preHandlerMetaHookHandler[]; - preSerialization?: preSerializationMetaHookHandler - | preSerializationMetaHookHandler[]; - onSend?: onSendMetaHookHandler - | onSendMetaHookHandler[]; - onResponse?: onResponseMetaHookHandler - | onResponseMetaHookHandler[]; - onTimeout?: onTimeoutMetaHookHandler - | onTimeoutMetaHookHandler[]; - onError?: onErrorMetaHookHandler - | onErrorMetaHookHandler[]; - onRequestAbort?: onRequestAbortMetaHookHandler - | onRequestAbortMetaHookHandler[]; + onRequest?: onRequestMetaHookHandler, TypeProvider, Logger> + | onRequestMetaHookHandler, TypeProvider, Logger>[]; + preParsing?: preParsingMetaHookHandler, TypeProvider, Logger> + | preParsingMetaHookHandler, TypeProvider, Logger>[]; + preValidation?: preValidationMetaHookHandler, TypeProvider, Logger> + | preValidationMetaHookHandler, TypeProvider, Logger>[]; + preHandler?: preHandlerMetaHookHandler, TypeProvider, Logger> + | preHandlerMetaHookHandler, TypeProvider, Logger>[]; + preSerialization?: preSerializationMetaHookHandler, TypeProvider, Logger> + | preSerializationMetaHookHandler, TypeProvider, Logger>[]; + onSend?: onSendMetaHookHandler, TypeProvider, Logger> + | onSendMetaHookHandler, TypeProvider, Logger>[]; + onResponse?: onResponseMetaHookHandler, TypeProvider, Logger> + | onResponseMetaHookHandler, TypeProvider, Logger>[]; + onTimeout?: onTimeoutMetaHookHandler, TypeProvider, Logger> + | onTimeoutMetaHookHandler, TypeProvider, Logger>[]; + onError?: onErrorMetaHookHandler, TypeProvider, Logger> + | onErrorMetaHookHandler, TypeProvider, Logger>[]; + onRequestAbort?: onRequestAbortMetaHookHandler, TypeProvider, Logger> + | onRequestAbortMetaHookHandler, TypeProvider, Logger>[]; } /** * Route handler method declaration. @@ -119,7 +119,7 @@ export interface RouteShorthandOptionsWithHandler< TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger > extends RouteShorthandOptions { - handler: RouteHandlerMethod; + handler: RouteHandlerMethod, TypeProvider, Logger>; } /** From 7ae82d8e476c79238755ec0f6d0984d7e4ca34c2 Mon Sep 17 00:00:00 2001 From: moradebianchetti81 <169001930+moradebianchetti81@users.noreply.github.com> Date: Wed, 12 Jun 2024 04:52:27 -0300 Subject: [PATCH 0688/1295] docs: addContentTypeParser with fastify.register (#5499) * docs: addContentTypeParser with fastify.register Signed-off-by: moradebianchetti81 <169001930+moradebianchetti81@users.noreply.github.com> * fix: add example doc contentTypeParse * implement suggested changes * Update docs/Reference/ContentTypeParser.md Signed-off-by: Frazer Smith --------- Signed-off-by: moradebianchetti81 <169001930+moradebianchetti81@users.noreply.github.com> Signed-off-by: Frazer Smith Co-authored-by: Frazer Smith --- docs/Reference/ContentTypeParser.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index 801d972d2d0..d86a2e190a1 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -87,6 +87,28 @@ fastify.addContentTypeParser('application/vnd.custom', (request, body, done) => fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) => {} ) ``` +### Using addContentTypeParser with fastify.register +When using `addContentTypeParser` in combination with `fastify.register`, +`await` should not be used when registering routes. Using `await` causes +the route registration to be asynchronous and can lead to routes being registered +before the addContentTypeParser has been set. + +#### Correct Usage +```js +const fastify = require('fastify')(); + + +fastify.register((fastify, opts) => { + fastify.addContentTypeParser('application/json', function (request, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.get('/hello', async (req, res) => {}); +}); +``` + Besides the `addContentTypeParser` API there are further APIs that can be used. These are `hasContentTypeParser`, `removeContentTypeParser` and `removeAllContentTypeParsers`. From bdfc10d12b430e1fc6e2513e17220c1451c82a7b Mon Sep 17 00:00:00 2001 From: laohan <120936337@qq.com> Date: Fri, 14 Jun 2024 17:54:55 +0800 Subject: [PATCH 0689/1295] docs: remove navigation for empty content (#5521) Co-authored-by: hanquliu --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7d4e1a4c477..a19fa22e166 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ Check out the [`4.x` branch](https://github.com/fastify/fastify/tree/4.x) for `v - [Quick start](#quick-start) - [Install](#install) - [Example](#example) - - [Fastify v1.x and v2.x](#fastify-v1x-and-v2x) - [Core features](#core-features) - [Benchmarks](#benchmarks) - [Documentation](#documentation) From 6514dc7563d6d4401f8902ce5f333999a6058776 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 14 Jun 2024 19:09:51 +0800 Subject: [PATCH 0690/1295] fix: server.listen listener is not cleanup properly (#5522) --- lib/server.js | 26 +++++++++---- test/listen.5.test.js | 88 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 test/listen.5.test.js diff --git a/lib/server.js b/lib/server.js index 7ccbaea86f2..a590be4da8d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -203,6 +203,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o function listenCallback (server, listenOptions) { const wrap = (err) => { server.removeListener('error', wrap) + server.removeListener('listening', wrap) if (!err) { const address = logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText) listenOptions.cb(null, address) @@ -223,7 +224,8 @@ function listenCallback (server, listenOptions) { server.once('error', wrap) if (!this[kState].closing) { - server.listen(listenOptions, wrap) + server.once('listening', wrap) + server.listen(listenOptions) this[kState].listening = true } } @@ -238,25 +240,33 @@ function listenPromise (server, listenOptions) { return this.ready().then(() => { let errEventHandler + let listeningEventHandler + function cleanup () { + server.removeListener('error', errEventHandler) + server.removeListener('listening', listeningEventHandler) + } const errEvent = new Promise((resolve, reject) => { errEventHandler = (err) => { + cleanup() this[kState].listening = false reject(err) } server.once('error', errEventHandler) }) - const listen = new Promise((resolve, reject) => { - server.listen(listenOptions, () => { - server.removeListener('error', errEventHandler) + const listeningEvent = new Promise((resolve, reject) => { + listeningEventHandler = () => { + cleanup() + this[kState].listening = true resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)) - }) - // we set it afterwards because listen can throw - this[kState].listening = true + } + server.once('listening', listeningEventHandler) }) + server.listen(listenOptions) + return Promise.race([ errEvent, // e.g invalid port range error is always emitted before the server listening - listen + listeningEvent ]) }) } diff --git a/test/listen.5.test.js b/test/listen.5.test.js new file mode 100644 index 00000000000..c7ac80417c0 --- /dev/null +++ b/test/listen.5.test.js @@ -0,0 +1,88 @@ +'use strict' + +const { test } = require('tap') +const net = require('node:net') +const Fastify = require('../fastify') +const { once } = require('node:events') + +test('same port conflict and success should not fire callback multiple times - callback', async (t) => { + t.plan(7) + const server = net.createServer() + server.listen({ port: 0, host: '127.0.0.1' }) + await once(server, 'listening') + const option = { port: server.address().port, host: server.address().address } + let count = 0 + const fastify = Fastify() + function callback (err) { + switch (count) { + case 6: { + // success in here + t.error(err) + fastify.close((err) => { + t.error(err) + }) + break + } + case 5: { + server.close() + setTimeout(() => { + fastify.listen(option, callback) + }, 100) + break + } + default: { + // expect error + t.equal(err.code, 'EADDRINUSE') + setTimeout(() => { + fastify.listen(option, callback) + }, 100) + } + } + count++ + } + fastify.listen(option, callback) +}) + +test('same port conflict and success should not fire callback multiple times - promise', async (t) => { + t.plan(5) + const server = net.createServer() + server.listen({ port: 0, host: '127.0.0.1' }) + await once(server, 'listening') + const option = { port: server.address().port, host: server.address().address } + const fastify = Fastify() + + try { + await fastify.listen(option) + } catch (err) { + t.equal(err.code, 'EADDRINUSE') + } + try { + await fastify.listen(option) + } catch (err) { + t.equal(err.code, 'EADDRINUSE') + } + try { + await fastify.listen(option) + } catch (err) { + t.equal(err.code, 'EADDRINUSE') + } + try { + await fastify.listen(option) + } catch (err) { + t.equal(err.code, 'EADDRINUSE') + } + try { + await fastify.listen(option) + } catch (err) { + t.equal(err.code, 'EADDRINUSE') + } + + server.close() + + await once(server, 'close') + + // when ever we can listen, and close properly + // which means there is no problem on the callback + await fastify.listen() + await fastify.close() +}) From c44dd0058ba90b44982f2331a6e9110e7d13a863 Mon Sep 17 00:00:00 2001 From: Bram del Canho Date: Fri, 21 Jun 2024 09:39:12 +0200 Subject: [PATCH 0691/1295] feat: type definitions and documentation for separated type provider. (#5427) --- docs/Guides/Write-Type-Provider.md | 6 ++-- test/types/register.test-d.ts | 2 +- test/types/type-provider.test-d.ts | 49 +++++++++++++++++++++++++----- types/reply.d.ts | 4 +-- types/type-provider.d.ts | 22 ++++++++------ 5 files changed, 61 insertions(+), 22 deletions(-) diff --git a/docs/Guides/Write-Type-Provider.md b/docs/Guides/Write-Type-Provider.md index fadba38765d..4078345015b 100644 --- a/docs/Guides/Write-Type-Provider.md +++ b/docs/Guides/Write-Type-Provider.md @@ -19,7 +19,8 @@ For example, `FastifyTypeProviderDefault` will not be assignable to the followin ```ts export interface NotSubstitutableTypeProvider extends FastifyTypeProvider { // bad, nothing is assignable to `never` (except for itself) - output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : never; + validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never; + serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never; } ``` @@ -27,6 +28,7 @@ Unless changed to: ```ts export interface SubstitutableTypeProvider extends FastifyTypeProvider { // good, anything can be assigned to `unknown` - output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown; + validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown; + serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown; } ``` diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index d1ddc10e6ba..2b60cbfa3b1 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -72,7 +72,7 @@ expectAssignable(serverWithHttp2.register(async (instance: Serv })) // With Type Provider -type TestTypeProvider = { input: 'test', output: 'test' } +type TestTypeProvider = { schema: 'test', validator: 'test', serializer: 'test' } const serverWithTypeProvider = fastify().withTypeProvider() type ServerWithTypeProvider = FastifyInstance const testPluginWithTypeProvider: FastifyPluginCallback = function (instance, opts, done) { } diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index 25dea1672f1..e4c7f14fa7b 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -24,7 +24,10 @@ expectAssignable(server.get('/', (req) => expectType(req.body))) // Remapping // ------------------------------------------------------------------- -interface NumberProvider extends FastifyTypeProvider { output: number } // remap all schemas to numbers +interface NumberProvider extends FastifyTypeProvider { + validator: number + serializer: number +} // remap all schemas to numbers expectAssignable(server.withTypeProvider().get( '/', @@ -48,7 +51,7 @@ expectAssignable(server.withTypeProvider().get( // Override // ------------------------------------------------------------------- -interface OverriddenProvider extends FastifyTypeProvider { output: 'inferenced' } +interface OverriddenProvider extends FastifyTypeProvider { validator: 'inferenced' } expectAssignable(server.withTypeProvider().get<{ Body: 'override' }>( '/', @@ -70,7 +73,10 @@ expectAssignable(server.withTypeProvider().get<{ Body: 'over // TypeBox // ------------------------------------------------------------------- -interface TypeBoxProvider extends FastifyTypeProvider { output: this['input'] extends TSchema ? Static : unknown } +interface TypeBoxProvider extends FastifyTypeProvider { + validator: this['schema'] extends TSchema ? Static : unknown + serializer: this['schema'] extends TSchema ? Static : unknown +} expectAssignable(server.withTypeProvider().get( '/', @@ -104,7 +110,10 @@ expectAssignable(server.withTypeProvider()) // JsonSchemaToTs // ------------------------------------------------------------------- -interface JsonSchemaToTsProvider extends FastifyTypeProvider { output: this['input'] extends JSONSchema ? FromSchema : unknown } +interface JsonSchemaToTsProvider extends FastifyTypeProvider { + validator: this['schema'] extends JSONSchema ? FromSchema : unknown + serializer: this['schema'] extends JSONSchema ? FromSchema : unknown +} // explicitly setting schema `as const` @@ -1004,7 +1013,7 @@ expectAssignable(server.withTypeProvider().get<{ Reply: // FastifyPlugin: Auxiliary // ------------------------------------------------------------------- -interface AuxiliaryPluginProvider extends FastifyTypeProvider { output: 'plugin-auxiliary' } +interface AuxiliaryPluginProvider extends FastifyTypeProvider { validator: 'plugin-auxiliary' } // Auxiliary plugins may have varying server types per application. Recommendation would be to explicitly remap instance provider context within plugin if required. function plugin (instance: T) { @@ -1033,7 +1042,7 @@ expectAssignable(server.withTypeProvider().register(plu // Handlers: Inline // ------------------------------------------------------------------- -interface InlineHandlerProvider extends FastifyTypeProvider { output: 'handler-inline' } +interface InlineHandlerProvider extends FastifyTypeProvider { validator: 'handler-inline' } // Inline handlers should infer for the request parameters (non-shared) expectAssignable(server.withTypeProvider().get( @@ -1053,7 +1062,7 @@ expectAssignable(server.withTypeProvider().get( // Handlers: Auxiliary // ------------------------------------------------------------------- -interface AuxiliaryHandlerProvider extends FastifyTypeProvider { output: this['input'] } +interface AuxiliaryHandlerProvider extends FastifyTypeProvider { validator: 'handler-auxiliary' } // Auxiliary handlers are likely shared for multiple routes and thus should infer as unknown due to potential varying parameters function auxiliaryHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction): void { @@ -1081,3 +1090,29 @@ const safePromiseLike = { expectAssignable>(safePromiseLike) expectAssignable>(safePromiseLike) expectError>(safePromiseLike) + +// ------------------------------------------------------------------- +// Separate Providers +// ------------------------------------------------------------------- + +interface SeparateProvider extends FastifyTypeProvider { + validator: string + serializer: Date +} + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: null, + response: { + 200: { type: 'string' } + } + } + }, + (req, res) => { + expectType(req.body) + + res.send(new Date()) + } +)) diff --git a/types/reply.d.ts b/types/reply.d.ts index 5bf3f46ed18..a0a4550f67e 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -5,7 +5,7 @@ import { FastifyBaseLogger } from './logger' import { FastifyRequest } from './request' import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' -import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType, CallTypeProvider } from './type-provider' +import { CallSerializerTypeProvider, FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider' import { CodeToReplyKey, ContextConfigDefault, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes, HttpHeader } from './utils' export interface ReplyGenericInterface { @@ -24,7 +24,7 @@ export type ResolveReplyTypeWithRouteGeneric = Code extends keyof SchemaCompiler['response'] ? - CallTypeProvider : + CallSerializerTypeProvider : ResolveFastifyReplyType }> /** * FastifyReply is an instance of the standard http or http2 reply types. diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 8c5409f7de5..a99f0d4bc65 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -7,13 +7,15 @@ import { RecordKeysToLowercase } from './utils' // ----------------------------------------------------------------------------------------------- export interface FastifyTypeProvider { - readonly input: unknown, - readonly output: unknown, + readonly schema: unknown, + readonly validator: unknown, + readonly serializer: unknown, } export interface FastifyTypeProviderDefault extends FastifyTypeProvider {} -export type CallTypeProvider = (F & { input: I })['output'] +export type CallValidatorTypeProvider = (F & { schema: S })['validator'] +export type CallSerializerTypeProvider = (F & { schema: S })['serializer'] // ----------------------------------------------------------------------------------------------- // FastifyRequestType @@ -31,13 +33,13 @@ type KeysOf = T extends any ? keyof T : never // Resolves Request types either from generic argument or Type Provider. type ResolveRequestParams = - UndefinedToUnknown extends never ? CallTypeProvider : RouteGeneric['Params']> + UndefinedToUnknown extends never ? CallValidatorTypeProvider : RouteGeneric['Params']> type ResolveRequestQuerystring = - UndefinedToUnknown extends never ? CallTypeProvider : RouteGeneric['Querystring']> + UndefinedToUnknown extends never ? CallValidatorTypeProvider : RouteGeneric['Querystring']> type ResolveRequestHeaders = - UndefinedToUnknown extends never ? CallTypeProvider : RouteGeneric['Headers']> + UndefinedToUnknown extends never ? CallValidatorTypeProvider : RouteGeneric['Headers']> type ResolveRequestBody = - UndefinedToUnknown extends never ? CallTypeProvider : RouteGeneric['Body']> + UndefinedToUnknown extends never ? CallValidatorTypeProvider : RouteGeneric['Body']> // The target request type. This type is inferenced on fastify 'requests' via generic argument assignment export interface FastifyRequestType { @@ -62,9 +64,9 @@ export interface ResolveFastifyRequestType = { [K1 in keyof SchemaCompiler['response']]: SchemaCompiler['response'][K1] extends { content: { [keyof: string]: { schema: unknown } } } ? ({ - [K2 in keyof SchemaCompiler['response'][K1]['content']]: CallTypeProvider - } extends infer Result ? Result[keyof Result] : unknown) : CallTypeProvider -} extends infer Result ? Result[keyof Result] : unknown + [K2 in keyof SchemaCompiler['response'][K1]['content']]: CallSerializerTypeProvider + } extends infer Result ? Result[keyof Result] : unknown) : CallSerializerTypeProvider +} extends infer Result ? Result[keyof Result] : unknown; // The target reply type. This type is inferenced on fastify 'replies' via generic argument assignment export type FastifyReplyType = Reply From 810ccf3823afb46ddb84d817f0090543fa4a578c Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:44:09 -0400 Subject: [PATCH 0692/1295] chore: support pre and alpha tags (#5528) --- lib/pluginUtils.js | 4 +- test/plugin.4.test.js | 88 ++++++++++++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 6251946c5ab..def8f0e7930 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -106,11 +106,11 @@ function _checkDecorators (that, instance, decorators, name) { function checkVersion (fn) { const meta = getMeta(fn) - if (!meta) return + if (meta == null || meta?.fastify == null) return const requiredVersion = meta.fastify - const fastifyRc = /-rc.+$/.test(this.version) + const fastifyRc = /-(rc|pre|alpha).+$/.test(this.version) if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) { // A Fastify release candidate phase is taking place. In order to reduce // the effort needed to test plugins with the RC, we allow plugins targeting diff --git a/test/plugin.4.test.js b/test/plugin.4.test.js index 3ae666fdb9b..d48222a215a 100644 --- a/test/plugin.4.test.js +++ b/test/plugin.4.test.js @@ -132,6 +132,9 @@ test('plugin metadata - version not matching requirement', t => { test('plugin metadata - version not matching requirement 2', t => { t.plan(2) const fastify = Fastify() + Object.defineProperty(fastify, 'version', { + value: '99.0.0' + }) plugin[Symbol.for('skip-override')] = true plugin[Symbol.for('plugin-meta')] = { @@ -197,36 +200,75 @@ test('plugin metadata - release candidate', t => { } }) -test('fastify-rc loads prior version plugins', t => { - t.plan(2) - const fastify = Fastify() - Object.defineProperty(fastify, 'version', { - value: '99.0.0-rc.1' +test('fastify-rc loads prior version plugins', async t => { + t.test('baseline (rc)', t => { + t.plan(2) + + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { + value: '99.0.0-rc.1' + }) + + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '^98.1.0' + } + plugin2[Symbol.for('plugin-meta')] = { + name: 'plugin2', + fastify: '98.x' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.error(err) + t.pass('everything right') + }) + + function plugin (instance, opts, done) { + done() + } + + function plugin2 (instance, opts, done) { + done() + } }) - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '^98.1.0' - } - plugin2[Symbol.for('plugin-meta')] = { - name: 'plugin2', - fastify: '98.x' - } + t.test('pre', t => { + t.plan(2) - fastify.register(plugin) + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { value: '99.0.0-pre.1' }) - fastify.ready((err) => { - t.error(err) - t.pass('everything right') + plugin[Symbol.for('plugin-meta')] = { name: 'plugin', fastify: '^98.x' } + + fastify.register(plugin) + + fastify.ready((err) => { + t.error(err) + t.pass() + }) + + function plugin (instance, opts, done) { done() } }) - function plugin (instance, opts, done) { - done() - } + t.test('alpha', t => { + t.plan(2) - function plugin2 (instance, opts, done) { - done() - } + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { value: '99.0.0-pre.1' }) + + plugin[Symbol.for('plugin-meta')] = { name: 'plugin', fastify: '^98.x' } + + fastify.register(plugin) + + fastify.ready((err) => { + t.error(err) + t.pass() + }) + + function plugin (instance, opts, done) { done() } + }) }) test('hasPlugin method exists as a function', t => { From 61a8419d97a1808cb8ef9c7e5f1d35e42bc2afe7 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 21 Jun 2024 19:29:44 +0200 Subject: [PATCH 0693/1295] Bumped v5.0.0-alpha.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 299991e9278..1e53575c9c3 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.0.0-alpha.1' +const VERSION = '5.0.0-alpha.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index c7447a3a0d5..f946ac970fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From f4cf75b063daeb497b8fc329a2b1ea12d0a2daca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:09:05 +0000 Subject: [PATCH 0694/1295] chore: Bump the dependencies-major group with 2 updates (#5530) Bumps the dependencies-major group with 2 updates: [@fastify/error](https://github.com/fastify/fastify-error) and [fast-json-stringify](https://github.com/fastify/fast-json-stringify). Updates `@fastify/error` from 3.4.1 to 4.0.0 - [Release notes](https://github.com/fastify/fastify-error/releases) - [Commits](https://github.com/fastify/fastify-error/compare/v3.4.1...v4.0.0) Updates `fast-json-stringify` from 5.16.1 to 6.0.0 - [Release notes](https://github.com/fastify/fast-json-stringify/releases) - [Commits](https://github.com/fastify/fast-json-stringify/compare/v5.16.1...v6.0.0) --- updated-dependencies: - dependency-name: "@fastify/error" dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies-major - dependency-name: fast-json-stringify dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f946ac970fc..c8dc9de9696 100644 --- a/package.json +++ b/package.json @@ -191,12 +191,12 @@ }, "dependencies": { "@fastify/ajv-compiler": "^3.5.0", - "@fastify/error": "^3.4.1", + "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", "avvio": "^8.3.0", "dc-polyfill": "^0.1.6", - "fast-json-stringify": "^5.14.1", + "fast-json-stringify": "^6.0.0", "find-my-way": "^8.1.0", "light-my-request": "^5.13.0", "pino": "^9.0.0", From 30af5405276ccc8997aa8cce7c0ec7c987a0a953 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:10:22 +0000 Subject: [PATCH 0695/1295] chore: Bump neostandard in the dev-dependencies group (#5531) Bumps the dev-dependencies group with 1 update: [neostandard](https://github.com/neostandard/neostandard). Updates `neostandard` from 0.7.2 to 0.8.0 - [Release notes](https://github.com/neostandard/neostandard/releases) - [Changelog](https://github.com/neostandard/neostandard/blob/main/CHANGELOG.md) - [Commits](https://github.com/neostandard/neostandard/compare/v0.7.2...v0.8.0) --- updated-dependencies: - dependency-name: neostandard dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c8dc9de9696..1499c930294 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", "markdownlint-cli2": "^0.13.0", - "neostandard": "^0.7.0", + "neostandard": "^0.8.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", "send": "^0.18.0", From c90cf5a6e06b1596dafa16395358218f05253698 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 27 Jun 2024 11:54:36 +0200 Subject: [PATCH 0696/1295] fix: update .npmignore (#5537) Signed-off-by: Manuel Spigolon --- .npmignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.npmignore b/.npmignore index 87cea5a3cb6..4226655b38c 100644 --- a/.npmignore +++ b/.npmignore @@ -3,10 +3,13 @@ .gitignore .github .nyc_output +.DS_Store coverage/ tools/ +.tap/ CODE_OF_CONDUCT.md CONTRIBUTING.md +EXPENSE_POLICY.md .clinic .gitpod.yml From 37ccb2a61e61f867eaedf0bc43b971cd88e35a35 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:13:56 +0800 Subject: [PATCH 0697/1295] test: fix test finished earlier than expected (#5540) --- test/listen.5.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/listen.5.test.js b/test/listen.5.test.js index c7ac80417c0..8976a10d64b 100644 --- a/test/listen.5.test.js +++ b/test/listen.5.test.js @@ -5,6 +5,14 @@ const net = require('node:net') const Fastify = require('../fastify') const { once } = require('node:events') +function createDeferredPromise () { + const promise = {} + promise.promise = new Promise((resolve) => { + promise.resolve = resolve + }) + return promise +} + test('same port conflict and success should not fire callback multiple times - callback', async (t) => { t.plan(7) const server = net.createServer() @@ -13,6 +21,7 @@ test('same port conflict and success should not fire callback multiple times - c const option = { port: server.address().port, host: server.address().address } let count = 0 const fastify = Fastify() + const promise = createDeferredPromise() function callback (err) { switch (count) { case 6: { @@ -20,6 +29,7 @@ test('same port conflict and success should not fire callback multiple times - c t.error(err) fastify.close((err) => { t.error(err) + promise.resolve() }) break } @@ -41,6 +51,7 @@ test('same port conflict and success should not fire callback multiple times - c count++ } fastify.listen(option, callback) + await promise.promise }) test('same port conflict and success should not fire callback multiple times - promise', async (t) => { From 93ca82c18db980155184eb92ba1f0fcd2e1006fa Mon Sep 17 00:00:00 2001 From: James Sumners Date: Fri, 28 Jun 2024 06:12:58 -0400 Subject: [PATCH 0698/1295] Bumped v5.0.0-alpha.3 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 1e53575c9c3..00cb36a4df7 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.0.0-alpha.2' +const VERSION = '5.0.0-alpha.3' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 1499c930294..6935b79ec95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 5a0eedf8293132d6a7295d7c6b1a794d20ebfd05 Mon Sep 17 00:00:00 2001 From: Cangit Date: Sun, 30 Jun 2024 09:37:36 +0200 Subject: [PATCH 0699/1295] chore: rm < node19 support from secondaryServer.close() (#5542) --- lib/server.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/server.js b/lib/server.js index a590be4da8d..8e6f6bd489b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -143,7 +143,6 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o cb: (_ignoreErr) => { bound++ - /* istanbul ignore next: the else won't be taken unless listening fails */ if (!_ignoreErr) { this[kServerBindings].push(secondaryServer) } @@ -157,18 +156,13 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o const secondaryServer = getServerInstance(serverOpts, httpHandler) const closeSecondary = () => { - // To avoid fall into situations where the close of the + // To avoid falling into situations where the close of the // secondary server is triggered before the preClose hook - // is done running, we better wait until the main server - // is closed. + // is done running, we better wait until the main server is closed. // No new TCP connections are accepted - // We swallow any error from the secondary - // server + // We swallow any error from the secondary server secondaryServer.close(() => {}) - if (serverOpts.forceCloseConnections === 'idle') { - // Not needed in Node 19 - secondaryServer.closeIdleConnections() - } else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) { + if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections === true) { secondaryServer.closeAllConnections() } } From 9898d089791d30623c4f48a968ab0983c1721ae8 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 30 Jun 2024 13:30:41 +0200 Subject: [PATCH 0700/1295] chore(sponsor): add valtown (#5543) Signed-off-by: Manuel Spigolon --- SPONSORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SPONSORS.md b/SPONSORS.md index 52e8db19b9c..b7f6d34890b 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -14,6 +14,7 @@ _Be the first!_ ## Tier 3 - [Mercedes-Benz Group](https://github.com/mercedes-benz) +- [Val Town, Inc.](https://opencollective.com/valtown) ## Tier 2 From f8dbeaae75de1910770890ce5265d82d74f7471f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:21:01 +0000 Subject: [PATCH 0701/1295] chore: Bump the dev-dependencies group with 2 updates (#5546) Bumps the dev-dependencies group with 2 updates: [neostandard](https://github.com/neostandard/neostandard) and [tap](https://github.com/tapjs/tapjs). Updates `neostandard` from 0.8.0 to 0.10.0 - [Release notes](https://github.com/neostandard/neostandard/releases) - [Changelog](https://github.com/neostandard/neostandard/blob/main/CHANGELOG.md) - [Commits](https://github.com/neostandard/neostandard/compare/v0.8.0...v0.10.0) Updates `tap` from 19.2.5 to 20.0.3 - [Release notes](https://github.com/tapjs/tapjs/releases) - [Commits](https://github.com/tapjs/tapjs/compare/tap@19.2.5...tap@20.0.3) --- updated-dependencies: - dependency-name: neostandard dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: tap dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6935b79ec95..1e6df6d9379 100644 --- a/package.json +++ b/package.json @@ -176,13 +176,13 @@ "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", "markdownlint-cli2": "^0.13.0", - "neostandard": "^0.8.0", + "neostandard": "^0.10.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", "send": "^0.18.0", "simple-get": "^4.0.1", "split2": "^4.2.0", - "tap": "^19.0.0", + "tap": "^20.0.3", "tsd": "^0.31.0", "typescript": "^5.4.5", "undici": "^6.13.0", From de1c064d725eb1ce1ea58f6c139747a8cf287bbe Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Wed, 3 Jul 2024 22:35:25 +0900 Subject: [PATCH 0702/1295] docs: use `http2` directive in nginx config (#5548) This directive appeared in version 1.25.1. The `http2` parameter in `listen` directive is deprecated. Signed-off-by: Livia Medeiros --- docs/Guides/Recommendations.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index 9ae694c959d..58e921d6149 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -212,17 +212,19 @@ server { # server group via port 3000. server { # This listen directive asks NGINX to accept requests - # coming to any address, port 443, with SSL, and HTTP/2 - # if possible. - listen 443 ssl http2 default_server; - listen [::]:443 ssl http2 default_server; + # coming to any address, port 443, with SSL. + listen 443 ssl default_server; + listen [::]:443 ssl default_server; # With a server_name directive you can also ask NGINX to # use this server block only with matching server name(s) - # listen 443 ssl http2; - # listen [::]:443 ssl http2; + # listen 443 ssl; + # listen [::]:443 ssl; # server_name example.tld; + # Enable HTTP/2 support + http2 on; + # Your SSL/TLS certificate (chain) and secret key in the PEM format ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/private.pem; From 5c64dc48e0d2ddef0f310e03040d8ad9870a0335 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:42:58 +0000 Subject: [PATCH 0703/1295] chore: Bump neostandard in the dev-dependencies group (#5554) Bumps the dev-dependencies group with 1 update: [neostandard](https://github.com/neostandard/neostandard). Updates `neostandard` from 0.10.0 to 0.11.0 - [Release notes](https://github.com/neostandard/neostandard/releases) - [Changelog](https://github.com/neostandard/neostandard/blob/main/CHANGELOG.md) - [Commits](https://github.com/neostandard/neostandard/compare/v0.10.0...v0.11.0) --- updated-dependencies: - dependency-name: neostandard dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e6df6d9379..6750a6b99c0 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", "markdownlint-cli2": "^0.13.0", - "neostandard": "^0.10.0", + "neostandard": "^0.11.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", "send": "^0.18.0", From fc02d685825bf576617a2ab3cec7821d6cd5f3d7 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:39:18 +0800 Subject: [PATCH 0704/1295] chore: fix lint (#5558) --- types/type-provider.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index a99f0d4bc65..1c789f51a73 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -66,7 +66,7 @@ type ResolveReplyFromSchemaCompiler } extends infer Result ? Result[keyof Result] : unknown) : CallSerializerTypeProvider -} extends infer Result ? Result[keyof Result] : unknown; +} extends infer Result ? Result[keyof Result] : unknown // The target reply type. This type is inferenced on fastify 'replies' via generic argument assignment export type FastifyReplyType = Reply From 870bb82de8444b091cee8928cc5e6110ace44f91 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:25:45 +0800 Subject: [PATCH 0705/1295] ci: remove automerge from ci alternative runtimes (#5557) --- .github/workflows/ci-alternative-runtime.yml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index 27bf7d1b755..19691dac88f 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -105,20 +105,3 @@ jobs: - name: Test esbuild bundle run: | cd test/bundler/esbuild && npm run test - - automerge: - if: > - github.event_name == 'pull_request' && - github.event.pull_request.user.login == 'dependabot[bot]' - needs: - - test-typescript - - test-unit - - package - runs-on: ubuntu-latest - permissions: - pull-requests: write - contents: write - steps: - - uses: fastify/github-action-merge-dependabot@v3 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} From f3ee7f79c558371309a498230c8dd0d4ac72dfdc Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 9 Jul 2024 14:28:38 +0100 Subject: [PATCH 0706/1295] feat: support different body schema per content type (#5545) * feat: support different body schema per content type * Add test for internal schema compiler * Consistent formatting of tests * Add test to avoid regression if types get stricter * Fix types test * Lint * Prefer single quoutes * Add docs with example schema * Remove unrelated formatting changes * Remove extra semicolon * Avoid using contractions Co-authored-by: Frazer Smith Signed-off-by: Nico Flaig * Reduce branches Co-authored-by: Manuel Spigolon Signed-off-by: Nico Flaig * Consistent variable naming * Add todo for adding request.contentType * Reduce nesting --------- Signed-off-by: Nico Flaig Co-authored-by: Frazer Smith Co-authored-by: Manuel Spigolon --- .../Reference/Validation-and-Serialization.md | 22 +++ lib/schemas.js | 13 ++ lib/validation.js | 25 +++- test/internals/validation.test.js | 28 +++- test/schema-feature.test.js | 21 +++ test/schema-validation.test.js | 136 ++++++++++++++++++ test/types/schema.test-d.ts | 19 +++ 7 files changed, 261 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 790e79bfe5f..54c40248bce 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -235,6 +235,28 @@ const schema = { fastify.post('/the/url', { schema }, handler) ``` +For `body` schema, it is further possible to differentiate the schema per content +type by nesting the schemas inside `content` property. The schema validation +will be applied based on the `Content-Type` header in the request. + +```js +fastify.post('/the/url', { + schema: { + body: { + content: { + 'application/json': { + schema: { type: 'object' } + }, + 'text/plain': { + schema: { type: 'string' } + } + // Other content types will not be validated + } + } + } +}, handler) +``` + *Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) the values to the types specified in your schema `type` keywords, both to pass the validation and to use the correctly typed data afterwards.* diff --git a/lib/schemas.js b/lib/schemas.js index c9a603e8e0a..55d76ea646d 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -73,6 +73,19 @@ function normalizeSchema (routeSchemas, serverOptions) { for (const key of SCHEMAS_SOURCE) { const schema = routeSchemas[key] if (schema && !isCustomSchemaPrototype(schema)) { + if (key === 'body' && schema.content) { + const contentProperty = schema.content + const keys = Object.keys(contentProperty) + for (let i = 0; i < keys.length; i++) { + const contentType = keys[i] + const contentSchema = contentProperty[contentType].schema + if (!contentSchema) { + throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType) + } + routeSchemas.body.content[contentType].schema = getSchemaAnyway(contentSchema, serverOptions.jsonShorthand) + } + continue + } routeSchemas[key] = getSchemaAnyway(schema, serverOptions.jsonShorthand) } } diff --git a/lib/validation.js b/lib/validation.js index a83d948cbf9..90589468d0d 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -87,7 +87,17 @@ function compileSchemasForValidation (context, compile, isCustom) { } if (schema.body) { - context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) + const contentProperty = schema.body.content + if (contentProperty) { + const contentTypeSchemas = {} + for (const contentType of Object.keys(contentProperty)) { + const contentSchema = contentProperty[contentType].schema + contentTypeSchemas[contentType] = compile({ schema: contentSchema, method, url, httpPart: 'body', contentType }) + } + context[bodySchema] = contentTypeSchemas + } else { + context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) + } } else if (Object.prototype.hasOwnProperty.call(schema, 'body')) { FSTWRN001('body', method, url) } @@ -140,7 +150,18 @@ function validate (context, request, execution) { } if (runExecution || !execution.skipBody) { - const body = validateParam(context[bodySchema], request, 'body') + let validatorFunction = null + if (typeof context[bodySchema] === 'function') { + validatorFunction = context[bodySchema] + } else if (context[bodySchema]) { + // TODO: add request.contentType and reuse it here + const contentType = request.headers['content-type']?.split(';', 1)[0] + const contentSchema = context[bodySchema][contentType] + if (contentSchema) { + validatorFunction = contentSchema + } + } + const body = validateParam(validatorFunction, request, 'body') if (body) { if (typeof body.then !== 'function') { return wrapValidationError(body, 'body', context.schemaErrorFormatter) diff --git a/test/internals/validation.test.js b/test/internals/validation.test.js index 3e1ce6b74d6..c56ebdc3654 100644 --- a/test/internals/validation.test.js +++ b/test/internals/validation.test.js @@ -63,7 +63,7 @@ test('build schema - output schema', t => { t.equal(typeof opts[symbols.responseSchema]['201'], 'function') }) -test('build schema - payload schema', t => { +test('build schema - body schema', t => { t.plan(1) const opts = { schema: { @@ -79,6 +79,32 @@ test('build schema - payload schema', t => { t.equal(typeof opts[symbols.bodySchema], 'function') }) +test('build schema - body with multiple content type schemas', t => { + t.plan(2) + const opts = { + schema: { + body: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + }, + 'text/plain': { + schema: { type: 'string' } + } + } + } + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.type(opts[symbols.bodySchema]['application/json'], 'function') + t.type(opts[symbols.bodySchema]['text/plain'], 'function') +}) + test('build schema - avoid repeated normalize schema', t => { t.plan(3) const serverConfig = { diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index 08911bfc209..ddcb54b079f 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -190,6 +190,27 @@ test('Should throw of the schema does not exists in input', t => { }) }) +test('Should throw if schema is missing for content type', t => { + t.plan(2) + + const fastify = Fastify() + fastify.post('/', { + handler: echoBody, + schema: { + body: { + content: { + 'application/json': {} + } + } + } + }) + + fastify.ready(err => { + t.equal(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + t.equal(err.message, "Schema is missing for the content type 'application/json'") + }) +}) + test('Should throw of the schema does not exists in output', t => { t.plan(2) const fastify = Fastify() diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index 4d75080ef6a..879f85a518a 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -105,6 +105,142 @@ test('Basic validation test', t => { }) }) +test('Different schema per content type', t => { + t.plan(12) + + const fastify = Fastify() + fastify.addContentTypeParser('application/octet-stream', { + parseAs: 'buffer' + }, async function (_, payload) { + return payload + }) + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: schemaArtist + }, + 'application/octet-stream': { + schema: {} // Skip validation + }, + 'text/plain': { + schema: { type: 'string' } + } + } + } + } + }, async function (req, reply) { + return reply.send(req.body) + }) + + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: { + name: 'michelangelo', + work: 'sculptor, painter, architect and poet' + } + }, (err, res) => { + t.error(err) + t.same(JSON.parse(res.payload).name, 'michelangelo') + t.equal(res.statusCode, 200) + }) + + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: { name: 'michelangelo' } + }, (err, res) => { + t.error(err) + t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) + t.equal(res.statusCode, 400) + }) + + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'application/octet-stream' }, + body: Buffer.from('AAAAAAAA') + }, (err, res) => { + t.error(err) + t.same(res.payload, 'AAAAAAAA') + t.equal(res.statusCode, 200) + }) + + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'text/plain' }, + body: 'AAAAAAAA' + }, (err, res) => { + t.error(err) + t.same(res.payload, 'AAAAAAAA') + t.equal(res.statusCode, 200) + }) +}) + +test('Skip validation if no schema for content type', t => { + t.plan(3) + + const fastify = Fastify() + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: schemaArtist + } + // No schema for 'text/plain' + } + } + } + }, async function (req, reply) { + return reply.send(req.body) + }) + + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'text/plain' }, + body: 'AAAAAAAA' + }, (err, res) => { + t.error(err) + t.same(res.payload, 'AAAAAAAA') + t.equal(res.statusCode, 200) + }) +}) + +test('Skip validation if no content type schemas', t => { + t.plan(3) + + const fastify = Fastify() + fastify.post('/', { + schema: { + body: { + content: { + // No schemas + } + } + } + }, async function (req, reply) { + return reply.send(req.body) + }) + + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'text/plain' }, + body: 'AAAAAAAA' + }, (err, res) => { + t.error(err) + t.same(res.payload, 'AAAAAAAA') + t.equal(res.statusCode, 200) + }) +}) + test('External AJV instance', t => { t.plan(5) diff --git a/test/types/schema.test-d.ts b/test/types/schema.test-d.ts index fb3f13cc3dd..8618a03665e 100644 --- a/test/types/schema.test-d.ts +++ b/test/types/schema.test-d.ts @@ -20,6 +20,25 @@ expectAssignable(server.get( () => { } )) +expectAssignable(server.post( + '/multiple-content-schema', + { + schema: { + body: { + content: { + 'application/json': { + schema: { type: 'object' } + }, + 'text/plain': { + schema: { type: 'string' } + } + } + } + } + }, + () => { } +)) + expectAssignable(server.get( '/empty-schema', { From df8261ee4830009f70be4aed9b667d4ee506b9b5 Mon Sep 17 00:00:00 2001 From: Cangit Date: Wed, 10 Jul 2024 09:50:07 +0200 Subject: [PATCH 0707/1295] chore: remove dc-polyfill (#5560) --- docs/Reference/Hooks.md | 14 +++++--------- fastify.js | 2 +- lib/handleRequest.js | 2 +- lib/wrapThenable.js | 2 +- package.json | 1 - test/diagnostics-channel/404.test.js | 2 +- .../async-delay-request.test.js | 2 +- test/diagnostics-channel/async-request.test.js | 2 +- .../error-before-handler.test.js | 2 +- test/diagnostics-channel/error-request.test.js | 2 +- test/diagnostics-channel/error-status.test.js | 2 +- test/diagnostics-channel/init.test.js | 4 ++-- .../diagnostics-channel/sync-delay-request.test.js | 2 +- .../diagnostics-channel/sync-request-reply.test.js | 2 +- test/diagnostics-channel/sync-request.test.js | 2 +- 15 files changed, 19 insertions(+), 24 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index a5eecf55206..13a07d02a0b 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -828,13 +828,6 @@ consider creating a custom [Plugin](./Plugins.md) instead. ## Diagnostics Channel Hooks -> **Note:** The `diagnostics_channel` is currently experimental on Node.js, so -> its API is subject to change even in semver-patch releases of Node.js. As some -> versions of Node.js are supported by Fastify where `diagnostics_channel` is -> unavailable, or with an incomplete feature set, the hook uses the -> [dc-polyfill](https://www.npmjs.com/package/dc-polyfill) package to provide a -> polyfill. - One [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html) publish event, `'fastify.initialization'`, happens at initialization time. The Fastify instance is passed into the hook as a property of the object passed in. @@ -848,7 +841,7 @@ tools first" fashion. ```js const tracer = /* retrieved from elsewhere in the package */ -const dc = require('node:diagnostics_channel') // or require('dc-polyfill') +const dc = require('node:diagnostics_channel') const channel = dc.channel('fastify.initialization') const spans = new WeakMap() @@ -867,6 +860,9 @@ channel.subscribe(function ({ fastify }) { }) ``` +> **Note:** The TracingChannel class API is currently experimental and may undergo +> breaking changes even in semver-patch releases of Node.js. + Five other events are published on a per-request basis following the [Tracing Channel](https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel) nomenclature. The list of the channel names and the event they receive is: @@ -895,7 +891,7 @@ associated with the request's failure. These events can be received like so: ```js -const dc = require('node:diagnostics_channel') // or require('dc-polyfill') +const dc = require('node:diagnostics_channel') const channel = dc.channel('tracing:fastify.request.handler:start') channel.subscribe((msg) => { console.log(msg.request, msg.reply) diff --git a/fastify.js b/fastify.js index 00cb36a4df7..69857462b5e 100644 --- a/fastify.js +++ b/fastify.js @@ -4,7 +4,7 @@ const VERSION = '5.0.0-alpha.3' const Avvio = require('avvio') const http = require('node:http') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') let lightMyRequest const { diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 6b0744b1a00..8babde8c539 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -1,7 +1,7 @@ 'use strict' const { bodylessMethods, bodyMethods } = require('./httpMethods') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const { validate: validateSchema } = require('./validation') const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') const wrapThenable = require('./wrapThenable') diff --git a/lib/wrapThenable.js b/lib/wrapThenable.js index d9283fceab6..8d3476557d2 100644 --- a/lib/wrapThenable.js +++ b/lib/wrapThenable.js @@ -5,7 +5,7 @@ const { kReplyHijacked } = require('./symbols') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const channels = diagnostics.tracingChannel('fastify.request.handler') function wrapThenable (thenable, reply, store) { diff --git a/package.json b/package.json index 6750a6b99c0..8c296f5a972 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,6 @@ "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", "avvio": "^8.3.0", - "dc-polyfill": "^0.1.6", "fast-json-stringify": "^6.0.0", "find-my-way": "^8.1.0", "light-my-request": "^5.13.0", diff --git a/test/diagnostics-channel/404.test.js b/test/diagnostics-channel/404.test.js index 5fc2ac2d4e8..2832dd71b1d 100644 --- a/test/diagnostics-channel/404.test.js +++ b/test/diagnostics-channel/404.test.js @@ -1,7 +1,7 @@ 'use strict' const t = require('tap') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') diff --git a/test/diagnostics-channel/async-delay-request.test.js b/test/diagnostics-channel/async-delay-request.test.js index 1e8677722cb..bf64f1bc6ef 100644 --- a/test/diagnostics-channel/async-delay-request.test.js +++ b/test/diagnostics-channel/async-delay-request.test.js @@ -1,7 +1,7 @@ 'use strict' const t = require('tap') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') diff --git a/test/diagnostics-channel/async-request.test.js b/test/diagnostics-channel/async-request.test.js index 9e6000b9b5b..86e2b5cf6f6 100644 --- a/test/diagnostics-channel/async-request.test.js +++ b/test/diagnostics-channel/async-request.test.js @@ -1,7 +1,7 @@ 'use strict' const t = require('tap') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') diff --git a/test/diagnostics-channel/error-before-handler.test.js b/test/diagnostics-channel/error-before-handler.test.js index 0d59c75e60d..11b5287cd55 100644 --- a/test/diagnostics-channel/error-before-handler.test.js +++ b/test/diagnostics-channel/error-before-handler.test.js @@ -1,7 +1,7 @@ 'use strict' const t = require('tap') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const test = t.test require('../../lib/hooks').onSendHookRunner = function Stub () {} const Request = require('../../lib/request') diff --git a/test/diagnostics-channel/error-request.test.js b/test/diagnostics-channel/error-request.test.js index 7963e267bde..8af11c543e7 100644 --- a/test/diagnostics-channel/error-request.test.js +++ b/test/diagnostics-channel/error-request.test.js @@ -1,7 +1,7 @@ 'use strict' const t = require('tap') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') diff --git a/test/diagnostics-channel/error-status.test.js b/test/diagnostics-channel/error-status.test.js index 6047b625f79..dbded479d95 100644 --- a/test/diagnostics-channel/error-status.test.js +++ b/test/diagnostics-channel/error-status.test.js @@ -4,7 +4,7 @@ const t = require('tap') const test = t.test const Fastify = require('../..') const statusCodes = require('node:http').STATUS_CODES -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') test('Error.status property support', t => { t.plan(4) diff --git a/test/diagnostics-channel/init.test.js b/test/diagnostics-channel/init.test.js index e2ab45e0a9b..a6b5e8646ae 100644 --- a/test/diagnostics-channel/init.test.js +++ b/test/diagnostics-channel/init.test.js @@ -24,7 +24,7 @@ test('diagnostics_channel when present and subscribers', t => { } const fastify = proxyquire('../../fastify', { - 'dc-polyfill': diagnostics + 'node:diagnostics_channel': diagnostics })() t.equal(fastifyInHook, fastify) }) @@ -46,6 +46,6 @@ test('diagnostics_channel when present and no subscribers', t => { } proxyquire('../../fastify', { - 'dc-polyfill': diagnostics + 'node:diagnostics_channel': diagnostics })() }) diff --git a/test/diagnostics-channel/sync-delay-request.test.js b/test/diagnostics-channel/sync-delay-request.test.js index 29e7442180a..8a712b2820d 100644 --- a/test/diagnostics-channel/sync-delay-request.test.js +++ b/test/diagnostics-channel/sync-delay-request.test.js @@ -1,7 +1,7 @@ 'use strict' const t = require('tap') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') diff --git a/test/diagnostics-channel/sync-request-reply.test.js b/test/diagnostics-channel/sync-request-reply.test.js index 66efbad0c34..02811e83cd6 100644 --- a/test/diagnostics-channel/sync-request-reply.test.js +++ b/test/diagnostics-channel/sync-request-reply.test.js @@ -1,7 +1,7 @@ 'use strict' const t = require('tap') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') diff --git a/test/diagnostics-channel/sync-request.test.js b/test/diagnostics-channel/sync-request.test.js index 749177464e8..7377228f54c 100644 --- a/test/diagnostics-channel/sync-request.test.js +++ b/test/diagnostics-channel/sync-request.test.js @@ -1,7 +1,7 @@ 'use strict' const t = require('tap') -const diagnostics = require('dc-polyfill') +const diagnostics = require('node:diagnostics_channel') const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') From 24c1543cd08b0a3d73b250b20a0deb15cb607dc3 Mon Sep 17 00:00:00 2001 From: mch-dsk <145979613+mch-dsk@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:38:25 +0200 Subject: [PATCH 0708/1295] fix: res serializers not given reply (#5561) --- lib/reply.js | 2 +- test/stream-serializers.test.js | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 test/stream-serializers.test.js diff --git a/lib/reply.js b/lib/reply.js index 26baf7c7b9f..e524c1ef76b 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -724,7 +724,7 @@ function sendStream (payload, res, reply) { if (res.headersSent || reply.request.raw.aborted === true) { if (!errorLogged) { errorLogged = true - logStreamError(reply.log, err, res) + logStreamError(reply.log, err, reply) } res.destroy() } else { diff --git a/test/stream-serializers.test.js b/test/stream-serializers.test.js new file mode 100644 index 00000000000..13f9e49c97a --- /dev/null +++ b/test/stream-serializers.test.js @@ -0,0 +1,37 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('..') +const Reply = require('../lib/reply') + +test('should serialize reply when response stream is ended', t => { + t.plan(3) + const stream = require('node:stream') + const fastify = Fastify({ + logger: { + serializers: { + res (reply) { + t.type(reply, Reply) + return reply + } + } + } + }) + + fastify.get('/error', function (req, reply) { + const reallyLongStream = new stream.Readable({ + read: () => { } + }) + reply.code(200).send(reallyLongStream) + reply.raw.end(Buffer.from('hello\n')) + }) + + fastify.inject({ + url: '/error', + method: 'GET' + }, (err) => { + t.error(err) + fastify.close() + }) +}) From 100ef435b656afb4bfeb712ee4509a2b1b6d78ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:30:29 +0000 Subject: [PATCH 0709/1295] chore: Bump process-warning in the dependencies-major group (#5568) Bumps the dependencies-major group with 1 update: [process-warning](https://github.com/fastify/process-warning). Updates `process-warning` from 3.0.0 to 4.0.0 - [Release notes](https://github.com/fastify/process-warning/releases) - [Commits](https://github.com/fastify/process-warning/compare/v3.0.0...v4.0.0) --- updated-dependencies: - dependency-name: process-warning dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c296f5a972..4a0e6e6c7b4 100644 --- a/package.json +++ b/package.json @@ -199,7 +199,7 @@ "find-my-way": "^8.1.0", "light-my-request": "^5.13.0", "pino": "^9.0.0", - "process-warning": "^3.0.0", + "process-warning": "^4.0.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.1", "secure-json-parse": "^2.7.0", From 3eab07e4656f43a3499466798dfa40d26c7fab42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:31:54 +0000 Subject: [PATCH 0710/1295] chore: Bump the dev-dependencies group with 2 updates (#5569) Bumps the dev-dependencies group with 2 updates: [fluent-json-schema](https://github.com/fastify/fluent-json-schema) and [tap](https://github.com/tapjs/tapjs). Updates `fluent-json-schema` from 4.2.1 to 5.0.0 - [Release notes](https://github.com/fastify/fluent-json-schema/releases) - [Commits](https://github.com/fastify/fluent-json-schema/commits) Updates `tap` from 20.0.3 to 21.0.0 - [Release notes](https://github.com/tapjs/tapjs/releases) - [Commits](https://github.com/tapjs/tapjs/compare/tap@20.0.3...tap@21.0.0) --- updated-dependencies: - dependency-name: fluent-json-schema dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies - dependency-name: tap dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4a0e6e6c7b4..5720adae834 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,7 @@ "eslint": "^9.0.0", "fast-json-body": "^1.1.0", "fastify-plugin": "^4.5.1", - "fluent-json-schema": "^4.2.1", + "fluent-json-schema": "^5.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", "joi": "^17.12.3", @@ -182,7 +182,7 @@ "send": "^0.18.0", "simple-get": "^4.0.1", "split2": "^4.2.0", - "tap": "^20.0.3", + "tap": "^21.0.0", "tsd": "^0.31.0", "typescript": "^5.4.5", "undici": "^6.13.0", From ba11f46e3d8387b8aac4f97763ed4addfa86dd7b Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Tue, 16 Jul 2024 22:27:25 +0800 Subject: [PATCH 0711/1295] chore: allow ! in PR title (#5571) --- .github/workflows/pull-request-title.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request-title.yml b/.github/workflows/pull-request-title.yml index ee0f7a630e6..62df7a1c07e 100644 --- a/.github/workflows/pull-request-title.yml +++ b/.github/workflows/pull-request-title.yml @@ -9,5 +9,5 @@ jobs: steps: - uses: fastify/action-pr-title@v0 with: - regex: '/^(build|chore|ci|docs|feat|types|fix|perf|refactor|style|test)(?:\([^\):]*\))?:\s/' + regex: '/^(build|chore|ci|docs|feat|types|fix|perf|refactor|style|test)(?:\([^\):]*\))?!?:\s/' github-token: ${{ github.token }} From 00aadd2ce6eaf5c5eb1dda068be539d03750544d Mon Sep 17 00:00:00 2001 From: Kunal Sharma Date: Mon, 22 Jul 2024 16:08:03 +0530 Subject: [PATCH 0712/1295] docs: add @pybot/fastify-autoload to comm plugins (#5579) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index afc84ba4297..b9c646e24c7 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -213,6 +213,8 @@ section. Fast sodium-based crypto for @mgcrea/fastify-session - [`@mgcrea/pino-pretty-compact`](https://github.com/mgcrea/pino-pretty-compact) A custom compact pino-base prettifier +- [`@pybot/fastify-autoload`](https://github.com/kunal097/fastify-autoload) + Plugin to generate routes automatically with valid json content - [`@scalar/fastify-api-reference`](https://github.com/scalar/scalar/tree/main/packages/fastify-api-reference) Beautiful OpenAPI/Swagger API references for Fastify - [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs) From ba276fd56b6c15e5602ec5d6f24e02f400d8bb93 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 22 Jul 2024 16:52:57 +0200 Subject: [PATCH 0713/1295] feat: customize http methods (#5567) --- docs/Reference/Routes.md | 25 +--- docs/Reference/Server.md | 28 +++++ fastify.js | 92 +++++++++------ lib/handleRequest.js | 8 +- lib/httpMethods.js | 40 ------- lib/route.js | 19 +-- lib/symbols.js | 1 + test/{ => http-methods}/copy.test.js | 3 +- test/http-methods/custom-http-methods.test.js | 111 ++++++++++++++++++ test/{ => http-methods}/get.test.js | 2 +- test/{ => http-methods}/head.test.js | 2 +- test/{ => http-methods}/lock.test.js | 3 +- test/{ => http-methods}/mkcalendar.test.js | 3 +- test/{ => http-methods}/mkcol.test.js | 3 +- test/{ => http-methods}/move.test.js | 3 +- test/{ => http-methods}/propfind.test.js | 3 +- test/{ => http-methods}/proppatch.test.js | 3 +- test/{ => http-methods}/report.test.js | 3 +- test/{ => http-methods}/search.test.js | 3 +- test/{ => http-methods}/trace.test.js | 3 +- test/{ => http-methods}/unlock.test.js | 3 +- test/internals/all.test.js | 6 +- test/method-missing.test.js | 24 ---- test/route-shorthand.test.js | 4 +- 24 files changed, 243 insertions(+), 152 deletions(-) delete mode 100644 lib/httpMethods.js rename test/{ => http-methods}/copy.test.js (91%) create mode 100644 test/http-methods/custom-http-methods.test.js rename test/{ => http-methods}/get.test.js (99%) rename test/{ => http-methods}/head.test.js (99%) rename test/{ => http-methods}/lock.test.js (97%) rename test/{ => http-methods}/mkcalendar.test.js (98%) rename test/{ => http-methods}/mkcol.test.js (91%) rename test/{ => http-methods}/move.test.js (93%) rename test/{ => http-methods}/propfind.test.js (97%) rename test/{ => http-methods}/proppatch.test.js (97%) rename test/{ => http-methods}/report.test.js (97%) rename test/{ => http-methods}/search.test.js (98%) rename test/{ => http-methods}/trace.test.js (81%) rename test/{ => http-methods}/unlock.test.js (91%) delete mode 100644 test/method-missing.test.js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 46b4a3dbd5e..9fcc2105963 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -32,10 +32,9 @@ fastify.route(options) ### Routes options -* `method`: currently it supports `'DELETE'`, `'GET'`, `'HEAD'`, `'PATCH'`, - `'POST'`, `'PUT'`, `'OPTIONS'`, `'SEARCH'`, `'TRACE'`, `'PROPFIND'`, - `'PROPPATCH'`, `'MKCOL'`, `'COPY'`, `'MOVE'`, `'LOCK'`, `'UNLOCK'`, - `'REPORT'` and `'MKCALENDAR'`. +* `method`: currently it supports `GET`, `HEAD`, `TRACE`, `DELETE`, + `OPTIONS`, `PATCH`, `PUT` and `POST`. To accept more methods, + the [`addHttpMethod`](./Server.md#addHttpMethod) must be used. It could also be an array of methods. * `url`: the path of the URL to match this route (alias: `path`). * `schema`: an object containing the schemas for the request and response. They @@ -190,24 +189,6 @@ The above route declaration is more *Hapi*-like, but if you prefer an `fastify.patch(path, [options], handler)` -`fastify.propfind(path, [options], handler)` - -`fastify.proppatch(path, [options], handler)` - -`fastify.mkcol(path, [options], handler)` - -`fastify.copy(path, [options], handler)` - -`fastify.move(path, [options], handler)` - -`fastify.lock(path, [options], handler)` - -`fastify.unlock(path, [options], handler)` - -`fastify.trace(path, [options], handler)` - -`fastify.search(path, [options], handler)` - Example: ```js const opts = { diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 421a4b66ab9..fcd69f8f9ae 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -68,6 +68,7 @@ describes the properties available in that options object. - [log](#log) - [version](#version) - [inject](#inject) + - [addHttpMethod](#addHttpMethod) - [addSchema](#addschema) - [getSchemas](#getschemas) - [getSchema](#getschema) @@ -1305,6 +1306,33 @@ used by plugins. Fake HTTP injection (for testing purposes) [here](../Guides/Testing.md#benefits-of-using-fastifyinject). +#### addHttpMethod + + +Fastify supports the `GET`, `HEAD`, `TRACE`, `DELETE`, `OPTIONS`, +`PATCH`, `PUT` and `POST` HTTP methods by default. +The `addHttpMethod` method allows to add any non standard HTTP +methods to the server that are [supported by Node.js](https://nodejs.org/api/http.html#httpmethods). + +```js +// Add a new HTTP method called 'MKCOL' that supports a request body +fastify.addHttpMethod('MKCOL', { hasBody: true, }) + +// Add a new HTTP method called 'COPY' that does not support a request body +fastify.addHttpMethod('COPY') +``` + +After calling `addHttpMethod`, it is possible to use the route shorthand +methods to define routes for the new HTTP method: + +```js +fastify.addHttpMethod('MKCOL', { hasBody: true }) +fastify.mkcol('/', (req, reply) => { + // Handle the 'MKCOL' request +}) +``` + + #### addSchema diff --git a/fastify.js b/fastify.js index 69857462b5e..0936c357c22 100644 --- a/fastify.js +++ b/fastify.js @@ -12,6 +12,7 @@ const { kChildren, kServerBindings, kBodyLimit, + kSupportedHTTPMethods, kRoutePrefix, kLogLevel, kLogSerializers, @@ -37,7 +38,6 @@ const { createServer } = require('./lib/server') const Reply = require('./lib/reply') const Request = require('./lib/request') const Context = require('./lib/context.js') -const { supportedMethods } = require('./lib/httpMethods') const decorator = require('./lib/decorate') const ContentTypeParser = require('./lib/contentTypeParser') const SchemaController = require('./lib/schema-controller') @@ -73,7 +73,8 @@ const { FST_ERR_REOPENED_CLOSE_SERVER, FST_ERR_ROUTE_REWRITE_NOT_STR, FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN, - FST_ERR_ERROR_HANDLER_NOT_FN + FST_ERR_ERROR_HANDLER_NOT_FN, + FST_ERR_ROUTE_METHOD_INVALID } = errorCodes const { buildErrorHandler } = require('./lib/error-handler.js') @@ -228,6 +229,22 @@ function fastify (options) { readyPromise: null }, [kKeepAliveConnections]: keepAliveConnections, + [kSupportedHTTPMethods]: { + bodyless: new Set([ + // Standard + 'GET', + 'HEAD', + 'TRACE' + ]), + bodywith: new Set([ + // Standard + 'DELETE', + 'OPTIONS', + 'PATCH', + 'PUT', + 'POST' + ]) + }, [kOptions]: options, [kChildren]: [], [kServerBindings]: [], @@ -265,6 +282,9 @@ function fastify (options) { head: function _head (url, options, handler) { return router.prepareRoute.call(this, { method: 'HEAD', url, options, handler }) }, + trace: function _trace (url, options, handler) { + return router.prepareRoute.call(this, { method: 'TRACE', url, options, handler }) + }, patch: function _patch (url, options, handler) { return router.prepareRoute.call(this, { method: 'PATCH', url, options, handler }) }, @@ -277,41 +297,8 @@ function fastify (options) { options: function _options (url, options, handler) { return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler }) }, - propfind: function _propfind (url, options, handler) { - return router.prepareRoute.call(this, { method: 'PROPFIND', url, options, handler }) - }, - proppatch: function _proppatch (url, options, handler) { - return router.prepareRoute.call(this, { method: 'PROPPATCH', url, options, handler }) - }, - mkcalendar: function _mkcalendar (url, options, handler) { - return router.prepareRoute.call(this, { method: 'MKCALENDAR', url, options, handler }) - }, - mkcol: function _mkcol (url, options, handler) { - return router.prepareRoute.call(this, { method: 'MKCOL', url, options, handler }) - }, - copy: function _copy (url, options, handler) { - return router.prepareRoute.call(this, { method: 'COPY', url, options, handler }) - }, - move: function _move (url, options, handler) { - return router.prepareRoute.call(this, { method: 'MOVE', url, options, handler }) - }, - lock: function _lock (url, options, handler) { - return router.prepareRoute.call(this, { method: 'LOCK', url, options, handler }) - }, - unlock: function _unlock (url, options, handler) { - return router.prepareRoute.call(this, { method: 'UNLOCK', url, options, handler }) - }, - trace: function _trace (url, options, handler) { - return router.prepareRoute.call(this, { method: 'TRACE', url, options, handler }) - }, - report: function _mkcalendar (url, options, handler) { - return router.prepareRoute.call(this, { method: 'REPORT', url, options, handler }) - }, - search: function _search (url, options, handler) { - return router.prepareRoute.call(this, { method: 'SEARCH', url, options, handler }) - }, all: function _all (url, options, handler) { - return router.prepareRoute.call(this, { method: supportedMethods, url, options, handler }) + return router.prepareRoute.call(this, { method: this.supportedMethods, url, options, handler }) }, // extended route route: function _route (options) { @@ -375,6 +362,7 @@ function fastify (options) { decorateRequest: decorator.decorateRequest, hasRequestDecorator: decorator.existRequest, hasReplyDecorator: decorator.existReply, + addHttpMethod, // fake http injection inject, // pretty print of the registered routes @@ -442,6 +430,15 @@ function fastify (options) { genReqId: { configurable: true, get () { return this[kGenReqId] } + }, + supportedMethods: { + configurable: false, + get () { + return [ + ...this[kSupportedHTTPMethods].bodyless, + ...this[kSupportedHTTPMethods].bodywith + ] + } } }) @@ -917,6 +914,29 @@ function fastify (options) { this[kGenReqId] = reqIdGenFactory(this[kOptions].requestIdHeader, func) return this } + + function addHttpMethod (method, { hasBody = false } = {}) { + if (typeof method !== 'string' || http.METHODS.indexOf(method) === -1) { + throw new FST_ERR_ROUTE_METHOD_INVALID() + } + + if (hasBody === true) { + this[kSupportedHTTPMethods].bodywith.add(method) + this[kSupportedHTTPMethods].bodyless.delete(method) + } else { + this[kSupportedHTTPMethods].bodywith.delete(method) + this[kSupportedHTTPMethods].bodyless.add(method) + } + + const _method = method.toLowerCase() + if (!this.hasDecorator(_method)) { + this.decorate(_method, function (url, options, handler) { + return router.prepareRoute.call(this, { method, url, options, handler }) + }) + } + + return this + } } function validateSchemaErrorFormatter (schemaErrorFormatter) { diff --git a/lib/handleRequest.js b/lib/handleRequest.js index 8babde8c539..ceb3800f939 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -1,6 +1,5 @@ 'use strict' -const { bodylessMethods, bodyMethods } = require('./httpMethods') const diagnostics = require('node:diagnostics_channel') const { validate: validateSchema } = require('./validation') const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') @@ -8,7 +7,8 @@ const wrapThenable = require('./wrapThenable') const { kReplyIsError, kRouteContext, - kFourOhFourContext + kFourOhFourContext, + kSupportedHTTPMethods } = require('./symbols') const channels = diagnostics.tracingChannel('fastify.request.handler') @@ -25,12 +25,12 @@ function handleRequest (err, request, reply) { const headers = request.headers const context = request[kRouteContext] - if (bodylessMethods.has(method)) { + if (this[kSupportedHTTPMethods].bodyless.has(method)) { handler(request, reply) return } - if (bodyMethods.has(method)) { + if (this[kSupportedHTTPMethods].bodywith.has(method)) { const contentType = headers['content-type'] const contentLength = headers['content-length'] const transferEncoding = headers['transfer-encoding'] diff --git a/lib/httpMethods.js b/lib/httpMethods.js deleted file mode 100644 index 6147f9c5c79..00000000000 --- a/lib/httpMethods.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict' - -const bodylessMethods = new Set([ - // Standard - 'GET', - 'HEAD', - 'TRACE', - - // WebDAV - 'UNLOCK' -]) - -const bodyMethods = new Set([ - // Standard - 'DELETE', - 'OPTIONS', - 'PATCH', - 'PUT', - 'POST', - - // WebDAV - 'COPY', - 'LOCK', - 'MOVE', - 'MKCOL', - 'PROPFIND', - 'PROPPATCH', - 'REPORT', - 'SEARCH', - 'MKCALENDAR' -]) - -module.exports = { - bodylessMethods, - bodyMethods, - supportedMethods: [ - ...bodylessMethods, - ...bodyMethods - ] -} diff --git a/lib/route.js b/lib/route.js index 4e4ad0f2d13..f2e11b498e6 100644 --- a/lib/route.js +++ b/lib/route.js @@ -4,7 +4,6 @@ const FindMyWay = require('find-my-way') const Context = require('./context') const handleRequest = require('./handleRequest') const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks') -const { supportedMethods } = require('./httpMethods') const { normalizeSchema } = require('./schemas') const { parseHeadOnSendHandlers } = require('./headRoute') const { @@ -36,6 +35,7 @@ const { const { kRoutePrefix, + kSupportedHTTPMethods, kLogLevel, kLogSerializers, kHooks, @@ -207,12 +207,12 @@ function buildRouting (options) { if (Array.isArray(opts.method)) { // eslint-disable-next-line no-var for (var i = 0; i < opts.method.length; ++i) { - opts.method[i] = normalizeAndValidateMethod(opts.method[i]) - validateSchemaBodyOption(opts.method[i], path, opts.schema) + opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i]) + validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema) } } else { - opts.method = normalizeAndValidateMethod(opts.method) - validateSchemaBodyOption(opts.method, path, opts.schema) + opts.method = normalizeAndValidateMethod.call(this, opts.method) + validateSchemaBodyOption.call(this, opts.method, path, opts.schema) } if (!opts.handler) { @@ -549,7 +549,8 @@ function normalizeAndValidateMethod (method) { throw new FST_ERR_ROUTE_METHOD_INVALID() } method = method.toUpperCase() - if (supportedMethods.indexOf(method) === -1) { + if (!this[kSupportedHTTPMethods].bodyless.has(method) && + !this[kSupportedHTTPMethods].bodywith.has(method)) { throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method) } @@ -557,7 +558,7 @@ function normalizeAndValidateMethod (method) { } function validateSchemaBodyOption (method, path, schema) { - if ((method === 'GET' || method === 'HEAD') && schema && schema.body) { + if (this[kSupportedHTTPMethods].bodyless.has(method) && schema?.body) { throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path) } } @@ -580,9 +581,9 @@ function runPreParsing (err, request, reply) { request[kRequestPayloadStream] = request.raw if (request[kRouteContext].preParsing !== null) { - preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest) + preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest.bind(request.server)) } else { - handleRequest(null, request, reply) + handleRequest.call(request.server, null, request, reply) } } diff --git a/lib/symbols.js b/lib/symbols.js index e31a66552b7..58992c3a878 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -5,6 +5,7 @@ const keys = { kChildren: Symbol('fastify.children'), kServerBindings: Symbol('fastify.serverBindings'), kBodyLimit: Symbol('fastify.bodyLimit'), + kSupportedHTTPMethods: Symbol('fastify.acceptedHTTPMethods'), kRoutePrefix: Symbol('fastify.routePrefix'), kLogLevel: Symbol('fastify.logLevel'), kLogSerializers: Symbol('fastify.logSerializers'), diff --git a/test/copy.test.js b/test/http-methods/copy.test.js similarity index 91% rename from test/copy.test.js rename to test/http-methods/copy.test.js index cdc4b2b18ac..2865f3c4f4c 100644 --- a/test/copy.test.js +++ b/test/http-methods/copy.test.js @@ -3,7 +3,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../fastify')() +fastify.addHttpMethod('COPY') test('can be created - copy', t => { t.plan(1) diff --git a/test/http-methods/custom-http-methods.test.js b/test/http-methods/custom-http-methods.test.js new file mode 100644 index 00000000000..fe27d7c1da0 --- /dev/null +++ b/test/http-methods/custom-http-methods.test.js @@ -0,0 +1,111 @@ +'use strict' + +const http = require('node:http') +const { test } = require('tap') +const Fastify = require('../../fastify') + +function addEcho (fastify, method) { + fastify.route({ + method, + url: '/', + handler: function (req, reply) { + reply.send(req.body) + } + }) +} + +test('missing method from http client', t => { + t.plan(2) + const fastify = Fastify() + + fastify.listen({ port: 3000 }, (err) => { + t.error(err) + + const port = fastify.server.address().port + const req = http.request({ + port, + method: 'REBIND', + path: '/' + }, (res) => { + t.equal(res.statusCode, 404) + fastify.close() + }) + + req.end() + }) +}) + +test('addHttpMethod increase the supported HTTP methods supported', t => { + t.plan(8) + const app = Fastify() + + t.throws(() => { addEcho(app, 'REBIND') }, /REBIND method is not supported./) + t.notOk(app.supportedMethods.includes('REBIND')) + t.notOk(app.rebind) + + app.addHttpMethod('REBIND') + t.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + t.ok(app.supportedMethods.includes('REBIND')) + t.ok(app.rebind) + + app.rebind('/foo', () => 'hello') + + app.inject({ + method: 'REBIND', + url: '/foo' + }, (err, response) => { + t.error(err) + t.equal(response.payload, 'hello') + }) +}) + +test('addHttpMethod adds a new custom method without body', t => { + t.plan(3) + const app = Fastify() + + t.throws(() => { addEcho(app, 'REBIND') }, /REBIND method is not supported./) + + app.addHttpMethod('REBIND') + t.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + + t.throws(() => { + app.route({ + url: '/', + method: 'REBIND', + schema: { + body: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + }, + handler: function (req, reply) { + reply.send(req.body) + } + }) + }, /Body validation schema for REBIND:\/ route is not supported!/) +}) + +test('addHttpMethod adds a new custom method with body', t => { + t.plan(3) + const app = Fastify() + + app.addHttpMethod('REBIND', { hasBody: true }) + t.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + + app.inject({ + method: 'REBIND', + url: '/', + payload: { hello: 'world' } + }, (err, response) => { + t.error(err) + t.same(response.json(), { hello: 'world' }) + }) +}) + +test('addHttpMethod rejects fake http method', t => { + t.plan(1) + const fastify = Fastify() + t.throws(() => { fastify.addHttpMethod('FOOO') }, /Provided method is invalid!/) +}) diff --git a/test/get.test.js b/test/http-methods/get.test.js similarity index 99% rename from test/get.test.js rename to test/http-methods/get.test.js index 9c12c82940a..49a04f72564 100644 --- a/test/get.test.js +++ b/test/http-methods/get.test.js @@ -3,7 +3,7 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../fastify')() const schema = { schema: { diff --git a/test/head.test.js b/test/http-methods/head.test.js similarity index 99% rename from test/head.test.js rename to test/http-methods/head.test.js index 3ef40e6ac7a..1a748efb8b5 100644 --- a/test/head.test.js +++ b/test/http-methods/head.test.js @@ -3,7 +3,7 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../fastify')() const schema = { schema: { diff --git a/test/lock.test.js b/test/http-methods/lock.test.js similarity index 97% rename from test/lock.test.js rename to test/http-methods/lock.test.js index f67e40cf845..2a06b1b11ea 100644 --- a/test/lock.test.js +++ b/test/http-methods/lock.test.js @@ -3,7 +3,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../fastify')() +fastify.addHttpMethod('LOCK', { hasBody: true }) const bodySample = ` diff --git a/test/mkcalendar.test.js b/test/http-methods/mkcalendar.test.js similarity index 98% rename from test/mkcalendar.test.js rename to test/http-methods/mkcalendar.test.js index e5086342c35..510fee98ddd 100644 --- a/test/mkcalendar.test.js +++ b/test/http-methods/mkcalendar.test.js @@ -3,7 +3,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('../fastify')() +const fastify = require('../../fastify')() +fastify.addHttpMethod('MKCALENDAR', { hasBody: true }) const bodySample = ` diff --git a/test/mkcol.test.js b/test/http-methods/mkcol.test.js similarity index 91% rename from test/mkcol.test.js rename to test/http-methods/mkcol.test.js index ace80348519..4c703051368 100644 --- a/test/mkcol.test.js +++ b/test/http-methods/mkcol.test.js @@ -3,7 +3,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../')() +fastify.addHttpMethod('MKCOL') test('can be created - mkcol', t => { t.plan(1) diff --git a/test/move.test.js b/test/http-methods/move.test.js similarity index 93% rename from test/move.test.js rename to test/http-methods/move.test.js index 45f556f09b3..8a0fec0aa25 100644 --- a/test/move.test.js +++ b/test/http-methods/move.test.js @@ -3,7 +3,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../')() +fastify.addHttpMethod('MOVE') test('shorthand - move', t => { t.plan(1) diff --git a/test/propfind.test.js b/test/http-methods/propfind.test.js similarity index 97% rename from test/propfind.test.js rename to test/http-methods/propfind.test.js index 586eb94948f..20cf57ea63f 100644 --- a/test/propfind.test.js +++ b/test/http-methods/propfind.test.js @@ -3,7 +3,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../')() +fastify.addHttpMethod('PROPFIND', { hasBody: true }) const bodySample = ` diff --git a/test/proppatch.test.js b/test/http-methods/proppatch.test.js similarity index 97% rename from test/proppatch.test.js rename to test/http-methods/proppatch.test.js index 402285f33ef..4f81b0c08e0 100644 --- a/test/proppatch.test.js +++ b/test/http-methods/proppatch.test.js @@ -3,7 +3,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../')() +fastify.addHttpMethod('PROPPATCH', { hasBody: true }) const bodySample = ` diff --git a/test/search.test.js b/test/http-methods/search.test.js similarity index 98% rename from test/search.test.js rename to test/http-methods/search.test.js index 67a95c29b4c..ff237327b94 100644 --- a/test/search.test.js +++ b/test/http-methods/search.test.js @@ -3,7 +3,8 @@ const t = require('tap') const sget = require('simple-get').concat const test = t.test -const fastify = require('..')() +const fastify = require('../../fastify')() +fastify.addHttpMethod('SEARCH', { hasBody: true }) const schema = { response: { diff --git a/test/trace.test.js b/test/http-methods/trace.test.js similarity index 81% rename from test/trace.test.js rename to test/http-methods/trace.test.js index e88c5e2085d..f1503ba6f5e 100644 --- a/test/trace.test.js +++ b/test/http-methods/trace.test.js @@ -2,7 +2,8 @@ const t = require('tap') const test = t.test -const fastify = require('..')() +const fastify = require('../../fastify')() +fastify.addHttpMethod('TRACE') test('shorthand - trace', t => { t.plan(1) diff --git a/test/unlock.test.js b/test/http-methods/unlock.test.js similarity index 91% rename from test/unlock.test.js rename to test/http-methods/unlock.test.js index 0d88729495b..909c55089e6 100644 --- a/test/unlock.test.js +++ b/test/http-methods/unlock.test.js @@ -3,7 +3,8 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat -const fastify = require('..')() +const fastify = require('../../fastify')() +fastify.addHttpMethod('UNLOCK') test('can be created - unlock', t => { t.plan(1) diff --git a/test/internals/all.test.js b/test/internals/all.test.js index 73867968cf8..3fc4b100f6d 100644 --- a/test/internals/all.test.js +++ b/test/internals/all.test.js @@ -3,19 +3,19 @@ const t = require('tap') const test = t.test const Fastify = require('../..') -const { supportedMethods } = require('../../lib/httpMethods') test('fastify.all should add all the methods to the same url', t => { + const fastify = Fastify() + const requirePayload = [ 'POST', 'PUT', 'PATCH' ] + const supportedMethods = fastify.supportedMethods t.plan(supportedMethods.length * 2) - const fastify = Fastify() - fastify.all('/', (req, reply) => { reply.send({ method: req.raw.method }) }) diff --git a/test/method-missing.test.js b/test/method-missing.test.js deleted file mode 100644 index bf63bd51826..00000000000 --- a/test/method-missing.test.js +++ /dev/null @@ -1,24 +0,0 @@ -const http = require('http') -const { test } = require('tap') -const Fastify = require('../fastify') - -test('missing method from http client', t => { - t.plan(2) - const fastify = Fastify() - - fastify.listen({ port: 3000 }, (err) => { - t.error(err) - - const port = fastify.server.address().port - const req = http.request({ - port, - method: 'REBIND', - path: '/' - }, (res) => { - t.equal(res.statusCode, 404) - fastify.close() - }) - - req.end() - }) -}) diff --git a/test/route-shorthand.test.js b/test/route-shorthand.test.js index d980f869ca1..1c3ca6048b2 100644 --- a/test/route-shorthand.test.js +++ b/test/route-shorthand.test.js @@ -4,9 +4,11 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat const Fastify = require('../fastify') -const supportedMethods = require('../lib/httpMethods').supportedMethods test('route-shorthand', t => { + const methodsReader = new Fastify() + const supportedMethods = methodsReader.supportedMethods + t.plan(supportedMethods.length + 1) const test = t.test From 9226a4793311c003e30dfddec4fd580deffcd28c Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:04:24 +0800 Subject: [PATCH 0714/1295] refactor(typescript): re-order FastifyReply generic parameters (#5570) * refactor(typescript): re-order FastifyReply generic parameters * docs(typescript): update FastifyReply --- docs/Reference/TypeScript.md | 2 +- fastify.d.ts | 2 +- test/types/instance.test-d.ts | 4 ++-- test/types/reply.test-d.ts | 6 +++--- types/hooks.d.ts | 36 +++++++++++++++++------------------ types/instance.d.ts | 2 +- types/logger.d.ts | 14 +++++++------- types/reply.d.ts | 30 ++++++++++++++--------------- types/route.d.ts | 8 ++++---- 9 files changed, 52 insertions(+), 52 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index b1634dd042a..1421cb4be32 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -1137,7 +1137,7 @@ RawRequestDefaultExpression // -> http2.Http2ServerRequest #### Reply -##### fastify.FastifyReply< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> +##### fastify.FastifyReply<[RequestGeneric][FastifyRequestGenericInterface], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [ContextConfig][ContextConfigGeneric]> [src](https://github.com/fastify/fastify/blob/main/types/reply.d.ts#L32) This interface contains the custom properties that Fastify adds to the standard diff --git a/fastify.d.ts b/fastify.d.ts index 9cb85b72477..1410810e801 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -149,7 +149,7 @@ declare namespace fastify { frameworkErrors?: ( error: FastifyError, req: FastifyRequest, FastifySchema, TypeProvider>, - res: FastifyReply, RawReplyDefaultExpression, RequestGeneric, FastifyContextConfig, SchemaCompiler, TypeProvider> + res: FastifyReply, RawReplyDefaultExpression, FastifyContextConfig, SchemaCompiler, TypeProvider> ) => void, rewriteUrl?: ( // The RawRequestDefaultExpression, RawReplyDefaultExpression, and FastifyTypeProviderDefault parameters diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 780f88072eb..527876b1674 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -89,12 +89,12 @@ interface ReplyPayload { // typed sync error handler server.setErrorHandler((error, request, reply) => { expectType(error) - expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.send) + expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) }) // typed async error handler send server.setErrorHandler(async (error, request, reply) => { expectType(error) - expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.send) + expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) }) // typed async error handler return server.setErrorHandler(async (error, request, reply) => { diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index e668c604775..fa491867a61 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -8,7 +8,7 @@ import { RouteGenericInterface } from '../../types/route' import { ContextConfigDefault, RawReplyDefaultExpression, RawServerDefault } from '../../types/utils' type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string -type DefaultFastifyReplyWithCode = FastifyReply> +type DefaultFastifyReplyWithCode = FastifyReply> const getHandler: RouteHandlerMethod = function (_request, reply) { expectType(reply.raw) @@ -84,8 +84,8 @@ interface InvalidReplyHttpCodes { } const typedHandler: RouteHandler = async (request, reply) => { - expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.send) - expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression, ReplyPayload>)>(reply.code(100).send) + expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) + expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression>)>(reply.code(100).send) } const server = fastify() diff --git a/types/hooks.d.ts b/types/hooks.d.ts index f17f799c971..6e878763810 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -39,7 +39,7 @@ export interface onRequestHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, done: HookHandlerDoneFunction ): void; } @@ -57,7 +57,7 @@ export interface onRequestAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, ): Promise; } @@ -96,7 +96,7 @@ export interface preParsingHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, payload: RequestPayload, done: (err?: TError | null, res?: RequestPayload) => void ): void; @@ -115,7 +115,7 @@ export interface preParsingAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, payload: RequestPayload, ): Promise; } @@ -154,7 +154,7 @@ export interface preValidationHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, done: HookHandlerDoneFunction ): void; } @@ -172,7 +172,7 @@ export interface preValidationAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, ): Promise; } @@ -210,7 +210,7 @@ export interface preHandlerHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, done: HookHandlerDoneFunction ): void; } @@ -228,7 +228,7 @@ export interface preHandlerAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, ): Promise; } @@ -275,7 +275,7 @@ export interface preSerializationHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, payload: PreSerializationPayload, done: DoneFuncWithErrOrRes ): void; @@ -295,7 +295,7 @@ export interface preSerializationAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, payload: PreSerializationPayload ): Promise; } @@ -337,7 +337,7 @@ export interface onSendHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, payload: OnSendPayload, done: DoneFuncWithErrOrRes ): void; @@ -357,7 +357,7 @@ export interface onSendAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, payload: OnSendPayload, ): Promise; } @@ -398,7 +398,7 @@ export interface onResponseHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, done: HookHandlerDoneFunction ): void; } @@ -416,7 +416,7 @@ export interface onResponseAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply + reply: FastifyReply ): Promise; } @@ -455,7 +455,7 @@ export interface onTimeoutHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, done: HookHandlerDoneFunction ): void; } @@ -473,7 +473,7 @@ export interface onTimeoutAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply + reply: FastifyReply ): Promise; } @@ -515,7 +515,7 @@ export interface onErrorHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, error: TError, done: () => void ): void; @@ -535,7 +535,7 @@ export interface onErrorAsyncHookHandler< ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply, error: TError ): Promise; } diff --git a/types/instance.d.ts b/types/instance.d.ts index 68ef39f75bd..b18c2127635 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -467,7 +467,7 @@ export interface FastifyInstance< * Set a function that will be called whenever an error happens */ setErrorHandler( - handler: (this: FastifyInstance, error: TError, request: FastifyRequest, reply: FastifyReply) => any | Promise + handler: (this: FastifyInstance, error: TError, request: FastifyRequest, reply: FastifyReply) => any | Promise ): FastifyInstance; /** diff --git a/types/logger.d.ts b/types/logger.d.ts index 70e3f5ee297..30199a25e18 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -1,11 +1,11 @@ import { FastifyError } from '@fastify/error' -import { RouteGenericInterface } from './route' -import { FastifyRequest } from './request' +import { FastifyInstance } from './instance' import { FastifyReply } from './reply' -import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' -import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' +import { FastifyRequest } from './request' +import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' -import { FastifyInstance } from './instance' +import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' +import { ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' import pino from 'pino' @@ -42,7 +42,7 @@ export type PinoLoggerOptions = pino.LoggerOptions */ export type ResSerializerReply< RawServer extends RawServerBase, - RawReply extends FastifyReply + RawReply extends FastifyReply > = Partial & Pick /** @@ -51,7 +51,7 @@ export type ResSerializerReply< export interface FastifyLoggerOptions< RawServer extends RawServerBase = RawServerDefault, RawRequest extends FastifyRequest, FastifySchema, FastifyTypeProvider> = FastifyRequest, FastifySchema, FastifyTypeProviderDefault>, - RawReply extends FastifyReply, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProvider> = FastifyReply, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault> + RawReply extends FastifyReply, RawReplyDefaultExpression, ContextConfigDefault, FastifySchema, FastifyTypeProvider> = FastifyReply, RawReplyDefaultExpression, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault> > { serializers?: { req?: (req: RawRequest) => { diff --git a/types/reply.d.ts b/types/reply.d.ts index a0a4550f67e..0bfc12a6d88 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -31,10 +31,10 @@ export type ResolveReplyTypeWithRouteGeneric = RawRequestDefaultExpression, RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, - RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, @@ -46,27 +46,27 @@ export interface FastifyReply< log: FastifyBaseLogger; request: FastifyRequest; server: FastifyInstance; - code>(statusCode: Code): FastifyReply>; - status>(statusCode: Code): FastifyReply>; + code>(statusCode: Code): FastifyReply>; + status>(statusCode: Code): FastifyReply>; statusCode: number; sent: boolean; - send(payload?: ReplyType): FastifyReply; - header(key: HttpHeader, value: any): FastifyReply; - headers(values: Partial>): FastifyReply; + send(payload?: ReplyType): FastifyReply; + header(key: HttpHeader, value: any): FastifyReply; + headers(values: Partial>): FastifyReply; getHeader(key: HttpHeader): number | string | string[] | undefined; getHeaders(): Record; - removeHeader(key: HttpHeader): FastifyReply; + removeHeader(key: HttpHeader): FastifyReply; hasHeader(key: HttpHeader): boolean; /** * @deprecated The `reply.redirect()` method has a new signature: `reply.reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. */ - redirect(statusCode: number, url: string): FastifyReply; - redirect(url: string, statusCode?: number): FastifyReply; + redirect(statusCode: number, url: string): FastifyReply; + redirect(url: string, statusCode?: number): FastifyReply; writeEarlyHints(hints: Record, callback?: () => void): void; - hijack(): FastifyReply; + hijack(): FastifyReply; callNotFound(): void; - type(contentType: string): FastifyReply; - serializer(fn: (payload: any) => string): FastifyReply; + type(contentType: string): FastifyReply; + serializer(fn: (payload: any) => string): FastifyReply; serialize(payload: any): string | ArrayBuffer | Buffer; // Serialization Methods getSerializationFunction(httpStatus: string, contentType?: string): ((payload: { [key: string]: unknown }) => string) | undefined; @@ -77,8 +77,8 @@ export interface FastifyReply< then(fulfilled: () => void, rejected: (err: Error) => void): void; trailer: ( key: string, - fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise) | ((reply: FastifyReply, payload: string | Buffer | null, done: (err: Error | null, value?: string) => void) => void) - ) => FastifyReply; + fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise) | ((reply: FastifyReply, payload: string | Buffer | null, done: (err: Error | null, value?: string) => void) => void) + ) => FastifyReply; hasTrailer(key: string): boolean; - removeTrailer(key: string): FastifyReply; + removeTrailer(key: string): FastifyReply; } diff --git a/types/route.d.ts b/types/route.d.ts index 5248db29eab..f67f1e5bbcf 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -60,7 +60,7 @@ export interface RouteShorthandOptions< this: FastifyInstance, error: FastifyError, request: FastifyRequest, TypeProvider, ContextConfig, Logger>, - reply: FastifyReply, TypeProvider> + reply: FastifyReply, TypeProvider> ) => void; childLoggerFactory?: FastifyChildLoggerFactory; schemaErrorFormatter?: SchemaErrorFormatter; @@ -102,8 +102,8 @@ export type RouteHandlerMethod< > = ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply -// This return type used to be a generic type argument. Due to TypeScript's inference of return types, this rendered returns unchecked. + reply: FastifyReply + // This return type used to be a generic type argument. Due to TypeScript's inference of return types, this rendered returns unchecked. ) => ResolveFastifyReplyReturnType /** @@ -177,7 +177,7 @@ export type RouteHandler< > = ( this: FastifyInstance, request: FastifyRequest, - reply: FastifyReply + reply: FastifyReply ) => RouteGeneric['Reply'] | void | Promise export type DefaultRoute = ( From 2ba46c49240154d0e20cbc040a96813fd00cc603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aar=C3=B3n=20Gabriel=20Hinojosa=20Maya?= Date: Wed, 24 Jul 2024 04:35:24 -0600 Subject: [PATCH 0715/1295] docs: Adding `crudify-mongo` plugin to community list (#5581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update Ecosystem.md Adding `crudify-mongo` plugin to community list Signed-off-by: Aarón Gabriel Hinojosa Maya * Apply suggestions from code review Co-authored-by: Gürgün Dayıoğlu Signed-off-by: Matteo Collina --------- Signed-off-by: Aarón Gabriel Hinojosa Maya Signed-off-by: Matteo Collina Co-authored-by: Matteo Collina Co-authored-by: Gürgün Dayıoğlu --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index b9c646e24c7..3453a425512 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -159,6 +159,8 @@ section. #### [Community](#community) +- [`@aaroncadillac/crudify-mongo`](https://github.com/aaroncadillac/crudify-mongo) + A simple way to add a crud in your fastify project. - [`@applicazza/fastify-nextjs`](https://github.com/applicazza/fastify-nextjs) Alternate Fastify and Next.js integration. - [`@blastorg/fastify-aws-dynamodb-cache`](https://github.com/blastorg/fastify-aws-dynamodb-cache) From fc2228ed31fe655cf4dd52df00c8ff88f775ec02 Mon Sep 17 00:00:00 2001 From: Jonas Scheffner Date: Thu, 25 Jul 2024 01:45:56 +0200 Subject: [PATCH 0716/1295] docs(reference/typescript): remove type provider from typebox example (#5576) also rephrase some sentences --- docs/Reference/TypeScript.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 1421cb4be32..0f23571b0f4 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -210,24 +210,23 @@ And a `zod` wrapper by a third party called [`fastify-type-provider-zod`](https: They simplify schema validation setup and you can read more about them in [Type Providers](./Type-Providers.md) page. -Below is how to setup schema validation using _vanilla_ `typebox` and -`json-schema-to-ts` packages. +Below is how to setup schema validation using the `typebox`, +`json-schema-to-typescript`, and `json-schema-to-ts` packages without type +providers. #### TypeBox -A useful library for building types and a schema at once is -[TypeBox](https://www.npmjs.com/package/@sinclair/typebox) along with -[fastify-type-provider-typebox](https://github.com/fastify/fastify-type-provider-typebox). -With TypeBox you define your schema within your code and use them -directly as types or schemas as you need them. +A useful library for building types and a schema at once is [TypeBox](https://www.npmjs.com/package/@sinclair/typebox). +With TypeBox you define your schema within your code and use them directly as +types or schemas as you need them. When you want to use it for validation of some payload in a fastify route you can do it as follows: -1. Install `typebox` and `fastify-type-provider-typebox` in your project. +1. Install `typebox` in your project. ```bash - npm i @sinclair/typebox @fastify/type-provider-typebox + npm i @sinclair/typebox ``` 2. Define the schema you need with `Type` and create the respective type with @@ -248,10 +247,9 @@ can do it as follows: ```typescript import Fastify from 'fastify' - import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox' // ... - const fastify = Fastify().withTypeProvider() + const fastify = Fastify() fastify.post<{ Body: UserType, Reply: UserType }>( '/', @@ -271,12 +269,12 @@ can do it as follows: ) ``` -#### Schemas in JSON Files +#### json-schema-to-typescript -In the last example we used interfaces to define the types for the request -querystring and headers. Many users will already be using JSON Schemas to define -these properties, and luckily there is a way to transform existing JSON Schemas -into TypeScript interfaces! +In the last example we used Typebox to define the types and schemas for our +route. Many users will already be using JSON Schemas to define these properties, +and luckily there is a way to transform existing JSON Schemas into TypeScript +interfaces! 1. If you did not complete the 'Getting Started' example, go back and follow steps 1-4 first. From 9af8a5ce3f66496dfbeda130d1ad287607883312 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:48:35 +0800 Subject: [PATCH 0717/1295] refactor!: remove json shorthand (#5586) --- build/build-validation.js | 2 - docs/Guides/Getting-Started.md | 2 - docs/Reference/Server.md | 40 --- .../Reference/Validation-and-Serialization.md | 67 ++-- fastify.d.ts | 19 +- lib/configValidator.js | 301 ++++++++---------- lib/schemas.js | 38 +-- test/internals/initialConfig.test.js | 2 - test/internals/reply-serialize.test.js | 35 +- test/internals/request-validate.test.js | 25 +- test/internals/validation.test.js | 77 ++--- test/request.deprecated.test.js | 5 +- test/route.3.test.js | 5 +- test/router-options.test.js | 5 +- test/schema-examples.test.js | 21 +- test/schema-feature.test.js | 192 +++++++++-- test/schema-serialization.test.js | 146 +++++++-- test/schema-validation.test.js | 21 +- test/throw.test.js | 12 - test/types/fastify.test-d.ts | 46 +-- test/types/schema.test-d.ts | 8 +- 21 files changed, 617 insertions(+), 452 deletions(-) diff --git a/build/build-validation.js b/build/build-validation.js index 4631e7b9e9a..3fddf1d7092 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -32,7 +32,6 @@ const defaultInitOptions = { caseSensitive: true, allowUnsafeRegex: false, disableRequestLogging: false, - jsonShorthand: true, ignoreTrailingSlash: false, ignoreDuplicateSlashes: false, maxParamLength: 100, @@ -94,7 +93,6 @@ const schema = { type: 'boolean', default: false }, - jsonShorthand: { type: 'boolean', default: defaultInitOptions.jsonShorthand }, maxParamLength: { type: 'integer', default: defaultInitOptions.maxParamLength }, onProtoPoisoning: { type: 'string', default: defaultInitOptions.onProtoPoisoning }, onConstructorPoisoning: { type: 'string', default: defaultInitOptions.onConstructorPoisoning }, diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index e6f57da0f6b..62144315745 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -450,8 +450,6 @@ Data validation is extremely important and a core concept of the framework. To validate incoming requests, Fastify uses [JSON Schema](https://json-schema.org/). -(JTD schemas are loosely supported, but `jsonShorthand` must be disabled first) - Let's look at an example demonstrating validation for routes: ```js /** diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index fcd69f8f9ae..19ce776809f 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -26,7 +26,6 @@ describes the properties available in that options object. - [`logger`](#logger) - [`disableRequestLogging`](#disablerequestlogging) - [`serverFactory`](#serverfactory) - - [`jsonShorthand`](#jsonshorthand) - [`caseSensitive`](#casesensitive) - [`allowUnsafeRegex`](#allowunsaferegex) - [`requestIdHeader`](#requestidheader) @@ -441,45 +440,6 @@ custom server you must be sure to have the same API exposed. If not, you can enhance the server instance inside the `serverFactory` function before the `return` statement. - -### `jsonShorthand` - - -+ Default: `true` - -By default, Fastify will automatically infer the root properties -of JSON Schemas if it does not find valid root properties according to the JSON -Schema spec. If you wish to implement your own schema validation compiler, to -parse schemas as JTD instead of JSON Schema for example, then you can explicitly -set this option to `false` to make sure the schemas you receive are unmodified -and are not being treated internally as JSON Schema. - -Fastify does not throw on invalid schemas so if this option is set to `false` -in an existing project, check that none of your existing schemas become -invalid as a result, as they will be treated as catch-alls. - -```js -const AjvJTD = require('ajv/dist/jtd'/* only valid for AJV v7+ */) -const ajv = new AjvJTD({ - // This would let you throw at start for invalid JTD schema objects - allErrors: process.env.NODE_ENV === 'development' -}) -const fastify = Fastify({ jsonShorthand: false }) -fastify.setValidatorCompiler(({ schema }) => { - return ajv.compile(schema) -}) -fastify.post('/', { - schema: { - body: { - properties: { - foo: { type: 'uint8' } - } - } - }, - handler (req, reply) { reply.send({ ok: 1 }) } -}) -``` - ### `caseSensitive` diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 54c40248bce..442139c2578 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -640,37 +640,47 @@ You can even have a specific response schema for different content types. For example: ```js const schema = { - response: { - 200: { - description: 'Response schema that support different content types' - content: { - 'application/json': { - schema: { - name: { type: 'string' }, - image: { type: 'string' }, - address: { type: 'string' } - } - }, - 'application/vnd.v1+json': { - schema: { - type: 'array', - items: { $ref: 'test' } - } - } + response: { + 200: { + description: 'Response schema that support different content types' + content: { + 'application/json': { + schema: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } } }, - '3xx': { - content: { - 'application/vnd.v2+json': { - schema: { - fullName: { type: 'string' }, - phone: { type: 'string' } - } - } + 'application/vnd.v1+json': { + schema: { + type: 'array', + items: { $ref: 'test' } + } + } + } + }, + '3xx': { + content: { + 'application/vnd.v2+json': { + schema: { + fullName: { type: 'string' }, + phone: { type: 'string' } + } + } + } + }, + default: { + content: { + // */* is match-all content-type + '*/*': { + schema: { + desc: { type: 'string' } } } } } + } +} fastify.post('/url', { schema }, handler) ``` @@ -695,8 +705,11 @@ fastify.get('/user', { schema: { response: { '2xx': { - id: { type: 'number' }, - name: { type: 'string' } + type: 'object', + properties: { + id: { type: 'number' }, + name: { type: 'string' } + } } } } diff --git a/fastify.d.ts b/fastify.d.ts index 1410810e801..7b643457bf0 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -7,23 +7,23 @@ import { Options as AjvOptions, ValidatorFactory } from '@fastify/ajv-compiler' import { FastifyError } from '@fastify/error' import { Options as FJSOptions, SerializerFactory } from '@fastify/fast-json-stringify-compiler' import { ConstraintStrategy, HTTPVersion } from 'find-my-way' -import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request' +import { InjectOptions, CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request' -import { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser' -import { FastifyRequestContext, FastifyContextConfig, FastifyReplyContext } from './types/context' +import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, FastifyContentTypeParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction } from './types/content-type-parser' +import { FastifyContextConfig, FastifyReplyContext, FastifyRequestContext } from './types/context' import { FastifyErrorCodes } from './types/errors' -import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler } from './types/hooks' -import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance' -import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel } from './types/logger' -import { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin' +import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onListenAsyncHookHandler, onListenHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, RequestPayload } from './types/hooks' +import { FastifyInstance, FastifyListenOptions, PrintRoutesOptions } from './types/instance' +import { FastifyBaseLogger, FastifyLogFn, FastifyLoggerInstance, FastifyLoggerOptions, LogLevel, PinoLoggerOptions } from './types/logger' +import { FastifyPlugin, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions } from './types/plugin' import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './types/register' import { FastifyReply } from './types/reply' import { FastifyRequest, RequestGenericInterface } from './types/request' -import { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route' +import { RouteGenericInterface, RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route' import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema' import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' import { FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike } from './types/type-provider' -import { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils' +import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './types/utils' declare module '@fastify/error' { interface FastifyError { @@ -111,7 +111,6 @@ declare namespace fastify { requestIdHeader?: string | false, requestIdLogLabel?: string; useSemicolonDelimiter?: boolean, - jsonShorthand?: boolean; genReqId?: (req: RawRequestDefaultExpression) => string, trustProxy?: boolean | string | string[] | number | TrustProxyFunction, querystringParser?: (str: string) => { [key: string]: unknown }, diff --git a/lib/configValidator.js b/lib/configValidator.js index 77394823c7b..696856e1a17 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -1,9 +1,9 @@ -// This file is autogenerated by build/build-validation.js, do not edit +// This file is autogenerated by build\build-validation.js, do not edit /* c8 ignore start */ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -42,9 +42,6 @@ data.ignoreDuplicateSlashes = false; if(data.disableRequestLogging === undefined){ data.disableRequestLogging = false; } -if(data.jsonShorthand === undefined){ -data.jsonShorthand = true; -} if(data.maxParamLength === undefined){ data.maxParamLength = 100; } @@ -713,56 +710,57 @@ data["disableRequestLogging"] = coerced15; } var valid0 = _errs45 === errors; if(valid0){ -let data14 = data.jsonShorthand; +let data14 = data.maxParamLength; const _errs47 = errors; -if(typeof data14 !== "boolean"){ +if(!(((typeof data14 == "number") && (!(data14 % 1) && !isNaN(data14))) && (isFinite(data14)))){ +let dataType16 = typeof data14; let coerced16 = undefined; if(!(coerced16 !== undefined)){ -if(data14 === "false" || data14 === 0 || data14 === null){ -coerced16 = false; -} -else if(data14 === "true" || data14 === 1){ -coerced16 = true; +if(dataType16 === "boolean" || data14 === null + || (dataType16 === "string" && data14 && data14 == +data14 && !(data14 % 1))){ +coerced16 = +data14; } else { -validate10.errors = [{instancePath:instancePath+"/jsonShorthand",schemaPath:"#/properties/jsonShorthand/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/maxParamLength",schemaPath:"#/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced16 !== undefined){ data14 = coerced16; if(data !== undefined){ -data["jsonShorthand"] = coerced16; +data["maxParamLength"] = coerced16; } } } var valid0 = _errs47 === errors; if(valid0){ -let data15 = data.maxParamLength; +let data15 = data.onProtoPoisoning; const _errs49 = errors; -if(!(((typeof data15 == "number") && (!(data15 % 1) && !isNaN(data15))) && (isFinite(data15)))){ +if(typeof data15 !== "string"){ let dataType17 = typeof data15; let coerced17 = undefined; if(!(coerced17 !== undefined)){ -if(dataType17 === "boolean" || data15 === null - || (dataType17 === "string" && data15 && data15 == +data15 && !(data15 % 1))){ -coerced17 = +data15; +if(dataType17 == "number" || dataType17 == "boolean"){ +coerced17 = "" + data15; +} +else if(data15 === null){ +coerced17 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/maxParamLength",schemaPath:"#/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/onProtoPoisoning",schemaPath:"#/properties/onProtoPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } if(coerced17 !== undefined){ data15 = coerced17; if(data !== undefined){ -data["maxParamLength"] = coerced17; +data["onProtoPoisoning"] = coerced17; } } } var valid0 = _errs49 === errors; if(valid0){ -let data16 = data.onProtoPoisoning; +let data16 = data.onConstructorPoisoning; const _errs51 = errors; if(typeof data16 !== "string"){ let dataType18 = typeof data16; @@ -775,82 +773,56 @@ else if(data16 === null){ coerced18 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/onProtoPoisoning",schemaPath:"#/properties/onProtoPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/onConstructorPoisoning",schemaPath:"#/properties/onConstructorPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } if(coerced18 !== undefined){ data16 = coerced18; if(data !== undefined){ -data["onProtoPoisoning"] = coerced18; +data["onConstructorPoisoning"] = coerced18; } } } var valid0 = _errs51 === errors; if(valid0){ -let data17 = data.onConstructorPoisoning; +let data17 = data.pluginTimeout; const _errs53 = errors; -if(typeof data17 !== "string"){ +if(!(((typeof data17 == "number") && (!(data17 % 1) && !isNaN(data17))) && (isFinite(data17)))){ let dataType19 = typeof data17; let coerced19 = undefined; if(!(coerced19 !== undefined)){ -if(dataType19 == "number" || dataType19 == "boolean"){ -coerced19 = "" + data17; -} -else if(data17 === null){ -coerced19 = ""; +if(dataType19 === "boolean" || data17 === null + || (dataType19 === "string" && data17 && data17 == +data17 && !(data17 % 1))){ +coerced19 = +data17; } else { -validate10.errors = [{instancePath:instancePath+"/onConstructorPoisoning",schemaPath:"#/properties/onConstructorPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/pluginTimeout",schemaPath:"#/properties/pluginTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced19 !== undefined){ data17 = coerced19; if(data !== undefined){ -data["onConstructorPoisoning"] = coerced19; +data["pluginTimeout"] = coerced19; } } } var valid0 = _errs53 === errors; if(valid0){ -let data18 = data.pluginTimeout; +let data18 = data.requestIdHeader; const _errs55 = errors; -if(!(((typeof data18 == "number") && (!(data18 % 1) && !isNaN(data18))) && (isFinite(data18)))){ -let dataType20 = typeof data18; +const _errs56 = errors; +let valid6 = false; +const _errs57 = errors; +if(typeof data18 !== "boolean"){ let coerced20 = undefined; if(!(coerced20 !== undefined)){ -if(dataType20 === "boolean" || data18 === null - || (dataType20 === "string" && data18 && data18 == +data18 && !(data18 % 1))){ -coerced20 = +data18; -} -else { -validate10.errors = [{instancePath:instancePath+"/pluginTimeout",schemaPath:"#/properties/pluginTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; -return false; -} -} -if(coerced20 !== undefined){ -data18 = coerced20; -if(data !== undefined){ -data["pluginTimeout"] = coerced20; -} -} -} -var valid0 = _errs55 === errors; -if(valid0){ -let data19 = data.requestIdHeader; -const _errs57 = errors; -const _errs58 = errors; -let valid6 = false; -const _errs59 = errors; -if(typeof data19 !== "boolean"){ -let coerced21 = undefined; -if(!(coerced21 !== undefined)){ -if(data19 === "false" || data19 === 0 || data19 === null){ -coerced21 = false; +if(data18 === "false" || data18 === 0 || data18 === null){ +coerced20 = false; } -else if(data19 === "true" || data19 === 1){ -coerced21 = true; +else if(data18 === "true" || data18 === 1){ +coerced20 = true; } else { const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; @@ -863,26 +835,26 @@ vErrors.push(err12); errors++; } } -if(coerced21 !== undefined){ -data19 = coerced21; +if(coerced20 !== undefined){ +data18 = coerced20; if(data !== undefined){ -data["requestIdHeader"] = coerced21; +data["requestIdHeader"] = coerced20; } } } -var _valid3 = _errs59 === errors; +var _valid3 = _errs57 === errors; valid6 = valid6 || _valid3; if(!valid6){ -const _errs61 = errors; -if(typeof data19 !== "string"){ -let dataType22 = typeof data19; -let coerced22 = undefined; -if(!(coerced22 !== undefined)){ -if(dataType22 == "number" || dataType22 == "boolean"){ -coerced22 = "" + data19; +const _errs59 = errors; +if(typeof data18 !== "string"){ +let dataType21 = typeof data18; +let coerced21 = undefined; +if(!(coerced21 !== undefined)){ +if(dataType21 == "number" || dataType21 == "boolean"){ +coerced21 = "" + data18; } -else if(data19 === null){ -coerced22 = ""; +else if(data18 === null){ +coerced21 = ""; } else { const err13 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"}; @@ -895,14 +867,14 @@ vErrors.push(err13); errors++; } } -if(coerced22 !== undefined){ -data19 = coerced22; +if(coerced21 !== undefined){ +data18 = coerced21; if(data !== undefined){ -data["requestIdHeader"] = coerced22; +data["requestIdHeader"] = coerced21; } } } -var _valid3 = _errs61 === errors; +var _valid3 = _errs59 === errors; valid6 = valid6 || _valid3; } if(!valid6){ @@ -918,69 +890,94 @@ validate10.errors = vErrors; return false; } else { -errors = _errs58; +errors = _errs56; if(vErrors !== null){ -if(_errs58){ -vErrors.length = _errs58; +if(_errs56){ +vErrors.length = _errs56; } else { vErrors = null; } } } -var valid0 = _errs57 === errors; +var valid0 = _errs55 === errors; if(valid0){ -let data20 = data.requestIdLogLabel; +let data19 = data.requestIdLogLabel; +const _errs61 = errors; +if(typeof data19 !== "string"){ +let dataType22 = typeof data19; +let coerced22 = undefined; +if(!(coerced22 !== undefined)){ +if(dataType22 == "number" || dataType22 == "boolean"){ +coerced22 = "" + data19; +} +else if(data19 === null){ +coerced22 = ""; +} +else { +validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +return false; +} +} +if(coerced22 !== undefined){ +data19 = coerced22; +if(data !== undefined){ +data["requestIdLogLabel"] = coerced22; +} +} +} +var valid0 = _errs61 === errors; +if(valid0){ +let data20 = data.http2SessionTimeout; const _errs63 = errors; -if(typeof data20 !== "string"){ +if(!(((typeof data20 == "number") && (!(data20 % 1) && !isNaN(data20))) && (isFinite(data20)))){ let dataType23 = typeof data20; let coerced23 = undefined; if(!(coerced23 !== undefined)){ -if(dataType23 == "number" || dataType23 == "boolean"){ -coerced23 = "" + data20; -} -else if(data20 === null){ -coerced23 = ""; +if(dataType23 === "boolean" || data20 === null + || (dataType23 === "string" && data20 && data20 == +data20 && !(data20 % 1))){ +coerced23 = +data20; } else { -validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced23 !== undefined){ data20 = coerced23; if(data !== undefined){ -data["requestIdLogLabel"] = coerced23; +data["http2SessionTimeout"] = coerced23; } } } var valid0 = _errs63 === errors; if(valid0){ -let data21 = data.http2SessionTimeout; +let data21 = data.exposeHeadRoutes; const _errs65 = errors; -if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){ -let dataType24 = typeof data21; +if(typeof data21 !== "boolean"){ let coerced24 = undefined; if(!(coerced24 !== undefined)){ -if(dataType24 === "boolean" || data21 === null - || (dataType24 === "string" && data21 && data21 == +data21 && !(data21 % 1))){ -coerced24 = +data21; +if(data21 === "false" || data21 === 0 || data21 === null){ +coerced24 = false; +} +else if(data21 === "true" || data21 === 1){ +coerced24 = true; } else { -validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced24 !== undefined){ data21 = coerced24; if(data !== undefined){ -data["http2SessionTimeout"] = coerced24; +data["exposeHeadRoutes"] = coerced24; } } } var valid0 = _errs65 === errors; if(valid0){ -let data22 = data.exposeHeadRoutes; +let data22 = data.useSemicolonDelimiter; const _errs67 = errors; if(typeof data22 !== "boolean"){ let coerced25 = undefined; @@ -992,51 +989,26 @@ else if(data22 === "true" || data22 === 1){ coerced25 = true; } else { -validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced25 !== undefined){ data22 = coerced25; if(data !== undefined){ -data["exposeHeadRoutes"] = coerced25; +data["useSemicolonDelimiter"] = coerced25; } } } var valid0 = _errs67 === errors; if(valid0){ -let data23 = data.useSemicolonDelimiter; -const _errs69 = errors; -if(typeof data23 !== "boolean"){ -let coerced26 = undefined; -if(!(coerced26 !== undefined)){ -if(data23 === "false" || data23 === 0 || data23 === null){ -coerced26 = false; -} -else if(data23 === "true" || data23 === 1){ -coerced26 = true; -} -else { -validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; -return false; -} -} -if(coerced26 !== undefined){ -data23 = coerced26; -if(data !== undefined){ -data["useSemicolonDelimiter"] = coerced26; -} -} -} -var valid0 = _errs69 === errors; -if(valid0){ if(data.versioning !== undefined){ -let data24 = data.versioning; -const _errs71 = errors; -if(errors === _errs71){ -if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ +let data23 = data.versioning; +const _errs69 = errors; +if(errors === _errs69){ +if(data23 && typeof data23 == "object" && !Array.isArray(data23)){ let missing1; -if(((data24.storage === undefined) && (missing1 = "storage")) || ((data24.deriveVersion === undefined) && (missing1 = "deriveVersion"))){ +if(((data23.storage === undefined) && (missing1 = "storage")) || ((data23.deriveVersion === undefined) && (missing1 = "deriveVersion"))){ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; return false; } @@ -1046,49 +1018,49 @@ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/prop return false; } } -var valid0 = _errs71 === errors; +var valid0 = _errs69 === errors; } else { var valid0 = true; } if(valid0){ if(data.constraints !== undefined){ -let data25 = data.constraints; -const _errs74 = errors; -if(errors === _errs74){ +let data24 = data.constraints; +const _errs72 = errors; +if(errors === _errs72){ +if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ +for(const key2 in data24){ +let data25 = data24[key2]; +const _errs75 = errors; +if(errors === _errs75){ if(data25 && typeof data25 == "object" && !Array.isArray(data25)){ -for(const key2 in data25){ -let data26 = data25[key2]; -const _errs77 = errors; -if(errors === _errs77){ -if(data26 && typeof data26 == "object" && !Array.isArray(data26)){ let missing2; -if(((((data26.name === undefined) && (missing2 = "name")) || ((data26.storage === undefined) && (missing2 = "storage"))) || ((data26.validate === undefined) && (missing2 = "validate"))) || ((data26.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){ +if(((((data25.name === undefined) && (missing2 = "name")) || ((data25.storage === undefined) && (missing2 = "storage"))) || ((data25.validate === undefined) && (missing2 = "validate"))) || ((data25.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing2},message:"must have required property '"+missing2+"'"}]; return false; } else { -if(data26.name !== undefined){ -let data27 = data26.name; -if(typeof data27 !== "string"){ -let dataType27 = typeof data27; -let coerced27 = undefined; -if(!(coerced27 !== undefined)){ -if(dataType27 == "number" || dataType27 == "boolean"){ -coerced27 = "" + data27; +if(data25.name !== undefined){ +let data26 = data25.name; +if(typeof data26 !== "string"){ +let dataType26 = typeof data26; +let coerced26 = undefined; +if(!(coerced26 !== undefined)){ +if(dataType26 == "number" || dataType26 == "boolean"){ +coerced26 = "" + data26; } -else if(data27 === null){ -coerced27 = ""; +else if(data26 === null){ +coerced26 = ""; } else { validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } -if(coerced27 !== undefined){ -data27 = coerced27; -if(data26 !== undefined){ -data26["name"] = coerced27; +if(coerced26 !== undefined){ +data26 = coerced26; +if(data25 !== undefined){ +data25["name"] = coerced26; } } } @@ -1100,7 +1072,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/ return false; } } -var valid7 = _errs77 === errors; +var valid7 = _errs75 === errors; if(!valid7){ break; } @@ -1111,7 +1083,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs74 === errors; +var valid0 = _errs72 === errors; } else { var valid0 = true; @@ -1141,7 +1113,6 @@ var valid0 = true; } } } -} else { validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; @@ -1152,5 +1123,5 @@ return errors === 0; } -module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false} +module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false} /* c8 ignore stop */ diff --git a/lib/schemas.js b/lib/schemas.js index 55d76ea646d..02524c311e6 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -82,11 +82,9 @@ function normalizeSchema (routeSchemas, serverOptions) { if (!contentSchema) { throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType) } - routeSchemas.body.content[contentType].schema = getSchemaAnyway(contentSchema, serverOptions.jsonShorthand) } continue } - routeSchemas[key] = getSchemaAnyway(schema, serverOptions.jsonShorthand) } } @@ -99,25 +97,15 @@ function normalizeSchema (routeSchemas, serverOptions) { const contentProperty = routeSchemas.response[code].content - let hasContentMultipleContentTypes = false if (contentProperty) { const keys = Object.keys(contentProperty) for (let i = 0; i < keys.length; i++) { const mediaName = keys[i] if (!contentProperty[mediaName].schema) { - if (keys.length === 1) { break } throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(mediaName) } - routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(contentProperty[mediaName].schema, serverOptions.jsonShorthand) - if (i === keys.length - 1) { - hasContentMultipleContentTypes = true - } } } - - if (!hasContentMultipleContentTypes) { - routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code], serverOptions.jsonShorthand) - } } } @@ -142,17 +130,6 @@ function generateFluentSchema (schema) { } } -function getSchemaAnyway (schema, jsonShorthand) { - if (!jsonShorthand || schema.$ref || schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema - if (!schema.type && !schema.properties) { - return { - type: 'object', - properties: schema - } - } - return schema -} - /** * Search for the right JSON schema compiled function in the request context * setup by the route configuration `schema.response`. @@ -176,6 +153,11 @@ function getSchemaSerializer (context, statusCode, contentType) { return responseSchemaDef[statusCode][mediaName] } + // fallback to match all media-type + if (responseSchemaDef[statusCode]['*/*']) { + return responseSchemaDef[statusCode]['*/*'] + } + return false } return responseSchemaDef[statusCode] @@ -188,6 +170,11 @@ function getSchemaSerializer (context, statusCode, contentType) { return responseSchemaDef[fallbackStatusCode][mediaName] } + // fallback to match all media-type + if (responseSchemaDef[fallbackStatusCode]['*/*']) { + return responseSchemaDef[fallbackStatusCode]['*/*'] + } + return false } @@ -200,6 +187,11 @@ function getSchemaSerializer (context, statusCode, contentType) { return responseSchemaDef.default[mediaName] } + // fallback to match all media-type + if (responseSchemaDef.default['*/*']) { + return responseSchemaDef.default['*/*'] + } + return false } diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index c1864705078..5efa0c4ce74 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -38,7 +38,6 @@ test('without options passed to Fastify, initialConfig should expose default val caseSensitive: true, allowUnsafeRegex: false, disableRequestLogging: false, - jsonShorthand: true, ignoreTrailingSlash: false, ignoreDuplicateSlashes: false, maxParamLength: 100, @@ -278,7 +277,6 @@ test('Should not have issues when passing stream options to Pino.js', t => { caseSensitive: true, allowUnsafeRegex: false, disableRequestLogging: false, - jsonShorthand: true, ignoreTrailingSlash: true, ignoreDuplicateSlashes: false, maxParamLength: 100, diff --git a/test/internals/reply-serialize.test.js b/test/internals/reply-serialize.test.js index 161fde01919..1c370023aaf 100644 --- a/test/internals/reply-serialize.test.js +++ b/test/internals/reply-serialize.test.js @@ -50,8 +50,11 @@ function getResponseSchema () { content: { 'application/json': { schema: { - fullName: { type: 'string' }, - phone: { type: 'number' } + type: 'object', + properties: { + fullName: { type: 'string' }, + phone: { type: 'number' } + } } } } @@ -264,8 +267,11 @@ test('Reply#getSerializationFunction', t => { '/:id', { params: { - id: { - type: 'integer' + type: 'object', + properites: { + id: { + type: 'integer' + } } }, schema: { @@ -356,8 +362,11 @@ test('Reply#getSerializationFunction', t => { '/:id', { params: { - id: { - type: 'integer' + type: 'object', + properites: { + id: { + type: 'integer' + } } } }, @@ -465,8 +474,11 @@ test('Reply#serializeInput', t => { content: { 'application/json': { schema: { - fullName: { type: 'string' }, - phone: { type: 'number' } + type: 'object', + properites: { + fullName: { type: 'string' }, + phone: { type: 'number' } + } } } } @@ -525,8 +537,11 @@ test('Reply#serializeInput', t => { '/', { params: { - id: { - type: 'integer' + type: 'object', + properites: { + id: { + type: 'integer' + } } }, schema: { diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js index ce184dc2a4b..0783bd2c6b8 100644 --- a/test/internals/request-validate.test.js +++ b/test/internals/request-validate.test.js @@ -16,21 +16,30 @@ const defaultSchema = { const requestSchema = { params: { - id: { - type: 'integer', - minimum: 1 + type: 'object', + properties: { + id: { + type: 'integer', + minimum: 1 + } } }, querystring: { - foo: { - type: 'string', - enum: ['bar'] + type: 'object', + properties: { + foo: { + type: 'string', + enum: ['bar'] + } } }, body: defaultSchema, headers: { - 'x-foo': { - type: 'string' + type: 'object', + properties: { + 'x-foo': { + type: 'string' + } } } } diff --git a/test/internals/validation.test.js b/test/internals/validation.test.js index c56ebdc3654..9c0da49543b 100644 --- a/test/internals/validation.test.js +++ b/test/internals/validation.test.js @@ -107,9 +107,7 @@ test('build schema - body with multiple content type schemas', t => { test('build schema - avoid repeated normalize schema', t => { t.plan(3) - const serverConfig = { - jsonShorthand: true - } + const serverConfig = {} const opts = { schema: { query: { @@ -128,9 +126,7 @@ test('build schema - avoid repeated normalize schema', t => { test('build schema - query schema', t => { t.plan(2) - const serverConfig = { - jsonShorthand: true - } + const serverConfig = {} const opts = { schema: { query: { @@ -149,13 +145,14 @@ test('build schema - query schema', t => { test('build schema - query schema abbreviated', t => { t.plan(2) - const serverConfig = { - jsonShorthand: true - } + const serverConfig = {} const opts = { schema: { query: { - hello: { type: 'string' } + type: 'object', + properties: { + hello: { type: 'string' } + } } } } @@ -184,13 +181,14 @@ test('build schema - querystring schema', t => { test('build schema - querystring schema abbreviated', t => { t.plan(2) - const serverConfig = { - jsonShorthand: true - } + const serverConfig = {} const opts = { schema: { querystring: { - hello: { type: 'string' } + type: 'object', + properties: { + hello: { type: 'string' } + } } } } @@ -203,9 +201,7 @@ test('build schema - querystring schema abbreviated', t => { test('build schema - must throw if querystring and query schema exist', t => { t.plan(2) try { - const serverConfig = { - jsonShorthand: true - } + const serverConfig = {} const opts = { schema: { query: { @@ -275,14 +271,14 @@ test('build schema - headers are lowercase', t => { } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { t.ok(schema.properties['content-type'], 'lowercase content-type exists') - return () => {} + return () => { } }) }) test('build schema - headers are not lowercased in case of custom object', t => { t.plan(1) - class Headers {} + class Headers { } const opts = { schema: { headers: new Headers() @@ -290,14 +286,14 @@ test('build schema - headers are not lowercased in case of custom object', t => } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { t.type(schema, Headers) - return () => {} + return () => { } }) }) test('build schema - headers are not lowercased in case of custom validator provided', t => { t.plan(1) - class Headers {} + class Headers { } const opts = { schema: { headers: new Headers() @@ -305,7 +301,7 @@ test('build schema - headers are not lowercased in case of custom validator prov } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { t.type(schema, Headers) - return () => {} + return () => { } }, true) }) @@ -323,21 +319,14 @@ test('build schema - uppercased headers are not included', t => { } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { t.notOk('Content-Type' in schema.properties, 'uppercase does not exist') - return () => {} + return () => { } }) }) test('build schema - mixed schema types are individually skipped or normalized', t => { - t.plan(6) + t.plan(2) - class CustomSchemaClass {} - const nonNormalizedSchema = { - hello: { type: 'string' } - } - const normalizedSchema = { - type: 'object', - properties: nonNormalizedSchema - } + class CustomSchemaClass { } const testCases = [{ schema: { @@ -355,32 +344,10 @@ test('build schema - mixed schema types are individually skipped or normalized', assertions: (schema) => { t.type(schema.response[200], CustomSchemaClass) } - }, { - schema: { - body: nonNormalizedSchema, - response: { - 200: new CustomSchemaClass() - } - }, - assertions: (schema) => { - t.same(schema.body, normalizedSchema) - t.type(schema.response[200], CustomSchemaClass) - } - }, { - schema: { - body: new CustomSchemaClass(), - response: { - 200: nonNormalizedSchema - } - }, - assertions: (schema) => { - t.type(schema.body, CustomSchemaClass) - t.same(schema.response[200], normalizedSchema) - } }] testCases.forEach((testCase) => { - const result = normalizeSchema(testCase.schema, { jsonShorthand: true }) + const result = normalizeSchema(testCase.schema, {}) testCase.assertions(result) }) }) diff --git a/test/request.deprecated.test.js b/test/request.deprecated.test.js index 0a5bcdf1317..0c0a9d81c05 100644 --- a/test/request.deprecated.test.js +++ b/test/request.deprecated.test.js @@ -13,7 +13,10 @@ test('Should expose router options via getters on request and reply', t => { const fastify = Fastify() const expectedSchema = { params: { - id: { type: 'integer' } + type: 'object', + properties: { + id: { type: 'integer' } + } } } diff --git a/test/route.3.test.js b/test/route.3.test.js index 7126d9e8f32..b6af1fcdf35 100644 --- a/test/route.3.test.js +++ b/test/route.3.test.js @@ -54,7 +54,10 @@ test('multiple routes with one schema', t => { const schema = { query: { - id: { type: 'number' } + type: 'object', + properties: { + id: { type: 'number' } + } } } diff --git a/test/router-options.test.js b/test/router-options.test.js index 3d2943a0c8b..8ebd405aa15 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -96,7 +96,10 @@ test('Should expose router options via getters on request and reply', t => { const fastify = Fastify() const expectedSchema = { params: { - id: { type: 'integer' } + type: 'object', + properties: { + id: { type: 'integer' } + } } } diff --git a/test/schema-examples.test.js b/test/schema-examples.test.js index c27361089c4..127073b255f 100644 --- a/test/schema-examples.test.js +++ b/test/schema-examples.test.js @@ -137,13 +137,19 @@ test('Example - validation', t => { } const queryStringJsonSchema = { - name: { type: 'string' }, - excitement: { type: 'integer' } + type: 'object', + properties: { + name: { type: 'string' }, + excitement: { type: 'integer' } + } } const paramsJsonSchema = { - par1: { type: 'string' }, - par2: { type: 'number' } + type: 'object', + properties: { + par1: { type: 'string' }, + par2: { type: 'number' } + } } const headersJsonSchema = { @@ -345,8 +351,11 @@ test('Example - serializator', t => { schema: { response: { '2xx': { - id: { type: 'number' }, - name: { type: 'string' } + type: 'object', + properties: { + id: { type: 'number' }, + name: { type: 'string' } + } } } } diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index ddcb54b079f..e526c37b0b2 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -110,7 +110,14 @@ test('Get compilers is empty when settle on routes', t => { fastify.post('/', { schema: { body: { type: 'object', properties: { hello: { type: 'string' } } }, - response: { '2xx': { foo: { type: 'array', items: { type: 'string' } } } } + response: { + '2xx': { + type: 'object', + properties: { + foo: { type: 'array', items: { type: 'string' } } + } + } + } }, validatorCompiler: ({ schema, method, url, httpPart }) => {}, serializerCompiler: ({ schema, method, url, httpPart }) => {} @@ -160,8 +167,18 @@ test('Cannot add schema for query and querystring', t => { fastify.get('/', { handler: () => {}, schema: { - query: { foo: { type: 'string' } }, - querystring: { foo: { type: 'string' } } + query: { + type: 'object', + properties: { + foo: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + foo: { type: 'string' } + } + } } }) @@ -179,7 +196,10 @@ test('Should throw of the schema does not exists in input', t => { handler: echoParams, schema: { params: { - name: { $ref: '#notExist' } + type: 'object', + properties: { + name: { $ref: '#notExist' } + } } } }) @@ -220,7 +240,10 @@ test('Should throw of the schema does not exists in output', t => { schema: { response: { '2xx': { - name: { $ref: '#notExist' } + type: 'object', + properties: { + name: { $ref: '#notExist' } + } } } } @@ -256,7 +279,10 @@ test('Should not change the input schemas', t => { }, response: { '2xx': { - name: { $ref: 'helloSchema#/definitions/hello' } + type: 'object', + properties: { + name: { $ref: 'helloSchema#/definitions/hello' } + } } } } @@ -528,12 +554,37 @@ test('Customize validator compiler in instance and route', t => { fastify.post('/:id', { handler: echoBody, schema: { - query: { lang: { type: 'string', enum: ['it', 'en'] } }, - headers: { x: { type: 'string' } }, - params: { id: { type: 'number' } }, - body: { foo: { type: 'array' } }, + query: { + type: 'object', + properties: { + lang: { type: 'string', enum: ['it', 'en'] } + } + }, + headers: { + type: 'object', + properties: { + x: { type: 'string' } + } + }, + params: { + type: 'object', + properties: { + id: { type: 'number' } + } + }, + body: { + type: 'object', + properties: { + foo: { type: 'array' } + } + }, response: { - '2xx': { foo: { type: 'array', items: { type: 'string' } } } + '2xx': { + type: 'object', + properties: { + foo: { type: 'array', items: { type: 'string' } } + } + } } } }) @@ -546,10 +597,32 @@ test('Customize validator compiler in instance and route', t => { return () => { return true } // ignore the validation }, schema: { - query: { lang: { type: 'string', enum: ['it', 'en'] } }, - headers: { x: { type: 'string' } }, - params: { id: { type: 'number' } }, - response: { '2xx': { foo: { type: 'array', items: { type: 'string' } } } } + query: { + type: 'object', + properties: { + lang: { type: 'string', enum: ['it', 'en'] } + } + }, + headers: { + type: 'object', + properties: { + x: { type: 'string' } + } + }, + params: { + type: 'object', + properties: { + id: { type: 'number' } + } + }, + response: { + '2xx': { + type: 'object', + properties: { + foo: { type: 'array', items: { type: 'string' } } + } + } + } } }) @@ -591,7 +664,12 @@ test('Use the same schema across multiple routes', t => { fastify.get('/first/:id', { schema: { - params: { id: { $ref: 'test#/properties/id' } } + params: { + type: 'object', + properties: { + id: { $ref: 'test#/properties/id' } + } + } }, handler: (req, reply) => { reply.send(typeof req.params.id) @@ -600,7 +678,12 @@ test('Use the same schema across multiple routes', t => { fastify.get('/second/:id', { schema: { - params: { id: { $ref: 'test#/properties/id' } } + params: { + type: 'object', + properties: { + id: { $ref: 'test#/properties/id' } + } + } }, handler: (req, reply) => { reply.send(typeof req.params.id) @@ -643,7 +726,12 @@ test('Encapsulation should intervene', t => { instance.get('/:id', { handler: echoParams, schema: { - params: { id: { $ref: 'encapsulation#/properties/id' } } + params: { + type: 'object', + properties: { + id: { $ref: 'encapsulation#/properties/id' } + } + } } }) done() @@ -782,9 +870,19 @@ test('Use the same schema id in different places', t => { fastify.post('/:id', { handler: echoBody, schema: { - body: { id: { $ref: 'test#/properties/id' } }, + body: { + type: 'object', + properties: { + id: { $ref: 'test#/properties/id' } + } + }, response: { - 200: { id: { $ref: 'test#/properties/id' } } + 200: { + type: 'object', + properties: { + id: { $ref: 'test#/properties/id' } + } + } } } }) @@ -1354,7 +1452,10 @@ test('The schema compiler recreate itself if needed', t => { fastify.get('/:foobarId', { schema: { params: { - foobarId: { $ref: 'identifier#' } + type: 'object', + properties: { + foobarId: { $ref: 'identifier#' } + } } } }, echoBody) @@ -1546,8 +1647,11 @@ test('setSchemaController: Inherits correctly parent schemas with a customized v { schema: { querystring: { - msg: { - $ref: 'some#' + type: 'object', + properties: { + msg: { + $ref: 'some#' + } } }, response: { @@ -1656,8 +1760,11 @@ test('setSchemaController: Inherits buildSerializer from parent if not present w { schema: { querystring: { - msg: { - $ref: 'some#' + type: 'object', + properties: { + msg: { + $ref: 'some#' + } } }, response: { @@ -1767,8 +1874,11 @@ test('setSchemaController: Inherits buildValidator from parent if not present wi { schema: { querystring: { - msg: { - $ref: 'some#' + type: 'object', + properties: { + msg: { + $ref: 'some#' + } } }, response: { @@ -1859,13 +1969,19 @@ test('Should throw if not default validator passed', async t => { { schema: { query: { - msg: { - $ref: 'some#' + type: 'object', + properties: { + msg: { + $ref: 'some#' + } } }, headers: { - 'x-another': { - $ref: 'another#' + type: 'object', + properties: { + 'x-another': { + $ref: 'another#' + } } } } @@ -1920,13 +2036,19 @@ test('Should coerce the array if the default validator is used', async t => { { schema: { query: { - msg: { - $ref: 'some#' + type: 'object', + properties: { + msg: { + $ref: 'some#' + } } }, headers: { - 'x-another': { - $ref: 'another#' + type: 'object', + properties: { + 'x-another': { + $ref: 'another#' + } } } } diff --git a/test/schema-serialization.test.js b/test/schema-serialization.test.js index f122e020a6c..b319be02b06 100644 --- a/test/schema-serialization.test.js +++ b/test/schema-serialization.test.js @@ -61,7 +61,7 @@ test('custom serializer options', t => { }) test('Different content types', t => { - t.plan(32) + t.plan(46) const fastify = Fastify() fastify.addSchema({ @@ -81,9 +81,12 @@ test('Different content types', t => { content: { 'application/json': { schema: { - name: { type: 'string' }, - image: { type: 'string' }, - address: { type: 'string' } + type: 'object', + properties: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } } }, 'application/vnd.v1+json': { @@ -95,17 +98,40 @@ test('Different content types', t => { } }, 201: { - content: { type: 'string' } + content: { + '*/*': { + schema: { type: 'string' } + } + } }, 202: { - content: { const: 'Processing exclusive content' } + content: { + '*/*': { + schema: { const: 'Processing exclusive content' } + } + } }, '3xx': { content: { 'application/vnd.v2+json': { schema: { - fullName: { type: 'string' }, - phone: { type: 'string' } + type: 'object', + properties: { + fullName: { type: 'string' }, + phone: { type: 'string' } + } + } + } + } + }, + '4xx': { + content: { + '*/*': { + schema: { + type: 'object', + properties: { + details: { type: 'string' } + } } } } @@ -114,7 +140,19 @@ test('Different content types', t => { content: { 'application/json': { schema: { - details: { type: 'string' } + type: 'object', + properties: { + details: { type: 'string' } + } + } + }, + '*/*': { + schema: { + type: 'object', + properties: { + desc: { type: 'string' }, + details: { type: 'string' } + } } } } @@ -160,6 +198,15 @@ test('Different content types', t => { reply.code(400) reply.send({ details: 'validation error' }) break + case 'application/vnd.v8+json': + reply.header('Content-Type', 'application/vnd.v8+json') + reply.code(500) + reply.send({ desc: 'age is missing', details: 'validation error' }) + break + case 'application/vnd.v9+json': + reply.code(500) + reply.send({ details: 'validation error' }) + break default: // to test if schema not found reply.header('Content-Type', 'application/vnd.v3+json') @@ -179,9 +226,24 @@ test('Different content types', t => { content: { 'application/json': { schema: { - name: { type: 'string' }, - image: { type: 'string' }, - address: { type: 'string' } + type: 'object', + properties: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } + } + } + } + }, + default: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + details: { type: 'string' } + } } } } @@ -189,8 +251,23 @@ test('Different content types', t => { } } }, function (req, reply) { - reply.header('Content-Type', 'application/json') - reply.send({ age: 18, city: 'AU' }) + switch (req.headers['code']) { + case '200': { + reply.header('Content-Type', 'application/json') + reply.code(200).send({ age: 18, city: 'AU' }) + break + } + case '201': { + reply.header('Content-Type', 'application/json') + reply.code(201).send({ details: 'validation error' }) + break + } + default: { + reply.header('Content-Type', 'application/vnd.v1+json') + reply.code(201).send({ created: true }) + break + } + } }) fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/json' } }, (err, res) => { @@ -225,19 +302,19 @@ test('Different content types', t => { fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v4+json' } }, (err, res) => { t.error(err) - t.equal(res.payload, JSON.stringify({ content: 'Games' })) + t.equal(res.payload, '"[object Object]"') t.equal(res.statusCode, 201) }) fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v5+json' } }, (err, res) => { t.error(err) - t.equal(res.payload, JSON.stringify({ content: 'Processing exclusive content' })) + t.equal(res.payload, '"Processing exclusive content"') t.equal(res.statusCode, 202) }) fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v6+json' } }, (err, res) => { t.error(err) - t.equal(res.payload, JSON.stringify({ desc: 'age is missing', details: 'validation error' })) + t.equal(res.payload, JSON.stringify({ details: 'validation error' })) t.equal(res.statusCode, 400) }) @@ -247,11 +324,35 @@ test('Different content types', t => { t.equal(res.statusCode, 400) }) - fastify.inject({ method: 'GET', url: '/test' }, (err, res) => { + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v8+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ desc: 'age is missing', details: 'validation error' })) + t.equal(res.statusCode, 500) + }) + + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v9+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ details: 'validation error' })) + t.equal(res.statusCode, 500) + }) + + fastify.inject({ method: 'GET', url: '/test', headers: { Code: '200' } }, (err, res) => { t.error(err) t.equal(res.payload, JSON.stringify({ age: 18, city: 'AU' })) t.equal(res.statusCode, 200) }) + + fastify.inject({ method: 'GET', url: '/test', headers: { Code: '201' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ details: 'validation error' })) + t.equal(res.statusCode, 201) + }) + + fastify.inject({ method: 'GET', url: '/test', headers: { Accept: 'application/vnd.v1+json' } }, (err, res) => { + t.error(err) + t.equal(res.payload, JSON.stringify({ created: true })) + t.equal(res.statusCode, 201) + }) }) test('Invalid multiple content schema, throw FST_ERR_SCH_CONTENT_MISSING_SCHEMA error', t => { @@ -265,8 +366,11 @@ test('Invalid multiple content schema, throw FST_ERR_SCH_CONTENT_MISSING_SCHEMA content: { 'application/json': { schema: { - fullName: { type: 'string' }, - phone: { type: 'string' } + type: 'object', + properties: { + fullName: { type: 'string' }, + phone: { type: 'string' } + } }, example: { fullName: 'John Doe', @@ -321,7 +425,7 @@ test('Use the same schema id in different places', t => { url: '/123' }, (err, res) => { t.error(err) - t.same(res.json(), [{ id: 1 }, { id: 2 }, { }]) + t.same(res.json(), [{ id: 1 }, { id: 2 }, {}]) }) }) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index 879f85a518a..f4c8c0b9045 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -965,7 +965,12 @@ test('Custom AJV settings - pt1', t => { fastify.post('/', { schema: { - body: { num: { type: 'integer' } } + body: { + type: 'object', + properties: { + num: { type: 'integer' } + } + } }, handler: (req, reply) => { t.equal(req.body.num, 12) @@ -998,7 +1003,12 @@ test('Custom AJV settings - pt2', t => { fastify.post('/', { schema: { - body: { num: { type: 'integer' } } + body: { + type: 'object', + properties: { + num: { type: 'integer' } + } + } }, handler: (req, reply) => { t.fail('the handler is not called because the "12" is not coerced to number') @@ -1025,7 +1035,12 @@ test('Custom AJV settings on different parameters - pt1', t => { fastify.post('/api/:id', { schema: { - querystring: { id: { type: 'integer' } }, + querystring: { + type: 'object', + properties: { + id: { type: 'integer' } + } + }, body: { type: 'object', properties: { diff --git a/test/throw.test.js b/test/throw.test.js index 2f08ab081d2..cfa809f613e 100644 --- a/test/throw.test.js +++ b/test/throw.test.js @@ -74,18 +74,6 @@ test('Fastify should throw for an invalid schema, printing the error route - bod }) }) -test('Fastify should throw for an invalid shorthand option type', t => { - t.plan(3) - try { - Fastify({ jsonShorthand: 'hello' }) - t.fail() - } catch (e) { - t.equal(e.code, 'FST_ERR_INIT_OPTS_INVALID') - t.match(e.message, /must be boolean/) - t.pass() - } -}) - test('Should throw on unsupported method', t => { t.plan(1) const fastify = Fastify() diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 455af87d4f2..5c9c8894246 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -1,25 +1,26 @@ +import { ErrorObject as AjvErrorObject } from 'ajv' +import * as http from 'http' +import * as http2 from 'http2' +import * as https from 'https' +import { Socket } from 'net' +import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd' import fastify, { ConnectionError, + FastifyBaseLogger, + FastifyError, + FastifyErrorCodes, FastifyInstance, FastifyPlugin, FastifyPluginAsync, FastifyPluginCallback, + InjectOptions, + LightMyRequestCallback, LightMyRequestChain, LightMyRequestResponse, - LightMyRequestCallback, - InjectOptions, FastifyBaseLogger, RawRequestDefaultExpression, RouteGenericInterface, - FastifyErrorCodes, - FastifyError, SafePromiseLike } from '../../fastify' -import { ErrorObject as AjvErrorObject } from 'ajv' -import * as http from 'http' -import * as https from 'https' -import * as http2 from 'http2' -import { expectType, expectError, expectAssignable, expectNotAssignable } from 'tsd' -import { Socket } from 'net' // FastifyInstance // http server @@ -80,8 +81,8 @@ expectAssignable(fastify({ onProtoPoisoning: 'error' })) expectAssignable(fastify({ onConstructorPoisoning: 'error' })) expectAssignable(fastify({ serializerOpts: { rounding: 'ceil' } })) expectAssignable(fastify({ serializerOpts: { ajv: { missingRefs: 'ignore' } } })) -expectAssignable(fastify({ serializerOpts: { schema: { } } })) -expectAssignable(fastify({ serializerOpts: { otherProp: { } } })) +expectAssignable(fastify({ serializerOpts: { schema: {} } })) +expectAssignable(fastify({ serializerOpts: { otherProp: {} } })) expectAssignable>(fastify({ logger: true })) expectAssignable>(fastify({ logger: true })) expectAssignable>(fastify({ @@ -156,35 +157,35 @@ expectAssignable(fastify({ version: { name: 'version', storage: () => ({ - get: () => () => {}, + get: () => () => { }, set: () => { }, del: () => { }, empty: () => { } }), - validate () {}, + validate () { }, deriveConstraint: () => 'foo' }, host: { name: 'host', storage: () => ({ - get: () => () => {}, + get: () => () => { }, set: () => { }, del: () => { }, empty: () => { } }), - validate () {}, + validate () { }, deriveConstraint: () => 'foo' }, withObjectValue: { name: 'withObjectValue', storage: () => ({ - get: () => () => {}, + get: () => () => { }, set: () => { }, del: () => { }, empty: () => { } }), - validate () {}, - deriveConstraint: () => {} + validate () { }, + deriveConstraint: () => { } } } @@ -228,15 +229,14 @@ expectAssignable(fastify({ expectType(socket) } })) -expectAssignable(fastify({ jsonShorthand: true })) // Thenable expectAssignable>(fastify({ return503OnClosing: true })) fastify().then(fastifyInstance => expectAssignable(fastifyInstance)) -expectAssignable(async () => {}) -expectAssignable(() => {}) -expectAssignable(() => {}) +expectAssignable(async () => { }) +expectAssignable(() => { }) +expectAssignable(() => { }) const ajvErrorObject: AjvErrorObject = { keyword: '', diff --git a/test/types/schema.test-d.ts b/test/types/schema.test-d.ts index 8618a03665e..07091abac86 100644 --- a/test/types/schema.test-d.ts +++ b/test/types/schema.test-d.ts @@ -1,8 +1,8 @@ -import { expectAssignable } from 'tsd' -import fastify, { FastifyInstance, FastifySchema } from '../../fastify' -import Ajv from 'ajv' import { StandaloneValidator } from '@fastify/ajv-compiler' import { StandaloneSerializer } from '@fastify/fast-json-stringify-compiler' +import Ajv from 'ajv' +import { expectAssignable } from 'tsd' +import fastify, { FastifyInstance, FastifySchema } from '../../fastify' const server = fastify() @@ -90,7 +90,6 @@ expectAssignable(server.setSerializerCompiler(server.setSerializerCompiler Date: Fri, 26 Jul 2024 08:41:11 -0400 Subject: [PATCH 0718/1295] chore: remove deprecation 005 (#5589) --- docs/Reference/Request.md | 2 -- docs/Reference/Warnings.md | 2 -- lib/request.js | 7 ------- lib/warnings.js | 7 ------- test/internals/request.test.js | 4 ---- 5 files changed, 22 deletions(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 26b8fdacb3c..6dcee76e673 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -36,8 +36,6 @@ Request is a core Fastify object containing the following fields: - `routerPath` - Deprecated, use `request.routeOptions.url` instead. The path pattern defined for the router that is handling the request - `is404` - true if request is being handled by 404 handler, false if it is not -- `connection` - Deprecated, use `socket` instead. The underlying connection of - the incoming request. - `socket` - the underlying connection of the incoming request - `context` - Deprecated, use `request.routeOptions.config` instead. A Fastify internal object. You should not use diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 72c2a1baccb..862d44a884f 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -8,7 +8,6 @@ - [FSTWRN001](#FSTWRN001) - [FSTWRN002](#FSTWRN002) - [Fastify Deprecation Codes](#fastify-deprecation-codes) - - [FSTDEP005](#FSTDEP005) - [FSTDEP007](#FSTDEP007) - [FSTDEP008](#FSTDEP008) - [FSTDEP009](#FSTDEP009) @@ -71,7 +70,6 @@ Deprecation codes are further supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | -| FSTDEP005 | You are accessing the deprecated `request.connection` property. | Use `request.socket`. | [#2594](https://github.com/fastify/fastify/pull/2594) | | FSTDEP007 | You are trying to set a HEAD route using `exposeHeadRoute` route flag when a sibling route is already set. | Remove `exposeHeadRoutes` or explicitly set `exposeHeadRoutes` to `false` | [#2700](https://github.com/fastify/fastify/pull/2700) | | FSTDEP008 | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | | FSTDEP009 | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | diff --git a/lib/request.js b/lib/request.js index 0d966094b0f..5ac9309ad65 100644 --- a/lib/request.js +++ b/lib/request.js @@ -2,7 +2,6 @@ const proxyAddr = require('proxy-addr') const { - FSTDEP005, FSTDEP012, FSTDEP015, FSTDEP016, @@ -236,12 +235,6 @@ Object.defineProperties(Request.prototype, { return this[kRouteContext].config?.url === undefined } }, - connection: { - get () { - FSTDEP005() - return this.raw.connection - } - }, socket: { get () { return this.raw.socket diff --git a/lib/warnings.js b/lib/warnings.js index e4b606ac732..f47e1aa534b 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -4,7 +4,6 @@ const { createDeprecation, createWarning } = require('process-warning') /** * Deprecation codes: - * - FSTDEP005 * - FSTDEP007 * - FSTDEP008 * - FSTDEP009 @@ -21,11 +20,6 @@ const { createDeprecation, createWarning } = require('process-warning') * - FSTSEC001 */ -const FSTDEP005 = createDeprecation({ - code: 'FSTDEP005', - message: 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.' -}) - const FSTDEP007 = createDeprecation({ code: 'FSTDEP007', message: 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.' @@ -101,7 +95,6 @@ const FSTSEC001 = createWarning({ }) module.exports = { - FSTDEP005, FSTDEP007, FSTDEP008, FSTDEP009, diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 72d2a49e2f2..acd1e0b2372 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -74,8 +74,6 @@ test('Regular request', t => { // Aim to not bad property keys (including Symbols) t.notOk('undefined' in request) - // This will be removed, it's deprecated - t.equal(request.connection, req.connection) t.end() }) @@ -136,8 +134,6 @@ test('Request with undefined config', t => { // Aim to not bad property keys (including Symbols) t.notOk('undefined' in request) - // This will be removed, it's deprecated - t.equal(request.connection, req.connection) t.end() }) From 11cbf3bd8295a7844a1e32153b94736e7bb4f76f Mon Sep 17 00:00:00 2001 From: Diego Imbriani Date: Sun, 28 Jul 2024 12:11:16 +0200 Subject: [PATCH 0719/1295] fix: nullish host (#5590) --- lib/server.js | 3 ++- test/listen.1.test.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index 8e6f6bd489b..418ea37f6e1 100644 --- a/lib/server.js +++ b/lib/server.js @@ -55,7 +55,8 @@ function createServer (options, httpHandler) { } else { host = listenOptions.host } - if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false) { + if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false || + listenOptions.host == null) { listenOptions.host = host } if (host === 'localhost') { diff --git a/test/listen.1.test.js b/test/listen.1.test.js index 9b5aeb8e59d..6f1429221e5 100644 --- a/test/listen.1.test.js +++ b/test/listen.1.test.js @@ -89,3 +89,39 @@ test('listen after Promise.resolve()', t => { }) }) }) + +test('listen works with undefined host', async t => { + const doNotWarn = () => { + t.fail('should not be deprecated') + } + process.on('warning', doNotWarn) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + t.teardown(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) + await fastify.listen({ host: undefined, port: 0 }) + const address = fastify.server.address() + t.equal(address.address, localhost) + t.ok(address.port > 0) +}) + +test('listen works with null host', async t => { + const doNotWarn = () => { + t.fail('should not be deprecated') + } + process.on('warning', doNotWarn) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + t.teardown(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) + await fastify.listen({ host: null, port: 0 }) + const address = fastify.server.address() + t.equal(address.address, localhost) + t.ok(address.port > 0) +}) From 8a1ef73fc2597a143f417ca94bcaa923619f32b9 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 28 Jul 2024 12:11:35 +0200 Subject: [PATCH 0720/1295] chore(sponsor): add handsontable (#5592) Signed-off-by: Manuel Spigolon --- SPONSORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SPONSORS.md b/SPONSORS.md index b7f6d34890b..71c4e785eb9 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -15,6 +15,7 @@ _Be the first!_ - [Mercedes-Benz Group](https://github.com/mercedes-benz) - [Val Town, Inc.](https://opencollective.com/valtown) +- [Handsontable - JavaScript Data Grid](https://handsontable.com/) ## Tier 2 From 9a6a8aa7481b97ba560e0ffbc1773080b3312cbc Mon Sep 17 00:00:00 2001 From: Diego Imbriani Date: Sun, 28 Jul 2024 20:14:58 +0200 Subject: [PATCH 0721/1295] remove warning listener (#5598) --- test/listen.1.test.js | 44 +++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/test/listen.1.test.js b/test/listen.1.test.js index 6f1429221e5..d47765b5f56 100644 --- a/test/listen.1.test.js +++ b/test/listen.1.test.js @@ -12,12 +12,16 @@ before(async function () { }) test('listen works without arguments', async t => { - process.on('warning', () => { + const doNotWarn = () => { t.fail('should not be deprecated') - }) + } + process.on('warning', doNotWarn) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.teardown(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) await fastify.listen() const address = fastify.server.address() t.equal(address.address, localhost) @@ -25,12 +29,16 @@ test('listen works without arguments', async t => { }) test('Async/await listen with arguments', async t => { - process.on('warning', () => { + const doNotWarn = () => { t.fail('should not be deprecated') - }) + } + process.on('warning', doNotWarn) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.teardown(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) const addr = await fastify.listen({ port: 0, host: '0.0.0.0' }) const address = fastify.server.address() t.equal(addr, `http://127.0.0.1:${address.port}`) @@ -42,13 +50,17 @@ test('Async/await listen with arguments', async t => { }) test('listen accepts a callback', t => { - process.on('warning', () => { + t.plan(2) + const doNotWarn = () => { t.fail('should not be deprecated') - }) + } + process.on('warning', doNotWarn) - t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.teardown(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) fastify.listen({ port: 0 }, (err) => { t.equal(fastify.server.address().address, localhost) t.error(err) @@ -56,13 +68,17 @@ test('listen accepts a callback', t => { }) test('listen accepts options and a callback', t => { - process.on('warning', () => { + t.plan(1) + const doNotWarn = () => { t.fail('should not be deprecated') - }) + } + process.on('warning', doNotWarn) - t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.teardown(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) fastify.listen({ port: 0, host: 'localhost', From ad588c177d32ce9a220e35e6cd92192da77a33a8 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Mon, 29 Jul 2024 02:14:47 -0400 Subject: [PATCH 0722/1295] fix: test suite import.js emitting errors (#5599) --- eslint.config.js | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 9f9205da1e6..a12d9b004d2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,7 +7,8 @@ module.exports = [ ignores: [ 'lib/configValidator.js', 'lib/error-serializer.js', - 'test/same-shape.test.js' + 'test/same-shape.test.js', + 'test/types/import.js' ] }, ...neo({ diff --git a/package.json b/package.json index 5720adae834..d751d627ba4 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "test:ci": "npm run unit -- --coverage-report=lcovonly && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", - "test:typescript": "tsc test/types/import.ts && tsd", + "test:typescript": "tsc test/types/import.ts --noEmit && tsd", "test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse", "unit": "tap", "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", From 6cbfe0559b9996ca1d6d306ea506bd31210951ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:04:32 +0000 Subject: [PATCH 0723/1295] chore: Bump @types/node in the dev-dependencies group (#5600) Bumps the dev-dependencies group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 20.14.13 to 22.0.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d751d627ba4..f1bca6ec9a4 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "@sinonjs/fake-timers": "^11.2.2", "@stylistic/eslint-plugin": "^2.1.0", "@stylistic/eslint-plugin-js": "^2.1.0", - "@types/node": "^20.12.7", + "@types/node": "^22.0.0", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", From 07b425c13ccb018ba4cc34b9aaa4e201ac93468c Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Wed, 31 Jul 2024 13:41:10 +0200 Subject: [PATCH 0724/1295] docs(reply): standardize import style in examples (#5580) --- docs/Reference/Reply.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 9d82d134059..a906e6666cb 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -700,8 +700,9 @@ As noted above, streams are considered to be pre-serialized, so they will be sent unmodified without response validation. ```js +const fs = require('node:fs') + fastify.get('/streams', function (request, reply) { - const fs = require('node:fs') const stream = fs.createReadStream('some-file', 'utf8') reply.header('Content-Type', 'application/octet-stream') reply.send(stream) @@ -709,8 +710,9 @@ fastify.get('/streams', function (request, reply) { ``` When using async-await you will need to return or await the reply object: ```js +const fs = require('node:fs') + fastify.get('/streams', async function (request, reply) { - const fs = require('node:fs') const stream = fs.createReadStream('some-file', 'utf8') reply.header('Content-Type', 'application/octet-stream') return reply.send(stream) @@ -728,6 +730,7 @@ sent unmodified without response validation. ```js const fs = require('node:fs') + fastify.get('/streams', function (request, reply) { fs.readFile('some-file', (err, fileBuffer) => { reply.send(err || fileBuffer) @@ -738,6 +741,7 @@ fastify.get('/streams', function (request, reply) { When using async-await you will need to return or await the reply object: ```js const fs = require('node:fs') + fastify.get('/streams', async function (request, reply) { fs.readFile('some-file', (err, fileBuffer) => { reply.send(err || fileBuffer) @@ -757,6 +761,7 @@ will be sent unmodified without response validation. ```js const fs = require('node:fs') + fastify.get('/streams', function (request, reply) { const typedArray = new Uint16Array(10) reply.send(typedArray) @@ -773,6 +778,7 @@ sent unmodified without response validation. ```js const fs = require('node:fs') const { ReadableStream } = require('node:stream/web') + fastify.get('/streams', function (request, reply) { const stream = fs.createReadStream('some-file') reply.header('Content-Type', 'application/octet-stream') @@ -797,6 +803,7 @@ and may confuse when checking the `payload` in `onSend` hooks. ```js const fs = require('node:fs') const { ReadableStream } = require('node:stream/web') + fastify.get('/streams', function (request, reply) { const stream = fs.createReadStream('some-file') const readableStream = ReadableStream.from(stream) From 1184d6cd0fdc4e38493b221590f3e5ad97ec3029 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 31 Jul 2024 18:24:56 +0100 Subject: [PATCH 0725/1295] docs(typescript): update example fastify version (#5602) --- docs/Reference/TypeScript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 0f23571b0f4..18297ff277a 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -594,7 +594,7 @@ your plugin. } module.exports = fp(myPlugin, { - fastify: '3.x', + fastify: '5.x', name: 'my-plugin' // this is used by fastify-plugin to derive the property name }) ``` From 5845c510e7681df03eb519ab58abdff26b7c2abe Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 2 Aug 2024 11:31:53 +0800 Subject: [PATCH 0726/1295] refactor: remove FSTDEP007 (#5604) * refactor: remove FSTDEP007 * fixup --- docs/Reference/Warnings.md | 2 - lib/route.js | 8 --- lib/warnings.js | 7 --- test/http-methods/head.test.js | 92 +++------------------------------- test/route.7.test.js | 14 +----- 5 files changed, 8 insertions(+), 115 deletions(-) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 862d44a884f..e3f4e1937ca 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -8,7 +8,6 @@ - [FSTWRN001](#FSTWRN001) - [FSTWRN002](#FSTWRN002) - [Fastify Deprecation Codes](#fastify-deprecation-codes) - - [FSTDEP007](#FSTDEP007) - [FSTDEP008](#FSTDEP008) - [FSTDEP009](#FSTDEP009) - [FSTDEP010](#FSTDEP010) @@ -70,7 +69,6 @@ Deprecation codes are further supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | -| FSTDEP007 | You are trying to set a HEAD route using `exposeHeadRoute` route flag when a sibling route is already set. | Remove `exposeHeadRoutes` or explicitly set `exposeHeadRoutes` to `false` | [#2700](https://github.com/fastify/fastify/pull/2700) | | FSTDEP008 | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | | FSTDEP009 | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | | FSTDEP010 | Modifying the `reply.sent` property is deprecated. | Use the `reply.hijack()` method. | [#3140](https://github.com/fastify/fastify/pull/3140) | diff --git a/lib/route.js b/lib/route.js index f2e11b498e6..3e4463b9e5c 100644 --- a/lib/route.js +++ b/lib/route.js @@ -7,7 +7,6 @@ const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeou const { normalizeSchema } = require('./schemas') const { parseHeadOnSendHandlers } = require('./headRoute') const { - FSTDEP007, FSTDEP008 } = require('./warnings') @@ -340,11 +339,6 @@ function buildRouting (options) { const headHandler = router.findRoute('HEAD', opts.url, constraints) const hasHEADHandler = headHandler !== null - // remove the head route created by fastify - if (isHeadRoute && hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) { - router.off('HEAD', opts.url, constraints) - } - try { router.on(opts.method, opts.url, { constraints }, routeHandler, context) } catch (error) { @@ -425,8 +419,6 @@ function buildRouting (options) { if (shouldExposeHead && isGetRoute && !isHeadRoute && !hasHEADHandler) { const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend) prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true }) - } else if (hasHEADHandler && exposeHeadRoute) { - FSTDEP007() } } } diff --git a/lib/warnings.js b/lib/warnings.js index f47e1aa534b..3a0ccefea6d 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -4,7 +4,6 @@ const { createDeprecation, createWarning } = require('process-warning') /** * Deprecation codes: - * - FSTDEP007 * - FSTDEP008 * - FSTDEP009 * - FSTDEP010 @@ -20,11 +19,6 @@ const { createDeprecation, createWarning } = require('process-warning') * - FSTSEC001 */ -const FSTDEP007 = createDeprecation({ - code: 'FSTDEP007', - message: 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.' -}) - const FSTDEP008 = createDeprecation({ code: 'FSTDEP008', message: 'You are using route constraints via the route { version: "..." } option, use { constraints: { version: "..." } } option instead.' @@ -95,7 +89,6 @@ const FSTSEC001 = createWarning({ }) module.exports = { - FSTDEP007, FSTDEP008, FSTDEP009, FSTDEP010, diff --git a/test/http-methods/head.test.js b/test/http-methods/head.test.js index 1a748efb8b5..0e1a25a9139 100644 --- a/test/http-methods/head.test.js +++ b/test/http-methods/head.test.js @@ -59,12 +59,12 @@ test('shorthand - head', t => { test('shorthand - custom head', t => { t.plan(1) try { - fastify.get('/proxy/*', function (req, reply) { + fastify.head('/proxy/*', function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) reply.code(200).send(null) }) - fastify.head('/proxy/*', function (req, reply) { - reply.headers({ 'x-foo': 'bar' }) + fastify.get('/proxy/*', function (req, reply) { reply.code(200).send(null) }) @@ -77,12 +77,12 @@ test('shorthand - custom head', t => { test('shorthand - custom head with constraints', t => { t.plan(1) try { - fastify.get('/proxy/*', { constraints: { version: '1.0.0' } }, function (req, reply) { + fastify.head('/proxy/*', { constraints: { version: '1.0.0' } }, function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) reply.code(200).send(null) }) - fastify.head('/proxy/*', { constraints: { version: '1.0.0' } }, function (req, reply) { - reply.headers({ 'x-foo': 'bar' }) + fastify.get('/proxy/*', { constraints: { version: '1.0.0' } }, function (req, reply) { reply.code(200).send(null) }) @@ -109,62 +109,6 @@ test('shorthand - should not reset a head route', t => { } }) -test('shorthand - should override head route when setting multiple routes', t => { - t.plan(1) - try { - fastify.route({ - method: 'GET', - url: '/query2', - handler: function (req, reply) { - reply.headers({ 'x-foo': 'bar' }) - reply.code(200).send(null) - } - }) - - fastify.route({ - method: ['POST', 'PUT', 'HEAD'], - url: '/query2', - handler: function (req, reply) { - reply.headers({ 'x-foo': 'bar' }) - reply.code(200).send(null) - } - }) - - t.pass() - } catch (e) { - console.log(e) - t.fail() - } -}) - -test('shorthand - should override head route when setting multiple routes', t => { - t.plan(1) - try { - fastify.route({ - method: ['GET'], - url: '/query3', - handler: function (req, reply) { - reply.headers({ 'x-foo': 'bar' }) - reply.code(200).send(null) - } - }) - - fastify.route({ - method: ['POST', 'PUT', 'HEAD'], - url: '/query3', - handler: function (req, reply) { - reply.headers({ 'x-foo': 'bar' }) - reply.code(200).send(null) - } - }) - - t.pass() - } catch (e) { - console.log(e) - t.fail() - } -}) - test('shorthand - should set get and head route in the same api call', t => { t.plan(1) try { @@ -330,30 +274,6 @@ fastify.listen({ port: 0 }, err => { }) }) - test('shorthand - should override head route when setting multiple routes', t => { - t.plan(3) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/query2' - }, (err, response) => { - t.error(err) - t.equal(response.headers['x-foo'], 'bar') - t.equal(response.statusCode, 200) - }) - }) - - test('shorthand - should override head route when setting multiple routes', t => { - t.plan(3) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/query3' - }, (err, response) => { - t.error(err) - t.equal(response.headers['x-foo'], 'bar') - t.equal(response.statusCode, 200) - }) - }) - test('shorthand - should set get and head route in the same api call', t => { t.plan(3) sget({ diff --git a/test/route.7.test.js b/test/route.7.test.js index 536eec5670e..c437f160845 100644 --- a/test/route.7.test.js +++ b/test/route.7.test.js @@ -5,7 +5,6 @@ const split = require('split2') const t = require('tap') const test = t.test const Fastify = require('..') -const proxyquire = require('proxyquire') test("HEAD route should handle stream.on('error')", t => { t.plan(6) @@ -176,18 +175,9 @@ test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes }) test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', t => { - t.plan(7) - - function onWarning () { - t.pass('warning emitted') - } + t.plan(6) - const route = proxyquire('../lib/route', { - './warnings': { - FSTDEP007: onWarning - } - }) - const fastify = proxyquire('..', { './lib/route.js': route })() + const fastify = Fastify() const resBuffer = Buffer.from('I am a coffee!') From 9a42c76dbab94335a174fa2c8def7d68e5bf9258 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:49:58 +0800 Subject: [PATCH 0727/1295] refactor: remove `FSTDEP008` and `FSTDEP009` (#5609) * refactor: remove deprecated route version and versioning option * fixup --- build/build-validation.js | 16 +-- docs/Reference/Errors.md | 2 - docs/Reference/Warnings.md | 4 - fastify.d.ts | 12 -- fastify.js | 25 +---- lib/errors.js | 6 - lib/route.js | 8 -- lib/warnings.js | 14 --- test/internals/errors.test.js | 24 ++-- test/internals/initialConfig.test.js | 29 ----- test/types/errors.test-d.ts | 3 +- test/types/fastify.test-d.ts | 11 -- test/versioned-routes.test.js | 90 --------------- types/errors.d.ts | 157 +++++++++++++-------------- types/request.d.ts | 17 ++- types/route.d.ts | 3 +- 16 files changed, 100 insertions(+), 321 deletions(-) diff --git a/build/build-validation.js b/build/build-validation.js index 3fddf1d7092..9e85d3f04d6 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -102,16 +102,6 @@ const schema = { http2SessionTimeout: { type: 'integer', default: defaultInitOptions.http2SessionTimeout }, exposeHeadRoutes: { type: 'boolean', default: defaultInitOptions.exposeHeadRoutes }, useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.useSemicolonDelimiter }, - // deprecated style of passing the versioning constraint - versioning: { - type: 'object', - additionalProperties: true, - required: ['storage', 'deriveVersion'], - properties: { - storage: { }, - deriveVersion: { } - } - }, constraints: { type: 'object', additionalProperties: { @@ -120,9 +110,9 @@ const schema = { additionalProperties: true, properties: { name: { type: 'string' }, - storage: { }, - validate: { }, - deriveConstraint: { } + storage: {}, + validate: {}, + deriveConstraint: {} } } } diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 56c0992a8f6..6543f1e622b 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -20,7 +20,6 @@ - [FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN](#fst_err_schema_error_formatter_not_fn) - [FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ](#fst_err_ajv_custom_options_opt_not_obj) - [FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR](#fst_err_ajv_custom_options_opt_not_arr) - - [FST_ERR_VERSION_CONSTRAINT_NOT_STR](#fst_err_version_constraint_not_str) - [FST_ERR_CTP_ALREADY_PRESENT](#fst_err_ctp_already_present) - [FST_ERR_CTP_INVALID_TYPE](#fst_err_ctp_invalid_type) - [FST_ERR_CTP_EMPTY_TYPE](#fst_err_ctp_empty_type) @@ -293,7 +292,6 @@ Below is a table with all the error codes that Fastify uses. | FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN | SchemaErrorFormatter option wrongly specified. | SchemaErrorFormatter option should be a non async function. | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ | ajv.customOptions wrongly specified. | ajv.customOptions option should be an object. | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR | ajv.plugins option wrongly specified. | ajv.plugins option should be an array. | [#4554](https://github.com/fastify/fastify/pull/4554) | -| FST_ERR_VERSION_CONSTRAINT_NOT_STR | Version constraint wrongly specified. | Version constraint should be a string. | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_CTP_ALREADY_PRESENT | The parser for this content type was already registered. | Use a different content type or delete the already registered parser. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_CTP_INVALID_TYPE | `Content-Type` wrongly specified | The `Content-Type` should be a string. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_CTP_EMPTY_TYPE | `Content-Type` is an empty string. | `Content-Type` cannot be an empty string. | [#1168](https://github.com/fastify/fastify/pull/1168) | diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index e3f4e1937ca..61cf09142fe 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -8,8 +8,6 @@ - [FSTWRN001](#FSTWRN001) - [FSTWRN002](#FSTWRN002) - [Fastify Deprecation Codes](#fastify-deprecation-codes) - - [FSTDEP008](#FSTDEP008) - - [FSTDEP009](#FSTDEP009) - [FSTDEP010](#FSTDEP010) - [FSTDEP012](#FSTDEP012) - [FSTDEP013](#FSTDEP013) @@ -69,8 +67,6 @@ Deprecation codes are further supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | -| FSTDEP008 | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | -| FSTDEP009 | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | | FSTDEP010 | Modifying the `reply.sent` property is deprecated. | Use the `reply.hijack()` method. | [#3140](https://github.com/fastify/fastify/pull/3140) | | FSTDEP012 | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) | | FSTDEP013 | Direct return of "trailers" function is deprecated. | Use "callback" or "async-await" for return value. | [#4380](https://github.com/fastify/fastify/pull/4380) | diff --git a/fastify.d.ts b/fastify.d.ts index 7b643457bf0..e24ed604fca 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -114,18 +114,6 @@ declare namespace fastify { genReqId?: (req: RawRequestDefaultExpression) => string, trustProxy?: boolean | string | string[] | number | TrustProxyFunction, querystringParser?: (str: string) => { [key: string]: unknown }, - /** - * @deprecated Prefer using the `constraints.version` property - */ - versioning?: { - storage(): { - get(version: string): string | null, - set(version: string, store: Function): void - del(version: string): void, - empty(): void - }, - deriveVersion(req: Object, ctx?: Context): string // not a fan of using Object here. Also what is Context? Can either of these be better defined? - }, constraints?: { [name: string]: ConstraintStrategy, unknown>, }, diff --git a/fastify.js b/fastify.js index 0936c357c22..b3f94559a88 100644 --- a/fastify.js +++ b/fastify.js @@ -49,7 +49,6 @@ const { buildRouting, validateBodyLimitOption } = require('./lib/route') const build404 = require('./lib/fourOhFour') const getSecuredInitialConfig = require('./lib/initialConfigValidation') const override = require('./lib/pluginOverride') -const { FSTDEP009 } = require('./lib/warnings') const noopSet = require('./lib/noop-set') const { appendStackTrace, @@ -68,7 +67,6 @@ const { FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN, FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ, FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR, - FST_ERR_VERSION_CONSTRAINT_NOT_STR, FST_ERR_INSTANCE_ALREADY_LISTENING, FST_ERR_REOPENED_CLOSE_SERVER, FST_ERR_ROUTE_REWRITE_NOT_STR, @@ -155,31 +153,12 @@ function fastify (options) { // exposeHeadRoutes have its default set from the validator options.exposeHeadRoutes = initialConfig.exposeHeadRoutes - let constraints = options.constraints - if (options.versioning) { - FSTDEP009() - constraints = { - ...constraints, - version: { - name: 'version', - mustMatchWhenDerived: true, - storage: options.versioning.storage, - deriveConstraint: options.versioning.deriveVersion, - validate (value) { - if (typeof value !== 'string') { - throw new FST_ERR_VERSION_CONSTRAINT_NOT_STR() - } - } - } - } - } - // Default router const router = buildRouting({ config: { defaultRoute, onBadUrl, - constraints, + constraints: options.constraints, ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash, ignoreDuplicateSlashes: options.ignoreDuplicateSlashes || defaultInitOptions.ignoreDuplicateSlashes, maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength, @@ -482,7 +461,7 @@ function fastify (options) { if (forceCloseConnections === 'idle') { // Not needed in Node 19 instance.server.closeIdleConnections() - /* istanbul ignore next: Cannot test this without Node.js core support */ + /* istanbul ignore next: Cannot test this without Node.js core support */ } else if (serverHasCloseAllConnections && forceCloseConnections) { instance.server.closeAllConnections() } else if (forceCloseConnections === true) { diff --git a/lib/errors.js b/lib/errors.js index 3e7a3f9512b..9fafe866521 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -47,12 +47,6 @@ const codes = { 500, TypeError ), - FST_ERR_VERSION_CONSTRAINT_NOT_STR: createError( - 'FST_ERR_VERSION_CONSTRAINT_NOT_STR', - 'Version constraint should be a string.', - 500, - TypeError - ), FST_ERR_VALIDATION: createError( 'FST_ERR_VALIDATION', '%s', diff --git a/lib/route.js b/lib/route.js index 3e4463b9e5c..8fe8962f5a8 100644 --- a/lib/route.js +++ b/lib/route.js @@ -6,9 +6,6 @@ const handleRequest = require('./handleRequest') const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks') const { normalizeSchema } = require('./schemas') const { parseHeadOnSendHandlers } = require('./headRoute') -const { - FSTDEP008 -} = require('./warnings') const { compileSchemasForValidation, @@ -331,11 +328,6 @@ function buildRouting (options) { isFastify }) - if (opts.version) { - FSTDEP008() - constraints.version = opts.version - } - const headHandler = router.findRoute('HEAD', opts.url, constraints) const hasHEADHandler = headHandler !== null diff --git a/lib/warnings.js b/lib/warnings.js index 3a0ccefea6d..d17f7a62132 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -4,8 +4,6 @@ const { createDeprecation, createWarning } = require('process-warning') /** * Deprecation codes: - * - FSTDEP008 - * - FSTDEP009 * - FSTDEP010 * - FSTDEP012 * - FSTDEP013 @@ -19,16 +17,6 @@ const { createDeprecation, createWarning } = require('process-warning') * - FSTSEC001 */ -const FSTDEP008 = createDeprecation({ - code: 'FSTDEP008', - message: 'You are using route constraints via the route { version: "..." } option, use { constraints: { version: "..." } } option instead.' -}) - -const FSTDEP009 = createDeprecation({ - code: 'FSTDEP009', - message: 'You are using a custom route versioning strategy via the server { versioning: "..." } option, use { constraints: { version: "..." } } option instead.' -}) - const FSTDEP010 = createDeprecation({ code: 'FSTDEP010', message: 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.' @@ -89,8 +77,6 @@ const FSTSEC001 = createWarning({ }) module.exports = { - FSTDEP008, - FSTDEP009, FSTDEP010, FSTDEP012, FSTDEP013, diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 570391e8d41..92365cc55dc 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -test('should expose 84 errors', t => { +test('should expose 83 errors', t => { t.plan(1) const exportedKeys = Object.keys(errors) let counter = 0 @@ -14,11 +14,11 @@ test('should expose 84 errors', t => { counter++ } } - t.equal(counter, 84) + t.equal(counter, 83) }) test('ensure name and codes of Errors are identical', t => { - t.plan(84) + t.plan(83) const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -97,16 +97,6 @@ test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR', t => { t.ok(error instanceof TypeError) }) -test('FST_ERR_VERSION_CONSTRAINT_NOT_STR', t => { - t.plan(5) - const error = new errors.FST_ERR_VERSION_CONSTRAINT_NOT_STR() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_VERSION_CONSTRAINT_NOT_STR') - t.equal(error.message, 'Version constraint should be a string.') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) -}) - test('FST_ERR_CTP_ALREADY_PRESENT', t => { t.plan(5) const error = new errors.FST_ERR_CTP_ALREADY_PRESENT() @@ -878,7 +868,7 @@ test('FST_ERR_ERROR_HANDLER_NOT_FN', t => { }) test('Ensure that all errors are in Errors.md TOC', t => { - t.plan(84) + t.plan(83) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -890,7 +880,7 @@ test('Ensure that all errors are in Errors.md TOC', t => { }) test('Ensure that non-existing errors are not in Errors.md TOC', t => { - t.plan(84) + t.plan(83) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = / {4}- \[([A-Z0-9_]+)\]\(#[a-z0-9_]+\)/g @@ -903,7 +893,7 @@ test('Ensure that non-existing errors are not in Errors.md TOC', t => { }) test('Ensure that all errors are in Errors.md documented', t => { - t.plan(84) + t.plan(83) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -915,7 +905,7 @@ test('Ensure that all errors are in Errors.md documented', t => { }) test('Ensure that non-existing errors are not in Errors.md documented', t => { - t.plan(84) + t.plan(83) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /([0-9a-zA-Z_]+)<\/a>/g diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index 5efa0c4ce74..1455cc01039 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -360,35 +360,6 @@ test('deepFreezeObject() should not throw on TypedArray', t => { } }) -test('Fastify.initialConfig should accept the deprecated versioning option', t => { - t.plan(1) - - function onWarning (warning) { - t.equal(warning.code, 'FSTDEP009') - } - - process.on('warning', onWarning) - - const versioning = { - storage: function () { - const versions = {} - return { - get: (version) => { return versions[version] || null }, - set: (version, store) => { versions[version] = store } - } - }, - deriveVersion: (req, ctx) => { - return req.headers.accept - } - } - - Fastify({ versioning }) - setImmediate(function () { - process.removeListener('warning', onWarning) - t.end() - }) -}) - test('pluginTimeout should be parsed correctly', t => { const withDisabledTimeout = Fastify({ pluginTimeout: '0' }) t.equal(withDisabledTimeout.initialConfig.pluginTimeout, 0) diff --git a/test/types/errors.test-d.ts b/test/types/errors.test-d.ts index fd674731017..cf1843321dc 100644 --- a/test/types/errors.test-d.ts +++ b/test/types/errors.test-d.ts @@ -1,6 +1,6 @@ +import { FastifyErrorConstructor } from '@fastify/error' import { expectAssignable } from 'tsd' import { errorCodes } from '../../fastify' -import { FastifyErrorConstructor } from '@fastify/error' expectAssignable(errorCodes.FST_ERR_VALIDATION) expectAssignable(errorCodes.FST_ERR_NOT_FOUND) @@ -10,7 +10,6 @@ expectAssignable(errorCodes.FST_ERR_SCHEMA_CONTROLLER_B expectAssignable(errorCodes.FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN) expectAssignable(errorCodes.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ) expectAssignable(errorCodes.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR) -expectAssignable(errorCodes.FST_ERR_VERSION_CONSTRAINT_NOT_STR) expectAssignable(errorCodes.FST_ERR_VALIDATION) expectAssignable(errorCodes.FST_ERR_CTP_ALREADY_PRESENT) expectAssignable(errorCodes.FST_ERR_CTP_INVALID_TYPE) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 5c9c8894246..ed536325f81 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -140,17 +140,6 @@ expectAssignable(fastify({ trustProxy: true })) expectAssignable(fastify({ querystringParser: () => ({ foo: 'bar' }) })) expectAssignable(fastify({ querystringParser: () => ({ foo: { bar: 'fuzz' } }) })) expectAssignable(fastify({ querystringParser: () => ({ foo: ['bar', 'fuzz'] }) })) -expectAssignable(fastify({ - versioning: { - storage: () => ({ - get: () => 'foo', - set: () => { }, - del: () => { }, - empty: () => { } - }), - deriveVersion: () => 'foo' - } -})) expectAssignable(fastify({ constraints: {} })) expectAssignable(fastify({ constraints: { diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js index b38dc05c058..0ba950dee8a 100644 --- a/test/versioned-routes.test.js +++ b/test/versioned-routes.test.js @@ -7,7 +7,6 @@ const sget = require('simple-get').concat const http = require('node:http') const split = require('split2') const append = require('vary').append -const proxyquire = require('proxyquire') process.removeAllListeners('warning') @@ -553,59 +552,6 @@ test('Should register a versioned route with custom versioning strategy', t => { }) }) -test('Should get error using an invalid a versioned route, using default validation (deprecated versioning option)', t => { - t.plan(3) - - const fastify = Fastify({ - versioning: { - storage: function () { - const versions = {} - return { - get: (version) => { return versions[version] || null }, - set: (version, store) => { versions[version] = store } - } - }, - deriveVersion: (req, ctx) => { - return req.headers.accept - } - } - }) - - fastify.route({ - method: 'GET', - url: '/', - constraints: { version: 'application/vnd.example.api+json;version=1' }, - handler: (req, reply) => { - reply.send({ hello: 'cant match route v1' }) - } - }) - - try { - fastify.route({ - method: 'GET', - url: '/', - // not a string version - constraints: { version: 2 }, - handler: (req, reply) => { - reply.send({ hello: 'cant match route v2' }) - } - }) - } catch (err) { - t.equal(err.message, 'Version constraint should be a string.') - } - - fastify.inject({ - method: 'GET', - url: '/', - headers: { - Accept: 'application/vnd.example.api+json;version=2' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) -}) - test('Vary header check (for documentation example)', t => { t.plan(8) const fastify = Fastify() @@ -659,39 +605,3 @@ test('Vary header check (for documentation example)', t => { t.equal(res.headers.vary, undefined) }) }) - -test('Should trigger a warning when a versioned route is registered via version option', t => { - t.plan(4) - - function onWarning () { - t.pass('FSTDEP008 has been emitted') - } - - const route = proxyquire('../lib/route', { - './warnings': { - FSTDEP008: onWarning - } - }) - const fastify = proxyquire('..', { './lib/route.js': route })({ exposeHeadRoutes: false }) - - fastify.route({ - method: 'GET', - url: '/', - version: '1.2.0', - handler: (req, reply) => { - reply.send({ hello: 'world' }) - } - }) - - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'Accept-Version': '1.x' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) - }) -}) diff --git a/types/errors.d.ts b/types/errors.d.ts index 20f31d74055..add96123c87 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -1,82 +1,81 @@ import { FastifyErrorConstructor } from '@fastify/error' export type FastifyErrorCodes = Record< -'FST_ERR_NOT_FOUND' | -'FST_ERR_OPTIONS_NOT_OBJ' | -'FST_ERR_QSP_NOT_FN' | -'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN' | -'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN' | -'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ' | -'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR' | -'FST_ERR_VERSION_CONSTRAINT_NOT_STR' | -'FST_ERR_VALIDATION' | -'FST_ERR_CTP_ALREADY_PRESENT' | -'FST_ERR_CTP_INVALID_TYPE' | -'FST_ERR_CTP_EMPTY_TYPE' | -'FST_ERR_CTP_INVALID_HANDLER' | -'FST_ERR_CTP_INVALID_PARSE_TYPE' | -'FST_ERR_CTP_BODY_TOO_LARGE' | -'FST_ERR_CTP_INVALID_MEDIA_TYPE' | -'FST_ERR_CTP_INVALID_CONTENT_LENGTH' | -'FST_ERR_CTP_EMPTY_JSON_BODY' | -'FST_ERR_CTP_INSTANCE_ALREADY_STARTED' | -'FST_ERR_DEC_ALREADY_PRESENT' | -'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE' | -'FST_ERR_DEC_MISSING_DEPENDENCY' | -'FST_ERR_DEC_AFTER_START' | -'FST_ERR_DEC_REFERENCE_TYPE' | -'FST_ERR_HOOK_INVALID_TYPE' | -'FST_ERR_HOOK_INVALID_HANDLER' | -'FST_ERR_HOOK_INVALID_ASYNC_HANDLER' | -'FST_ERR_HOOK_NOT_SUPPORTED' | -'FST_ERR_MISSING_MIDDLEWARE' | -'FST_ERR_HOOK_TIMEOUT' | -'FST_ERR_LOG_INVALID_DESTINATION' | -'FST_ERR_LOG_INVALID_LOGGER' | -'FST_ERR_REP_INVALID_PAYLOAD_TYPE' | -'FST_ERR_REP_ALREADY_SENT' | -'FST_ERR_REP_SENT_VALUE' | -'FST_ERR_SEND_INSIDE_ONERR' | -'FST_ERR_SEND_UNDEFINED_ERR' | -'FST_ERR_BAD_STATUS_CODE' | -'FST_ERR_BAD_TRAILER_NAME' | -'FST_ERR_BAD_TRAILER_VALUE' | -'FST_ERR_FAILED_ERROR_SERIALIZATION' | -'FST_ERR_MISSING_SERIALIZATION_FN' | -'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN' | -'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION' | -'FST_ERR_SCH_MISSING_ID' | -'FST_ERR_SCH_ALREADY_PRESENT' | -'FST_ERR_SCH_CONTENT_MISSING_SCHEMA' | -'FST_ERR_SCH_DUPLICATE' | -'FST_ERR_SCH_VALIDATION_BUILD' | -'FST_ERR_SCH_SERIALIZATION_BUILD' | -'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX' | -'FST_ERR_HTTP2_INVALID_VERSION' | -'FST_ERR_INIT_OPTS_INVALID' | -'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE' | -'FST_ERR_DUPLICATED_ROUTE' | -'FST_ERR_BAD_URL' | -'FST_ERR_ASYNC_CONSTRAINT' | -'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE' | -'FST_ERR_INVALID_URL' | -'FST_ERR_ROUTE_OPTIONS_NOT_OBJ' | -'FST_ERR_ROUTE_DUPLICATED_HANDLER' | -'FST_ERR_ROUTE_HANDLER_NOT_FN' | -'FST_ERR_ROUTE_MISSING_HANDLER' | -'FST_ERR_ROUTE_METHOD_INVALID' | -'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED' | -'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED' | -'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT' | -'FST_ERR_ROUTE_REWRITE_NOT_STR' | -'FST_ERR_REOPENED_CLOSE_SERVER' | -'FST_ERR_REOPENED_SERVER' | -'FST_ERR_INSTANCE_ALREADY_LISTENING' | -'FST_ERR_PLUGIN_VERSION_MISMATCH' | -'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE' | -'FST_ERR_PLUGIN_CALLBACK_NOT_FN' | -'FST_ERR_PLUGIN_NOT_VALID' | -'FST_ERR_ROOT_PLG_BOOTED' | -'FST_ERR_PARENT_PLUGIN_BOOTED' | -'FST_ERR_PLUGIN_TIMEOUT' -, FastifyErrorConstructor> + 'FST_ERR_NOT_FOUND' | + 'FST_ERR_OPTIONS_NOT_OBJ' | + 'FST_ERR_QSP_NOT_FN' | + 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN' | + 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN' | + 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ' | + 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR' | + 'FST_ERR_VALIDATION' | + 'FST_ERR_CTP_ALREADY_PRESENT' | + 'FST_ERR_CTP_INVALID_TYPE' | + 'FST_ERR_CTP_EMPTY_TYPE' | + 'FST_ERR_CTP_INVALID_HANDLER' | + 'FST_ERR_CTP_INVALID_PARSE_TYPE' | + 'FST_ERR_CTP_BODY_TOO_LARGE' | + 'FST_ERR_CTP_INVALID_MEDIA_TYPE' | + 'FST_ERR_CTP_INVALID_CONTENT_LENGTH' | + 'FST_ERR_CTP_EMPTY_JSON_BODY' | + 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED' | + 'FST_ERR_DEC_ALREADY_PRESENT' | + 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE' | + 'FST_ERR_DEC_MISSING_DEPENDENCY' | + 'FST_ERR_DEC_AFTER_START' | + 'FST_ERR_DEC_REFERENCE_TYPE' | + 'FST_ERR_HOOK_INVALID_TYPE' | + 'FST_ERR_HOOK_INVALID_HANDLER' | + 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER' | + 'FST_ERR_HOOK_NOT_SUPPORTED' | + 'FST_ERR_MISSING_MIDDLEWARE' | + 'FST_ERR_HOOK_TIMEOUT' | + 'FST_ERR_LOG_INVALID_DESTINATION' | + 'FST_ERR_LOG_INVALID_LOGGER' | + 'FST_ERR_REP_INVALID_PAYLOAD_TYPE' | + 'FST_ERR_REP_ALREADY_SENT' | + 'FST_ERR_REP_SENT_VALUE' | + 'FST_ERR_SEND_INSIDE_ONERR' | + 'FST_ERR_SEND_UNDEFINED_ERR' | + 'FST_ERR_BAD_STATUS_CODE' | + 'FST_ERR_BAD_TRAILER_NAME' | + 'FST_ERR_BAD_TRAILER_VALUE' | + 'FST_ERR_FAILED_ERROR_SERIALIZATION' | + 'FST_ERR_MISSING_SERIALIZATION_FN' | + 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN' | + 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION' | + 'FST_ERR_SCH_MISSING_ID' | + 'FST_ERR_SCH_ALREADY_PRESENT' | + 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA' | + 'FST_ERR_SCH_DUPLICATE' | + 'FST_ERR_SCH_VALIDATION_BUILD' | + 'FST_ERR_SCH_SERIALIZATION_BUILD' | + 'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX' | + 'FST_ERR_HTTP2_INVALID_VERSION' | + 'FST_ERR_INIT_OPTS_INVALID' | + 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE' | + 'FST_ERR_DUPLICATED_ROUTE' | + 'FST_ERR_BAD_URL' | + 'FST_ERR_ASYNC_CONSTRAINT' | + 'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE' | + 'FST_ERR_INVALID_URL' | + 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ' | + 'FST_ERR_ROUTE_DUPLICATED_HANDLER' | + 'FST_ERR_ROUTE_HANDLER_NOT_FN' | + 'FST_ERR_ROUTE_MISSING_HANDLER' | + 'FST_ERR_ROUTE_METHOD_INVALID' | + 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED' | + 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED' | + 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT' | + 'FST_ERR_ROUTE_REWRITE_NOT_STR' | + 'FST_ERR_REOPENED_CLOSE_SERVER' | + 'FST_ERR_REOPENED_SERVER' | + 'FST_ERR_INSTANCE_ALREADY_LISTENING' | + 'FST_ERR_PLUGIN_VERSION_MISMATCH' | + 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE' | + 'FST_ERR_PLUGIN_CALLBACK_NOT_FN' | + 'FST_ERR_PLUGIN_NOT_VALID' | + 'FST_ERR_ROOT_PLG_BOOTED' | + 'FST_ERR_PARENT_PLUGIN_BOOTED' | + 'FST_ERR_PLUGIN_TIMEOUT' + , FastifyErrorConstructor> diff --git a/types/request.d.ts b/types/request.d.ts index a6af0103278..f7a93deadf9 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,8 +1,8 @@ import { ErrorObject } from '@fastify/ajv-compiler' -import { FastifyRequestContext, FastifyContextConfig } from './context' +import { FastifyContextConfig, FastifyRequestContext } from './context' import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' -import { RouteGenericInterface, FastifyRouteConfig, RouteHandlerMethod } from './route' +import { FastifyRouteConfig, RouteGenericInterface, RouteHandlerMethod } from './route' import { FastifySchema } from './schema' import { FastifyRequestType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyRequestType } from './type-provider' import { ContextConfigDefault, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './utils' @@ -27,7 +27,6 @@ export interface RequestRouteOptions - // ^ Temporary Note: RequestType has been re-ordered to be the last argument in - // generic list. This generic argument is now considered optional as it can be - // automatically inferred from the SchemaCompiler, RouteGeneric and TypeProvider - // arguments. Implementations that already pass this argument can either omit - // the RequestType (preferred) or swap Logger and RequestType arguments when - // creating custom types of FastifyRequest. Related issue #4123 +// ^ Temporary Note: RequestType has been re-ordered to be the last argument in +// generic list. This generic argument is now considered optional as it can be +// automatically inferred from the SchemaCompiler, RouteGeneric and TypeProvider +// arguments. Implementations that already pass this argument can either omit +// the RequestType (preferred) or swap Logger and RequestType arguments when +// creating custom types of FastifyRequest. Related issue #4123 > { id: string; params: RequestType['params']; // deferred inference diff --git a/types/route.d.ts b/types/route.d.ts index f67f1e5bbcf..463185647b7 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -19,7 +19,7 @@ export interface FastifyRouteConfig { method: HTTPMethods | HTTPMethods[]; } -export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface {} +export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface { } export type RouteConstraintType = Omit, 'deriveConstraint'> & { deriveConstraint(req: RawRequestDefaultExpression, ctx?: Context, done?: (err: Error, ...args: any) => any): any, @@ -53,7 +53,6 @@ export interface RouteShorthandOptions< bodyLimit?: number; logLevel?: LogLevel; config?: FastifyContextConfig & ContextConfig; - version?: string; constraints?: RouteConstraint, prefixTrailingSlash?: 'slash' | 'no-slash' | 'both'; errorHandler?: ( From 2c2339b84623471161859afb8f00e217a1f51a64 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Sat, 3 Aug 2024 23:28:16 +0800 Subject: [PATCH 0728/1295] refactor: remove FSTDEP010 (#5611) --- docs/Reference/Warnings.md | 2 - lib/reply.js | 17 +------ lib/warnings.js | 7 --- test/internals/reply.test.js | 86 +++++------------------------------- test/skip-reply-send.test.js | 12 ++--- 5 files changed, 17 insertions(+), 107 deletions(-) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 61cf09142fe..800f68ebbad 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -8,7 +8,6 @@ - [FSTWRN001](#FSTWRN001) - [FSTWRN002](#FSTWRN002) - [Fastify Deprecation Codes](#fastify-deprecation-codes) - - [FSTDEP010](#FSTDEP010) - [FSTDEP012](#FSTDEP012) - [FSTDEP013](#FSTDEP013) - [FSTDEP015](#FSTDEP015) @@ -67,7 +66,6 @@ Deprecation codes are further supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | -| FSTDEP010 | Modifying the `reply.sent` property is deprecated. | Use the `reply.hijack()` method. | [#3140](https://github.com/fastify/fastify/pull/3140) | | FSTDEP012 | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) | | FSTDEP013 | Direct return of "trailers" function is deprecated. | Use "callback" or "async-await" for return value. | [#4380](https://github.com/fastify/fastify/pull/4380) | | FSTDEP015 | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) | diff --git a/lib/reply.js b/lib/reply.js index e524c1ef76b..0531700cdb3 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -47,7 +47,6 @@ const { FST_ERR_REP_INVALID_PAYLOAD_TYPE, FST_ERR_REP_RESPONSE_BODY_CONSUMED, FST_ERR_REP_ALREADY_SENT, - FST_ERR_REP_SENT_VALUE, FST_ERR_SEND_INSIDE_ONERR, FST_ERR_BAD_STATUS_CODE, FST_ERR_BAD_TRAILER_NAME, @@ -55,7 +54,7 @@ const { FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') -const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP021 } = require('./warnings') +const { FSTDEP013, FSTDEP019, FSTDEP021 } = require('./warnings') const toString = Object.prototype.toString @@ -106,20 +105,6 @@ Object.defineProperties(Reply.prototype, { get () { // We are checking whether reply was hijacked or the response has ended. return (this[kReplyHijacked] || this.raw.writableEnded) === true - }, - set (value) { - FSTDEP010() - - if (value !== true) { - throw new FST_ERR_REP_SENT_VALUE() - } - - // We throw only if sent was overwritten from Fastify - if (this.sent && this[kReplyHijacked]) { - throw new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) - } - - this[kReplyHijacked] = true } }, statusCode: { diff --git a/lib/warnings.js b/lib/warnings.js index d17f7a62132..f954b396b2e 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -4,7 +4,6 @@ const { createDeprecation, createWarning } = require('process-warning') /** * Deprecation codes: - * - FSTDEP010 * - FSTDEP012 * - FSTDEP013 * - FSTDEP015 @@ -17,11 +16,6 @@ const { createDeprecation, createWarning } = require('process-warning') * - FSTSEC001 */ -const FSTDEP010 = createDeprecation({ - code: 'FSTDEP010', - message: 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.' -}) - const FSTDEP012 = createDeprecation({ code: 'FSTDEP012', message: 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.' @@ -77,7 +71,6 @@ const FSTSEC001 = createWarning({ }) module.exports = { - FSTDEP010, FSTDEP012, FSTDEP013, FSTDEP015, diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 3996d8a7659..ff23f716621 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -19,7 +19,7 @@ const { } = require('../../lib/symbols') const fs = require('node:fs') const path = require('node:path') -const { FSTDEP010, FSTDEP019, FSTDEP021 } = require('../../lib/warnings') +const { FSTDEP019, FSTDEP021 } = require('../../lib/warnings') const agent = new http.Agent({ keepAlive: false }) @@ -1461,30 +1461,22 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t = }) }) -test('should emit deprecation warning when trying to modify the reply.sent property', t => { - t.plan(4) +test('should throw when trying to modify the reply.sent property', t => { + t.plan(3) const fastify = Fastify() - FSTDEP010.emitted = false - - process.removeAllListeners('warning') - process.on('warning', onWarning) - function onWarning (warning) { - t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, FSTDEP010.code) - } - - fastify.get('/', (req, reply) => { - reply.sent = true - - reply.raw.end() + fastify.get('/', function (req, reply) { + try { + reply.sent = true + } catch (err) { + t.ok(err) + reply.send() + } }) fastify.inject('/', (err, res) => { t.error(err) t.pass() - - process.removeListener('warning', onWarning) }) }) @@ -1513,64 +1505,6 @@ test('should emit deprecation warning when trying to use the reply.context.confi }) }) -test('should throw error when passing falsy value to reply.sent', t => { - t.plan(4) - const fastify = Fastify() - - fastify.get('/', function (req, reply) { - try { - reply.sent = false - } catch (err) { - t.equal(err.code, 'FST_ERR_REP_SENT_VALUE') - t.equal(err.message, 'The only possible value for reply.sent is true.') - reply.send() - } - }) - - fastify.inject('/', (err, res) => { - t.error(err) - t.pass() - }) -}) - -test('should throw error when attempting to set reply.sent more than once', t => { - t.plan(3) - const fastify = Fastify() - - fastify.get('/', function (req, reply) { - reply.sent = true - try { - reply.sent = true - t.fail('must throw') - } catch (err) { - t.equal(err.code, 'FST_ERR_REP_ALREADY_SENT') - } - reply.raw.end() - }) - - fastify.inject('/', (err, res) => { - t.error(err) - t.pass() - }) -}) - -test('should not throw error when attempting to set reply.sent if the underlining request was sent', t => { - t.plan(3) - const fastify = Fastify() - - fastify.get('/', function (req, reply) { - reply.raw.end() - t.doesNotThrow(() => { - reply.sent = true - }) - }) - - fastify.inject('/', (err, res) => { - t.error(err) - t.pass() - }) -}) - test('reply.elapsedTime should return 0 before the timer is initialised on the reply by setting up response listeners', t => { t.plan(1) const response = { statusCode: 200 } diff --git a/test/skip-reply-send.test.js b/test/skip-reply-send.test.js index 4de6cc2321f..bf1b6117897 100644 --- a/test/skip-reply-send.test.js +++ b/test/skip-reply-send.test.js @@ -19,7 +19,7 @@ const lifecycleHooks = [ 'onError' ] -test('skip automatic reply.send() with reply.sent = true and a body', (t) => { +test('skip automatic reply.send() with reply.hijack and a body', (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { @@ -33,7 +33,7 @@ test('skip automatic reply.send() with reply.sent = true and a body', (t) => { }) app.get('/', (req, reply) => { - reply.sent = true + reply.hijack() reply.raw.end('hello world') return Promise.resolve('this will be skipped') @@ -48,7 +48,7 @@ test('skip automatic reply.send() with reply.sent = true and a body', (t) => { }) }) -test('skip automatic reply.send() with reply.sent = true and no body', (t) => { +test('skip automatic reply.send() with reply.hijack and no body', (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { @@ -62,7 +62,7 @@ test('skip automatic reply.send() with reply.sent = true and no body', (t) => { }) app.get('/', (req, reply) => { - reply.sent = true + reply.hijack() reply.raw.end('hello world') return Promise.resolve() @@ -77,7 +77,7 @@ test('skip automatic reply.send() with reply.sent = true and no body', (t) => { }) }) -test('skip automatic reply.send() with reply.sent = true and an error', (t) => { +test('skip automatic reply.send() with reply.hijack and an error', (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { @@ -96,7 +96,7 @@ test('skip automatic reply.send() with reply.sent = true and an error', (t) => { }) app.get('/', (req, reply) => { - reply.sent = true + reply.hijack() reply.raw.end('hello world') return Promise.reject(new Error('kaboom')) From e4e9ca4cb06170784f5ca9e40a36b0e78271e6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 4 Aug 2024 19:30:03 +0300 Subject: [PATCH 0729/1295] refactor: remove FSTDEP021 (#5613) --- docs/Reference/Warnings.md | 2 -- lib/reply.js | 9 +-------- lib/warnings.js | 7 ------- test/internals/reply.test.js | 32 +------------------------------- test/types/reply.test-d.ts | 2 +- types/reply.d.ts | 4 ---- 6 files changed, 3 insertions(+), 53 deletions(-) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 800f68ebbad..57da261a235 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -15,7 +15,6 @@ - [FSTDEP017](#FSTDEP017) - [FSTDEP018](#FSTDEP018) - [FSTDEP019](#FSTDEP019) - - [FSTDEP021](#FSTDEP021) ## Warnings @@ -73,4 +72,3 @@ Deprecation codes are further supported by the Node.js CLI options: | FSTDEP017 | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) | | FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | | FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | -| FSTDEP021 | The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. | [#5483](https://github.com/fastify/fastify/pull/5483) | diff --git a/lib/reply.js b/lib/reply.js index 0531700cdb3..be4846cf1cc 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -54,7 +54,7 @@ const { FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') -const { FSTDEP013, FSTDEP019, FSTDEP021 } = require('./warnings') +const { FSTDEP013, FSTDEP019 } = require('./warnings') const toString = Object.prototype.toString @@ -448,13 +448,6 @@ Reply.prototype.type = function (type) { } Reply.prototype.redirect = function (url, code) { - if (typeof url === 'number') { - FSTDEP021() - const temp = code - code = url - url = temp - } - if (!code) { code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302 } diff --git a/lib/warnings.js b/lib/warnings.js index f954b396b2e..c48352b5257 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -11,7 +11,6 @@ const { createDeprecation, createWarning } = require('process-warning') * - FSTDEP017 * - FSTDEP018 * - FSTDEP019 - * - FSTDEP021 * - FSTWRN001 * - FSTSEC001 */ @@ -51,11 +50,6 @@ const FSTDEP019 = createDeprecation({ message: 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.' }) -const FSTDEP021 = createDeprecation({ - code: 'FSTDEP021', - message: 'The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`' -}) - const FSTWRN001 = createWarning({ name: 'FastifyWarning', code: 'FSTWRN001', @@ -78,7 +72,6 @@ module.exports = { FSTDEP017, FSTDEP018, FSTDEP019, - FSTDEP021, FSTWRN001, FSTSEC001 } diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index ff23f716621..99dd14167a8 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -19,7 +19,7 @@ const { } = require('../../lib/symbols') const fs = require('node:fs') const path = require('node:path') -const { FSTDEP019, FSTDEP021 } = require('../../lib/warnings') +const { FSTDEP019 } = require('../../lib/warnings') const agent = new http.Agent({ keepAlive: false }) @@ -1956,36 +1956,6 @@ test('redirect to an invalid URL should not crash the server', async t => { await fastify.close() }) -test('redirect with deprecated signature should warn', t => { - t.plan(4) - - process.removeAllListeners('warning') - process.on('warning', onWarning) - function onWarning (warning) { - t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, FSTDEP021.code) - } - - const fastify = Fastify() - - fastify.get('/', (req, reply) => { - reply.redirect(302, '/new') - }) - - fastify.get('/new', (req, reply) => { - reply.send('new') - }) - - fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.pass() - - process.removeListener('warning', onWarning) - }) - - FSTDEP021.emitted = false -}) - test('invalid response headers should not crash the server', async t => { const fastify = Fastify() fastify.route({ diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index fa491867a61..a6db2656007 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -30,7 +30,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectAssignable<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders) expectAssignable<(key: string) => FastifyReply>(reply.removeHeader) expectAssignable<(key: string) => boolean>(reply.hasHeader) - expectType<{ (statusCode: number, url: string): FastifyReply;(url: string, statusCode?: number): FastifyReply; }>(reply.redirect) + expectType<(url: string, statusCode?: number) => FastifyReply>(reply.redirect) expectType<() => FastifyReply>(reply.hijack) expectType<() => void>(reply.callNotFound) expectType<(contentType: string) => FastifyReply>(reply.type) diff --git a/types/reply.d.ts b/types/reply.d.ts index 0bfc12a6d88..1e1b3546ba6 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -57,10 +57,6 @@ export interface FastifyReply< getHeaders(): Record; removeHeader(key: HttpHeader): FastifyReply; hasHeader(key: HttpHeader): boolean; - /** - * @deprecated The `reply.redirect()` method has a new signature: `reply.reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. - */ - redirect(statusCode: number, url: string): FastifyReply; redirect(url: string, statusCode?: number): FastifyReply; writeEarlyHints(hints: Record, callback?: () => void): void; hijack(): FastifyReply; From 8b9e2df06c2ebdfc00d5b89528844bf994da03a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 4 Aug 2024 20:02:16 +0300 Subject: [PATCH 0730/1295] refactor: use `Object.hasOwn` (#5614) --- lib/decorate.js | 4 ++-- lib/route.js | 2 +- lib/server.js | 2 +- lib/validation.js | 8 ++++---- test/decorator.test.js | 2 +- test/internals/decorator.test.js | 4 ++-- test/internals/request-validate.test.js | 8 ++++---- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/decorate.js b/lib/decorate.js index 26a3b539d79..70ac94febef 100644 --- a/lib/decorate.js +++ b/lib/decorate.js @@ -16,7 +16,7 @@ const { } = require('./errors') function decorate (instance, name, fn, dependencies) { - if (Object.prototype.hasOwnProperty.call(instance, name)) { + if (Object.hasOwn(instance, name)) { throw new FST_ERR_DEC_ALREADY_PRESENT(name) } @@ -34,7 +34,7 @@ function decorate (instance, name, fn, dependencies) { function decorateConstructor (konstructor, name, fn, dependencies) { const instance = konstructor.prototype - if (Object.prototype.hasOwnProperty.call(instance, name) || hasKey(konstructor, name)) { + if (Object.hasOwn(instance, name) || hasKey(konstructor, name)) { throw new FST_ERR_DEC_ALREADY_PRESENT(name) } diff --git a/lib/route.js b/lib/route.js index 8fe8962f5a8..441f5050f57 100644 --- a/lib/route.js +++ b/lib/route.js @@ -87,7 +87,7 @@ function buildRouting (options) { disableRequestLogging = options.disableRequestLogging ignoreTrailingSlash = options.ignoreTrailingSlash ignoreDuplicateSlashes = options.ignoreDuplicateSlashes - return503OnClosing = Object.prototype.hasOwnProperty.call(options, 'return503OnClosing') ? options.return503OnClosing : true + return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true keepAliveConnections = fastifyArgs.keepAliveConnections }, routing: router.lookup.bind(router), // router func to find the right handler to call diff --git a/lib/server.js b/lib/server.js index 418ea37f6e1..dee3ced8c25 100644 --- a/lib/server.js +++ b/lib/server.js @@ -55,7 +55,7 @@ function createServer (options, httpHandler) { } else { host = listenOptions.host } - if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false || + if (!Object.hasOwn(listenOptions, 'host') || listenOptions.host == null) { listenOptions.host = host } diff --git a/lib/validation.js b/lib/validation.js index 90589468d0d..dd3f74e4852 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -82,7 +82,7 @@ function compileSchemasForValidation (context, compile, isCustom) { }) } context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' }) - } else if (Object.prototype.hasOwnProperty.call(schema, 'headers')) { + } else if (Object.hasOwn(schema, 'headers')) { FSTWRN001('headers', method, url) } @@ -98,19 +98,19 @@ function compileSchemasForValidation (context, compile, isCustom) { } else { context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) } - } else if (Object.prototype.hasOwnProperty.call(schema, 'body')) { + } else if (Object.hasOwn(schema, 'body')) { FSTWRN001('body', method, url) } if (schema.querystring) { context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' }) - } else if (Object.prototype.hasOwnProperty.call(schema, 'querystring')) { + } else if (Object.hasOwn(schema, 'querystring')) { FSTWRN001('querystring', method, url) } if (schema.params) { context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' }) - } else if (Object.prototype.hasOwnProperty.call(schema, 'params')) { + } else if (Object.hasOwn(schema, 'params')) { FSTWRN001('params', method, url) } } diff --git a/test/decorator.test.js b/test/decorator.test.js index f32ae1b4823..5e4006fba95 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -639,7 +639,7 @@ test('should register empty values', t => { fastify.register((instance, opts, done) => { instance.decorate('test', null) - t.ok(Object.prototype.hasOwnProperty.call(instance, 'test')) + t.ok(Object.hasOwn(instance, 'test')) done() }) diff --git a/test/internals/decorator.test.js b/test/internals/decorator.test.js index 8a6cd1433d7..95585b6ed80 100644 --- a/test/internals/decorator.test.js +++ b/test/internals/decorator.test.js @@ -136,7 +136,7 @@ test('decorate should recognize getter/setter objects', t => { this._a = val } }) - t.equal(Object.prototype.hasOwnProperty.call(one, 'foo'), true) + t.equal(Object.hasOwn(one, 'foo'), true) t.equal(one.foo, undefined) one.foo = 'a' t.equal(one.foo, 'a') @@ -152,6 +152,6 @@ test('decorate should recognize getter/setter objects', t => { decorator.add.call(two, 'foo', { getter: () => 'a getter' }) - t.equal(Object.prototype.hasOwnProperty.call(two, 'foo'), true) + t.equal(Object.hasOwn(two, 'foo'), true) t.equal(two.foo, 'a getter') }) diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js index 0783bd2c6b8..200f5216c39 100644 --- a/test/internals/request-validate.test.js +++ b/test/internals/request-validate.test.js @@ -77,7 +77,7 @@ test('#compileValidationSchema', subtest => { const validate = req.compileValidationSchema(defaultSchema) t.ok(validate({ hello: 'world' })) - t.ok(Object.prototype.hasOwnProperty.call(validate, 'errors')) + t.ok(Object.hasOwn(validate, 'errors')) t.equal(validate.errors, null) reply.send({ hello: 'world' }) @@ -98,7 +98,7 @@ test('#compileValidationSchema', subtest => { const validate = req.compileValidationSchema(defaultSchema) t.notOk(validate({ world: 'foo' })) - t.ok(Object.prototype.hasOwnProperty.call(validate, 'errors')) + t.ok(Object.hasOwn(validate, 'errors')) t.ok(Array.isArray(validate.errors)) t.ok(validate.errors.length > 0) @@ -290,7 +290,7 @@ test('#getValidationFunction', subtest => { const validate = req.getValidationFunction(defaultSchema) t.ok(validate({ hello: 'world' })) - t.ok(Object.prototype.hasOwnProperty.call(validate, 'errors')) + t.ok(Object.hasOwn(validate, 'errors')) t.equal(validate.errors, null) reply.send({ hello: 'world' }) @@ -312,7 +312,7 @@ test('#getValidationFunction', subtest => { const validate = req.getValidationFunction(defaultSchema) t.notOk(validate({ world: 'foo' })) - t.ok(Object.prototype.hasOwnProperty.call(validate, 'errors')) + t.ok(Object.hasOwn(validate, 'errors')) t.ok(Array.isArray(validate.errors)) t.ok(validate.errors.length > 0) From 11c8d83fe67e24ee8ee67a4e75e46523f632b040 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:12:55 +0800 Subject: [PATCH 0731/1295] refactor: remove `FSTDEP012`, `FSTDEP015`, `FSTDEP016`, `FSTDEP017`, `FSTDEP018`, `FSTDEP019` (#5616) --- docs/Reference/Logging.md | 2 +- docs/Reference/Reply.md | 9 - docs/Reference/Request.md | 9 - docs/Reference/Routes.md | 2 +- docs/Reference/Warnings.md | 12 -- lib/context.js | 23 +-- lib/reply.js | 37 ++-- lib/request.js | 38 ---- lib/symbols.js | 1 - lib/warnings.js | 43 ----- test/internals/reply.test.js | 82 +++------ test/internals/request.test.js | 15 +- test/request.deprecated.test.js | 41 ----- test/route.8.test.js | 8 +- test/router-options.test.js | 4 +- test/types/hooks.test-d.ts | 112 +++++------ test/types/instance.test-d.ts | 2 +- test/types/reply.test-d.ts | 7 +- test/types/request.test-d.ts | 14 +- test/types/route.test-d.ts | 316 ++++++++++++++++---------------- types/reply.d.ts | 8 +- types/request.d.ts | 7 +- 22 files changed, 277 insertions(+), 515 deletions(-) delete mode 100644 test/request.deprecated.test.js diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 10360c2c0e4..f99c2adc27e 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -140,7 +140,7 @@ const fastify = require('fastify')({ return { method: request.method, url: request.url, - path: request.routerPath, + path: request.routeOptions.url, parameters: request.params, // Including the headers in the log could be in violation // of privacy laws, e.g. GDPR. You should use the "redact" option to diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index a906e6666cb..889488a6a9d 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -92,7 +92,6 @@ since the request was received by Fastify. from Node core. - `.log` - The logger instance of the incoming request. - `.request` - The incoming request. -- `.context` - Deprecated, access the [Request's context](./Request.md) property. ```js fastify.get('/', options, function (request, reply) { @@ -104,14 +103,6 @@ fastify.get('/', options, function (request, reply) { }) ``` -Additionally, `Reply` provides access to the context of the request: - -```js -fastify.get('/', {config: {foo: 'bar'}}, function (request, reply) { - reply.send('handler config.foo = ' + reply.context.config.foo) -}) -``` - ### .code(statusCode) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 6dcee76e673..5e3a8a36e87 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -31,21 +31,12 @@ Request is a core Fastify object containing the following fields: - `url` - the URL of the incoming request - `originalUrl` - similar to `url`, this allows you to access the original `url` in case of internal re-routing -- `routerMethod` - Deprecated, use `request.routeOptions.method` instead. The - method defined for the router that is handling the request -- `routerPath` - Deprecated, use `request.routeOptions.url` instead. The - path pattern defined for the router that is handling the request - `is404` - true if request is being handled by 404 handler, false if it is not - `socket` - the underlying connection of the incoming request - `context` - Deprecated, use `request.routeOptions.config` instead. A Fastify internal object. You should not use it directly or modify it. It is useful to access one special key: - `context.config` - The route [`config`](./Routes.md#routes-config) object. -- `routeSchema` - Deprecated, use `request.routeOptions.schema` instead. The - scheme definition set for the router that is handling the request -- `routeConfig` - Deprecated, use `request.routeOptions.config` instead. The - route [`config`](./Routes.md#routes-config) - object. - `routeOptions` - The route [`option`](./Routes.md#routes-options) object - `bodyLimit` - either server limit or route limit - `config` - the [`config`](./Routes.md#routes-config) object for this route diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 9fcc2105963..2f78c3f5bf5 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -614,7 +614,7 @@ retrieve it in the handler. const fastify = require('fastify')() function handler (req, reply) { - reply.send(reply.context.config.output) + reply.send(reply.routeOptions.config.output) } fastify.get('/en', { config: { output: 'hello world!' } }, handler) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 57da261a235..7a8a5ff8b16 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -8,13 +8,7 @@ - [FSTWRN001](#FSTWRN001) - [FSTWRN002](#FSTWRN002) - [Fastify Deprecation Codes](#fastify-deprecation-codes) - - [FSTDEP012](#FSTDEP012) - [FSTDEP013](#FSTDEP013) - - [FSTDEP015](#FSTDEP015) - - [FSTDEP016](#FSTDEP016) - - [FSTDEP017](#FSTDEP017) - - [FSTDEP018](#FSTDEP018) - - [FSTDEP019](#FSTDEP019) ## Warnings @@ -65,10 +59,4 @@ Deprecation codes are further supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | -| FSTDEP012 | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) | | FSTDEP013 | Direct return of "trailers" function is deprecated. | Use "callback" or "async-await" for return value. | [#4380](https://github.com/fastify/fastify/pull/4380) | -| FSTDEP015 | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) | -| FSTDEP016 | You are accessing the deprecated `request.routeConfig` property. | Use `request.routeOptions.config`. | [#4470](https://github.com/fastify/fastify/pull/4470) | -| FSTDEP017 | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) | -| FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | -| FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | diff --git a/lib/context.js b/lib/context.js index e1584711966..9d86908a480 100644 --- a/lib/context.js +++ b/lib/context.js @@ -14,8 +14,7 @@ const { kContentTypeParser, kRouteByFastify, kRequestCacheValidateFns, - kReplyCacheSerializeFns, - kPublicRouteContext + kReplyCacheSerializeFns } = require('./symbols.js') // Object that holds the context of every request @@ -79,29 +78,9 @@ function Context ({ this.validatorCompiler = validatorCompiler || null this.serializerCompiler = serializerCompiler || null - // Route + Userland configurations for the route - this[kPublicRouteContext] = getPublicRouteContext(this) - this.server = server } -function getPublicRouteContext (context) { - return Object.create(null, { - schema: { - enumerable: true, - get () { - return context.schema - } - }, - config: { - enumerable: true, - get () { - return context.config - } - } - }) -} - function defaultSchemaErrorFormatter (errors, dataVar) { let text = '' const separator = ', ' diff --git a/lib/reply.js b/lib/reply.js index be4846cf1cc..90d0134714f 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -5,7 +5,6 @@ const Readable = require('node:stream').Readable const { kFourOhFourContext, - kPublicRouteContext, kReplyErrorHandlerCalled, kReplyHijacked, kReplyStartTime, @@ -54,7 +53,7 @@ const { FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') -const { FSTDEP013, FSTDEP019 } = require('./warnings') +const { FSTDEP013 } = require('./warnings') const toString = Object.prototype.toString @@ -79,14 +78,6 @@ Object.defineProperties(Reply.prototype, { return this.request[kRouteContext] } }, - // TODO: remove once v5 is done - // Is temporary to avoid constant conflicts between `next` and `main` - context: { - get () { - FSTDEP019() - return this.request[kRouteContext] - } - }, elapsedTime: { get () { if (this[kReplyStartTime] === undefined) { @@ -115,9 +106,9 @@ Object.defineProperties(Reply.prototype, { this.code(value) } }, - [kPublicRouteContext]: { + routeOptions: { get () { - return this.request[kPublicRouteContext] + return this.request.routeOptions } } }) @@ -193,7 +184,7 @@ Reply.prototype.send = function (payload) { payload = this[kReplySerializer](payload) } - // The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json' + // The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json' } else if (hasContentType === false || contentType.indexOf('json') > -1) { if (hasContentType === false) { this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON @@ -361,13 +352,13 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null } const serializerCompiler = this[kRouteContext].serializerCompiler || - this.server[kSchemaController].serializerCompiler || - ( - // We compile the schemas if no custom serializerCompiler is provided - // nor set - this.server[kSchemaController].setupSerializer(this.server[kOptions]) || - this.server[kSchemaController].serializerCompiler - ) + this.server[kSchemaController].serializerCompiler || + ( + // We compile the schemas if no custom serializerCompiler is provided + // nor set + this.server[kSchemaController].setupSerializer(this.server[kOptions]) || + this.server[kSchemaController].serializerCompiler + ) const serializeFn = serializerCompiler({ schema, @@ -659,9 +650,9 @@ function onSendEnd (reply, payload) { if (reply[kReplyTrailers] === null) { const contentLength = reply[kReplyHeaders]['content-length'] if (!contentLength || - (req.raw.method !== 'HEAD' && - Number(contentLength) !== Buffer.byteLength(payload) - ) + (req.raw.method !== 'HEAD' && + Number(contentLength) !== Buffer.byteLength(payload) + ) ) { reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload) } diff --git a/lib/request.js b/lib/request.js index 5ac9309ad65..b37fe89c22f 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,13 +1,6 @@ 'use strict' const proxyAddr = require('proxy-addr') -const { - FSTDEP012, - FSTDEP015, - FSTDEP016, - FSTDEP017, - FSTDEP018 -} = require('./warnings') const { kHasBeenDecorated, kSchemaBody, @@ -18,7 +11,6 @@ const { kOptions, kRequestCacheValidateFns, kRouteContext, - kPublicRouteContext, kRequestOriginalUrl } = require('./symbols') const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors') @@ -170,18 +162,6 @@ Object.defineProperties(Request.prototype, { return this.raw.method } }, - context: { - get () { - FSTDEP012() - return this[kRouteContext] - } - }, - routerPath: { - get () { - FSTDEP017() - return this[kRouteContext].config?.url - } - }, routeOptions: { get () { const context = this[kRouteContext] @@ -212,24 +192,6 @@ Object.defineProperties(Request.prototype, { return Object.freeze(options) } }, - routerMethod: { - get () { - FSTDEP018() - return this[kRouteContext].config?.method - } - }, - routeConfig: { - get () { - FSTDEP016() - return this[kRouteContext][kPublicRouteContext]?.config - } - }, - routeSchema: { - get () { - FSTDEP015() - return this[kRouteContext][kPublicRouteContext].schema - } - }, is404: { get () { return this[kRouteContext].config?.url === undefined diff --git a/lib/symbols.js b/lib/symbols.js index 58992c3a878..53c44f37328 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -16,7 +16,6 @@ const keys = { kDisableRequestLogging: Symbol('fastify.disableRequestLogging'), kPluginNameChain: Symbol('fastify.pluginNameChain'), kRouteContext: Symbol('fastify.context'), - kPublicRouteContext: Symbol('fastify.routeOptions'), kGenReqId: Symbol('fastify.genReqId'), // Schema kSchemaController: Symbol('fastify.schemaController'), diff --git a/lib/warnings.js b/lib/warnings.js index c48352b5257..c7ba5a64d78 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -4,52 +4,15 @@ const { createDeprecation, createWarning } = require('process-warning') /** * Deprecation codes: - * - FSTDEP012 * - FSTDEP013 - * - FSTDEP015 - * - FSTDEP016 - * - FSTDEP017 - * - FSTDEP018 - * - FSTDEP019 * - FSTWRN001 * - FSTSEC001 */ -const FSTDEP012 = createDeprecation({ - code: 'FSTDEP012', - message: 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.' -}) - const FSTDEP013 = createDeprecation({ code: 'FSTDEP013', message: '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`.' }) - -const FSTDEP015 = createDeprecation({ - code: 'FSTDEP015', - message: 'You are accessing the deprecated "request.routeSchema" property. Use "request.routeOptions.schema" instead. Property "req.routeSchema" will be removed in `fastify@5`.' -}) - -const FSTDEP016 = createDeprecation({ - code: 'FSTDEP016', - message: 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.' -}) - -const FSTDEP017 = createDeprecation({ - code: 'FSTDEP017', - message: 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.url" instead. Property "req.routerPath" will be removed in `fastify@5`.' -}) - -const FSTDEP018 = createDeprecation({ - code: 'FSTDEP018', - message: 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.' -}) - -const FSTDEP019 = createDeprecation({ - code: 'FSTDEP019', - message: 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.' -}) - const FSTWRN001 = createWarning({ name: 'FastifyWarning', code: 'FSTWRN001', @@ -65,13 +28,7 @@ const FSTSEC001 = createWarning({ }) module.exports = { - FSTDEP012, FSTDEP013, - FSTDEP015, - FSTDEP016, - FSTDEP017, - FSTDEP018, - FSTDEP019, FSTWRN001, FSTSEC001 } diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 99dd14167a8..8f78b11ae01 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -5,6 +5,7 @@ const test = t.test const sget = require('simple-get').concat const http = require('node:http') const NotFound = require('http-errors').NotFound +const Request = require('../../lib/request') const Reply = require('../../lib/reply') const Fastify = require('../..') const { Readable, Writable } = require('node:stream') @@ -14,12 +15,10 @@ const { kReplySerializer, kReplyIsError, kReplySerializerDefault, - kRouteContext, - kPublicRouteContext + kRouteContext } = require('../../lib/symbols') const fs = require('node:fs') const path = require('node:path') -const { FSTDEP019 } = require('../../lib/warnings') const agent = new http.Agent({ keepAlive: false }) @@ -38,8 +37,8 @@ const doGet = function (url) { test('Once called, Reply should return an object with methods', t => { t.plan(15) const response = { res: 'res' } - const context = { config: { onSend: [] }, schema: {} } - const request = { [kRouteContext]: context, [kPublicRouteContext]: { config: context.config, schema: context.schema } } + const context = { config: { onSend: [] }, schema: {}, _parserOptions: {}, server: { hasConstraintStrategy: () => false, initialConfig: {} } } + const request = new Request(null, null, null, null, null, context) const reply = new Reply(response, request) t.equal(typeof reply, 'object') t.equal(typeof reply[kReplyIsError], 'boolean') @@ -52,8 +51,8 @@ test('Once called, Reply should return an object with methods', t => { t.equal(typeof reply[kReplyHeaders], 'object') t.same(reply.raw, response) t.equal(reply[kRouteContext], context) - t.equal(reply[kPublicRouteContext].config, context.config) - t.equal(reply[kPublicRouteContext].schema, context.schema) + t.equal(reply.routeOptions.config, context.config) + t.equal(reply.routeOptions.schema, context.schema) t.equal(reply.request, request) // Aim to not bad property keys (including Symbols) t.notOk('undefined' in reply) @@ -63,7 +62,7 @@ test('reply.send will logStream error and destroy the stream', t => { t.plan(1) let destroyCalled const payload = new Readable({ - read () {}, + read () { }, destroy (err, cb) { destroyCalled = true cb(err) @@ -72,16 +71,16 @@ test('reply.send will logStream error and destroy the stream', t => { const response = new Writable() Object.assign(response, { - setHeader: () => {}, + setHeader: () => { }, hasHeader: () => false, getHeader: () => undefined, - writeHead: () => {}, - write: () => {}, + writeHead: () => { }, + write: () => { }, headersSent: true }) const log = { - warn: () => {} + warn: () => { } } const reply = new Reply(response, { [kRouteContext]: { onSend: null } }, log) @@ -94,12 +93,12 @@ test('reply.send will logStream error and destroy the stream', t => { test('reply.send throw with circular JSON', t => { t.plan(1) const response = { - setHeader: () => {}, + setHeader: () => { }, hasHeader: () => false, getHeader: () => undefined, - writeHead: () => {}, - write: () => {}, - end: () => {} + writeHead: () => { }, + write: () => { }, + end: () => { } } const reply = new Reply(response, { [kRouteContext]: { onSend: [] } }) t.throws(() => { @@ -112,12 +111,12 @@ test('reply.send throw with circular JSON', t => { test('reply.send returns itself', t => { t.plan(1) const response = { - setHeader: () => {}, + setHeader: () => { }, hasHeader: () => false, getHeader: () => undefined, - writeHead: () => {}, - write: () => {}, - end: () => {} + writeHead: () => { }, + write: () => { }, + end: () => { } } const reply = new Reply(response, { [kRouteContext]: { onSend: [] } }) t.equal(reply.send('hello'), reply) @@ -1119,7 +1118,7 @@ test('reply.hasHeader returns correct values', t => { sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => {}) + }, () => { }) }) }) @@ -1151,18 +1150,18 @@ test('reply.getHeader returns correct values', t => { sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => {}) + }, () => { }) }) }) test('reply.getHeader returns raw header if there is not in the reply headers', t => { t.plan(1) const response = { - setHeader: () => {}, + setHeader: () => { }, hasHeader: () => true, getHeader: () => 'bar', - writeHead: () => {}, - end: () => {} + writeHead: () => { }, + end: () => { } } const reply = new Reply(response, { onSend: [] }, null) t.equal(reply.getHeader('foo'), 'bar') @@ -1480,31 +1479,6 @@ test('should throw when trying to modify the reply.sent property', t => { }) }) -test('should emit deprecation warning when trying to use the reply.context.config property', t => { - t.plan(4) - const fastify = Fastify() - - FSTDEP019.emitted = false - - process.removeAllListeners('warning') - process.on('warning', onWarning) - function onWarning (warning) { - t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, FSTDEP019.code) - } - - fastify.get('/', (req, reply) => { - req.log(reply.context.config) - }) - - fastify.inject('/', (err, res) => { - t.error(err) - t.pass() - - process.removeListener('warning', onWarning) - }) -}) - test('reply.elapsedTime should return 0 before the timer is initialised on the reply by setting up response listeners', t => { t.plan(1) const response = { statusCode: 200 } @@ -1777,7 +1751,7 @@ test('cannot set the replySerializer when the server is running', t => { fastify.listen({ port: 0 }, err => { t.error(err) try { - fastify.setReplySerializer(() => {}) + fastify.setReplySerializer(() => { }) t.fail('this serializer should not be setup') } catch (e) { t.equal(e.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') @@ -1827,7 +1801,7 @@ test('reply should not call the custom serializer for errors and not found', t = test('reply.then', t => { t.plan(4) - function request () {} + function request () { } t.test('without an error', t => { t.plan(1) @@ -2057,7 +2031,7 @@ test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', const response = new Writable() Object.assign(response, { - setHeader: () => {}, + setHeader: () => { }, hasHeader: () => false, getHeader: () => undefined, writeHead: () => { @@ -2065,7 +2039,7 @@ test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', err.code = 'ERR_HTTP_HEADERS_SENT' throw err }, - write: () => {}, + write: () => { }, headersSent: true }) diff --git a/test/internals/request.test.js b/test/internals/request.test.js index acd1e0b2372..5ce8d3513c0 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -5,7 +5,6 @@ const { test } = require('tap') const Request = require('../../lib/request') const Context = require('../../lib/context') const { - kPublicRouteContext, kReply, kRequest, kOptions @@ -67,10 +66,6 @@ test('Regular request', t => { t.equal(request.originalUrl, '/') t.equal(request.socket, req.socket) t.equal(request.protocol, 'http') - t.equal(request.routerPath, context.config.url) - t.equal(request.routerMethod, context.config.method) - t.equal(request.routeConfig, context[kPublicRouteContext].config) - t.equal(request.routeSchema, context[kPublicRouteContext].schema) // Aim to not bad property keys (including Symbols) t.notOk('undefined' in request) @@ -126,10 +121,6 @@ test('Request with undefined config', t => { t.equal(request.originalUrl, '/') t.equal(request.socket, req.socket) t.equal(request.protocol, 'http') - t.equal(request.routeSchema, context[kPublicRouteContext].schema) - t.equal(request.routerPath, undefined) - t.equal(request.routerMethod, undefined) - t.equal(request.routeConfig, undefined) // Aim to not bad property keys (including Symbols) t.notOk('undefined' in request) @@ -174,7 +165,7 @@ test('Regular request - host header has precedence over authority', t => { }) test('Request with trust proxy', t => { - t.plan(22) + t.plan(18) const headers = { 'x-forwarded-for': '2.2.2.2, 1.1.1.1', 'x-forwarded-host': 'example.com' @@ -229,10 +220,6 @@ test('Request with trust proxy', t => { t.type(request.validateInput, Function) t.type(request.getValidationFunction, Function) t.type(request.compileValidationSchema, Function) - t.equal(request.routerPath, context.config.url) - t.equal(request.routerMethod, context.config.method) - t.equal(request.routeConfig, context[kPublicRouteContext].config) - t.equal(request.routeSchema, context[kPublicRouteContext].schema) }) test('Request with trust proxy, encrypted', t => { diff --git a/test/request.deprecated.test.js b/test/request.deprecated.test.js deleted file mode 100644 index 0c0a9d81c05..00000000000 --- a/test/request.deprecated.test.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict' - -// Tests for some deprecated `request.*` keys. This file should be -// removed when the deprecation is complete. - -process.removeAllListeners('warning') - -const test = require('tap').test -const Fastify = require('../') - -test('Should expose router options via getters on request and reply', t => { - t.plan(7) - const fastify = Fastify() - const expectedSchema = { - params: { - type: 'object', - properties: { - id: { type: 'integer' } - } - } - } - - fastify.get('/test/:id', { - schema: expectedSchema - }, (req, reply) => { - t.equal(req.routeConfig.url, '/test/:id') - t.equal(req.routeConfig.method, 'GET') - t.same(req.routeSchema, expectedSchema) - t.equal(req.routerPath, '/test/:id') - t.equal(req.routerMethod, 'GET') - reply.send() - }) - - fastify.inject({ - method: 'GET', - url: '/test/123456789' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - }) -}) diff --git a/test/route.8.test.js b/test/route.8.test.js index 4ae6fd1419a..eb1e0f924fe 100644 --- a/test/route.8.test.js +++ b/test/route.8.test.js @@ -7,7 +7,7 @@ const Fastify = require('../fastify') const { FST_ERR_INVALID_URL } = require('../lib/errors') const { getServerUrl } = require('./helper') -test('Request and Reply share the route config', async t => { +test('Request and Reply share the route options', async t => { t.plan(3) const fastify = Fastify() @@ -22,9 +22,9 @@ test('Request and Reply share the route config', async t => { url: '/', config, handler: (req, reply) => { - t.same(req.context, reply.context) - t.same(req.context.config, reply.context.config) - t.match(req.context.config, config, 'there are url and method additional properties') + t.same(req.routeOptions, reply.routeOptions) + t.same(req.routeOptions.config, reply.routeOptions.config) + t.match(req.routeOptions.config, config, 'there are url and method additional properties') reply.send({ hello: 'world' }) } diff --git a/test/router-options.test.js b/test/router-options.test.js index 8ebd405aa15..fd48fbfe78e 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -106,8 +106,8 @@ test('Should expose router options via getters on request and reply', t => { fastify.get('/test/:id', { schema: expectedSchema }, (req, reply) => { - t.equal(reply.context.config.url, '/test/:id') - t.equal(reply.context.config.method, 'GET') + t.equal(reply.routeOptions.config.url, '/test/:id') + t.equal(reply.routeOptions.config.method, 'GET') t.same(req.routeOptions.schema, expectedSchema) t.equal(typeof req.routeOptions.handler, 'function') t.equal(req.routeOptions.config.url, '/test/:id') diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 1d2964e48e8..63ced1938ea 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -273,83 +273,83 @@ server.route({ url: '/', handler: () => { }, onRequest: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preParsing: (request, reply, payload, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preValidation: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preHandler: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preSerialization: (request, reply, payload, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onSend: (request, reply, payload, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onResponse: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onTimeout: (request, reply, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onError: (request, reply, error, done) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) } }) server.get('/', { onRequest: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preParsing: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preValidation: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preHandler: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preSerialization: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onSend: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onResponse: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onTimeout: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onError: async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) } }, async (request, reply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }) type CustomContextRequest = FastifyRequest @@ -359,40 +359,40 @@ server.route({ url: '/', handler: () => { }, onRequest: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preParsing: async (request: CustomContextRequest, reply: CustomContextReply, payload: RequestPayload) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preValidation: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preHandler: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, preSerialization: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onSend: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onResponse: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onTimeout: async (request: CustomContextRequest, reply: CustomContextReply) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) }, onError: async (request: CustomContextRequest, reply: CustomContextReply, error: FastifyError) => { - expectType(request.context.config) - expectType(reply.context.config) + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) } }) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 527876b1674..f41f92bb952 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -250,7 +250,7 @@ expectType(fastify().get { expectAssignable(error) expectAssignable(request) - expectAssignable<{ contextKey: string }>(request.routeConfig) + expectAssignable<{ contextKey: string }>(request.routeOptions.config) expectAssignable(reply) expectAssignable(server.errorHandler(error, request, reply)) } diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index a6db2656007..9a96d1bd10e 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,10 +1,10 @@ import { Buffer } from 'buffer' import { expectAssignable, expectError, expectType } from 'tsd' -import fastify, { FastifyReplyContext, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' +import fastify, { FastifyContextConfig, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' import { FastifyInstance } from '../../types/instance' import { FastifyLoggerInstance } from '../../types/logger' import { ResolveReplyTypeWithRouteGeneric } from '../../types/reply' -import { RouteGenericInterface } from '../../types/route' +import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' import { ContextConfigDefault, RawReplyDefaultExpression, RawServerDefault } from '../../types/utils' type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string @@ -12,8 +12,6 @@ type DefaultFastifyReplyWithCode = FastifyReply(reply.raw) - expectType>(reply.context) - expectType['config']>(reply.context.config) expectType(reply.log) expectType>(reply.request) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) @@ -46,6 +44,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectAssignable<((schema: { [key: string]: unknown }, httpStatus?: string) => DefaultSerializationFunction)>(reply.compileSerializationSchema) expectAssignable<((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown)>(reply.serializeInput) expectAssignable<((input: { [key: string]: unknown }, httpStatus: string) => unknown)>(reply.serializeInput) + expectType(reply.routeOptions.config) } interface ReplyPayload { diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 09f96a3cc35..720ca60d021 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -1,7 +1,6 @@ import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { ContextConfigDefault, - FastifyRequestContext, FastifyContextConfig, FastifyLogFn, FastifySchema, @@ -65,8 +64,6 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.url) expectType(request.originalUrl) expectType(request.method) - expectType(request.routerPath) - expectType(request.routerMethod) expectType>(request.routeOptions) expectType(request.is404) expectType(request.hostname) @@ -77,12 +74,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.raw) expectType(request.body) expectType(request.params) - expectType>(request.context) - expectType['config']>(request.context.config) - expectType['config']>(request.routeConfig) - expectType['config']>(request.routeOptions.config) expectType(request.routeOptions.config) - expectType(request.routeSchema) expectType(request.routeOptions.schema) expectType(request.routeOptions.handler) expectType(request.routeOptions.url) @@ -116,8 +108,7 @@ const postHandler: Handler = function (request) { expectType(request.params.id) expectType(request.headers['x-foobar']) expectType(request.server) - expectType>(request.context) - expectType['config']>(request.context.config) + expectType(request.routeOptions.config) } function putHandler (request: CustomRequest, reply: FastifyReply) { @@ -134,8 +125,7 @@ function putHandler (request: CustomRequest, reply: FastifyReply) { expectType(request.params.id) expectType(request.headers['x-foobar']) expectType(request.server) - expectType>(request.context) - expectType['config']>(request.context.config) + expectType(request.routeOptions.config) } const server = fastify() diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index c7d10d9db4d..8dcf7ea11a3 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -61,10 +61,10 @@ fastify().get( ) type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' | -'options' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' | -'unlock' | 'trace' | 'search' | 'mkcalendar' | 'report' + 'options' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' | + 'unlock' | 'trace' | 'search' | 'mkcalendar' | 'report' -;['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'PROPFIND', + ;['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', 'TRACE', 'SEARCH', 'MKCALENDAR', 'REPORT' ].forEach(method => { // route method @@ -111,16 +111,16 @@ type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.extra) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(res.context.config.extra) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.extra) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(res.routeOptions.config.extra) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }) fastify().route({ @@ -133,28 +133,28 @@ type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, preParsing: (req, res, payload, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) expectType(payload) expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done) expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) @@ -164,42 +164,42 @@ type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, preHandler: (req, res, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, onResponse: (req, res, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) expectType(res.statusCode) }, onError: (req, res, error, done) => { @@ -207,56 +207,56 @@ type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, preSerialization: (req, res, payload, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, onSend: (req, res, payload, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, handler: (req, res) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) } }) @@ -270,28 +270,28 @@ type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, preParsing: async (req, res, payload, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) expectType(payload) expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done) expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) @@ -301,42 +301,42 @@ type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, preHandler: async (req, res, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, onResponse: async (req, res, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) expectType(res.statusCode) }, onError: async (req, res, error, done) => { @@ -344,56 +344,56 @@ type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, preSerialization: async (req, res, payload, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, onSend: async (req, res, payload, done) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) }, handler: (req, res) => { expectType(req.body) expectType(req.query) expectType(req.params) expectType(req.headers) - expectType(req.context.config.foo) - expectType(req.context.config.bar) - expectType(req.context.config.url) - expectType(req.context.config.method) - expectType(res.context.config.foo) - expectType(res.context.config.bar) - expectType(req.routeConfig.url) - expectType(req.routeConfig.method) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) } }) }) @@ -477,7 +477,7 @@ expectType(fastify().hasRoute({ number: 12, date: new Date(), boolean: true, - function: () => {}, + function: () => { }, object: { foo: 'bar' } } })) diff --git a/types/reply.d.ts b/types/reply.d.ts index 1e1b3546ba6..75edf70ca6c 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -1,12 +1,11 @@ import { Buffer } from 'buffer' -import { FastifyReplyContext } from './context' import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' -import { FastifyRequest } from './request' +import { FastifyRequest, RequestRouteOptions } from './request' import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' import { CallSerializerTypeProvider, FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider' -import { CodeToReplyKey, ContextConfigDefault, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes, HttpHeader } from './utils' +import { CodeToReplyKey, ContextConfigDefault, HttpHeader, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils' export interface ReplyGenericInterface { Reply?: ReplyDefault; @@ -40,8 +39,9 @@ export interface FastifyReply< TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ReplyType extends FastifyReplyType = ResolveFastifyReplyType > { + readonly routeOptions: Readonly> + raw: RawReply; - context: FastifyReplyContext; elapsedTime: number; log: FastifyBaseLogger; request: FastifyRequest; diff --git a/types/request.d.ts b/types/request.d.ts index f7a93deadf9..63e3122a440 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,5 +1,5 @@ import { ErrorObject } from '@fastify/ajv-compiler' -import { FastifyContextConfig, FastifyRequestContext } from './context' +import { FastifyContextConfig } from './context' import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' import { FastifyRouteConfig, RouteGenericInterface, RouteHandlerMethod } from './route' @@ -61,9 +61,6 @@ export interface FastifyRequest; - routeConfig: FastifyRequestContext['config']; - routeSchema?: FastifySchema; // it is empty for 404 requests /** in order for this to be used the user should ensure they have set the attachValidation option. */ validationError?: Error & { validation: any; validationContext: string }; @@ -81,8 +78,6 @@ export interface FastifyRequest> readonly is404: boolean; readonly socket: RawRequest['socket']; From d9c41f9c2652080eb227faac762f310a14664e57 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:49:21 +0800 Subject: [PATCH 0732/1295] refactor: remove `FSTDEP013` (#5618) --- docs/Reference/Warnings.md | 2 -- lib/reply.js | 5 ----- lib/warnings.js | 8 +------- test/reply-trailers.test.js | 33 +-------------------------------- 4 files changed, 2 insertions(+), 46 deletions(-) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 7a8a5ff8b16..7358454ce31 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -8,7 +8,6 @@ - [FSTWRN001](#FSTWRN001) - [FSTWRN002](#FSTWRN002) - [Fastify Deprecation Codes](#fastify-deprecation-codes) - - [FSTDEP013](#FSTDEP013) ## Warnings @@ -59,4 +58,3 @@ Deprecation codes are further supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | -| FSTDEP013 | Direct return of "trailers" function is deprecated. | Use "callback" or "async-await" for return value. | [#4380](https://github.com/fastify/fastify/pull/4380) | diff --git a/lib/reply.js b/lib/reply.js index 90d0134714f..8e916bb5510 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -53,7 +53,6 @@ const { FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN } = require('./errors') -const { FSTDEP013 } = require('./warnings') const toString = Object.prototype.toString @@ -781,10 +780,6 @@ function sendTrailer (payload, res, reply) { const result = reply[kReplyTrailers][trailerName](reply, payload, cb) if (typeof result === 'object' && typeof result.then === 'function') { result.then((v) => cb(null, v), cb) - } else if (result !== null && result !== undefined) { - // TODO: should be removed in fastify@5 - FSTDEP013() - cb(null, result) } } diff --git a/lib/warnings.js b/lib/warnings.js index c7ba5a64d78..9332260062d 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -1,18 +1,13 @@ 'use strict' -const { createDeprecation, createWarning } = require('process-warning') +const { createWarning } = require('process-warning') /** * Deprecation codes: - * - FSTDEP013 * - FSTWRN001 * - FSTSEC001 */ -const FSTDEP013 = createDeprecation({ - code: 'FSTDEP013', - message: '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`.' -}) const FSTWRN001 = createWarning({ name: 'FastifyWarning', code: 'FSTWRN001', @@ -28,7 +23,6 @@ const FSTSEC001 = createWarning({ }) module.exports = { - FSTDEP013, FSTWRN001, FSTSEC001 } diff --git a/test/reply-trailers.test.js b/test/reply-trailers.test.js index 57a31bcf086..374625a8a03 100644 --- a/test/reply-trailers.test.js +++ b/test/reply-trailers.test.js @@ -186,37 +186,6 @@ test('error in trailers should be ignored', t => { }) }) -test('should emit deprecation warning when using direct return', t => { - t.plan(7) - - const fastify = Fastify() - - fastify.get('/', function (request, reply) { - reply.trailer('ETag', function (reply, payload) { - return 'custom-etag' - }) - reply.send('') - }) - - process.on('warning', onWarning) - function onWarning (warning) { - t.equal(warning.name, 'DeprecationWarning') - t.equal(warning.code, 'FSTDEP013') - } - t.teardown(() => process.removeListener('warning', onWarning)) - - fastify.inject({ - method: 'GET', - url: '/' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers.trailer, 'etag') - t.equal(res.trailers.etag, 'custom-etag') - t.notHas(res.headers, 'content-length') - }) -}) - test('trailer handler counter', t => { t.plan(2) @@ -412,7 +381,7 @@ test('throw error when trailer header name is not allowed', t => { fastify.get('/', function (request, reply) { for (const key of INVALID_TRAILERS) { try { - reply.trailer(key, () => {}) + reply.trailer(key, () => { }) } catch (err) { t.equal(err.message, `Called reply.trailer with an invalid header name: ${key}`) } From 309d9dafd7f66f944d815a68e212f799135e624b Mon Sep 17 00:00:00 2001 From: Ran Toledo Date: Thu, 8 Aug 2024 16:45:33 +0300 Subject: [PATCH 0733/1295] fix: throwing "FST_ERR_DUPLICATED_ROUTE" error instead of raw error (#5621) --- lib/route.js | 2 +- test/route.8.test.js | 31 ++++++++++++++++++++++++++++++- test/throw.test.js | 4 ++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/route.js b/lib/route.js index 441f5050f57..3d55a6074c3 100644 --- a/lib/route.js +++ b/lib/route.js @@ -337,7 +337,7 @@ function buildRouting (options) { // any route insertion error created by fastify can be safely ignore // because it only duplicate route for head if (!context[kRouteByFastify]) { - const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route '${opts.url}'`) + const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route`) if (isDuplicatedRoute) { throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url) } diff --git a/test/route.8.test.js b/test/route.8.test.js index eb1e0f924fe..23513e8465b 100644 --- a/test/route.8.test.js +++ b/test/route.8.test.js @@ -4,7 +4,9 @@ const t = require('tap') const test = t.test const sget = require('simple-get').concat const Fastify = require('../fastify') -const { FST_ERR_INVALID_URL } = require('../lib/errors') +const { + FST_ERR_INVALID_URL +} = require('../lib/errors') const { getServerUrl } = require('./helper') test('Request and Reply share the route options', async t => { @@ -140,3 +142,30 @@ test('using fastify.all when a catchall is defined does not degrade performance' t.pass() }) + +test('Adding manually HEAD route after GET with the same path throws Fastify duplicated route instance error', t => { + t.plan(1) + + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/:param1', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + try { + fastify.route({ + method: 'HEAD', + path: '/:param2', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + t.fail('Should throw fastify duplicated route declaration') + } catch (error) { + t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') + } +}) diff --git a/test/throw.test.js b/test/throw.test.js index cfa809f613e..eb31d756a05 100644 --- a/test/throw.test.js +++ b/test/throw.test.js @@ -22,9 +22,9 @@ test('Fastify should throw on multiple assignment to the same route', t => { try { fastify.get('/', () => {}) - t.fail('Should throw on duplicated route declaration') + t.fail('Should throw fastify duplicated route declaration') } catch (error) { - t.equal(error.message, "Method 'GET' already declared for route '/'") + t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') } }) From b6c828e07e1049a611c72013d6cf02c07194f4ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:09:32 +0000 Subject: [PATCH 0734/1295] chore: Bump @sinclair/typebox in the dev-dependencies group (#5625) Bumps the dev-dependencies group with 1 update: [@sinclair/typebox](https://github.com/sinclairzx81/typebox). Updates `@sinclair/typebox` from 0.32.35 to 0.33.4 - [Commits](https://github.com/sinclairzx81/typebox/compare/0.32.35...0.33.4) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f1bca6ec9a4..16ed677f1e1 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ ], "devDependencies": { "@fastify/pre-commit": "^2.1.0", - "@sinclair/typebox": "^0.32.22", + "@sinclair/typebox": "^0.33.4", "@sinonjs/fake-timers": "^11.2.2", "@stylistic/eslint-plugin": "^2.1.0", "@stylistic/eslint-plugin-js": "^2.1.0", From 3ececdda6c43a6e1a102762a2b9d3bbbee2a04f9 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Fri, 16 Aug 2024 00:10:41 -0300 Subject: [PATCH 0735/1295] refactor: reorder handling of Response replies (#5612) We must add Response.headers before proceeding, otherwise early returns (ie: 204) will miss them. We can also remove special handling of ReadableStream: since the Response usage is fully finished, we can continue with Response.body as the payload and the possible types 'null' or 'ReadableStream' will be handled by existing cases. Signed-off-by: Gustavo Sverzut Barbieri --- lib/reply.js | 44 ++++++++++++-------------- test/web-api.test.js | 75 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 24 deletions(-) diff --git a/lib/reply.js b/lib/reply.js index 8e916bb5510..d3d0c1eccad 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -568,14 +568,30 @@ function onSendEnd (reply, payload) { reply.header('Trailer', header.trim()) } - // since Response contain status code, we need to update before - // any action that used statusCode - const isResponse = toString.call(payload) === '[object Response]' - if (isResponse) { + // since Response contain status code, headers and body, + // we need to update the status, add the headers and use it's body as payload + // before continuing + if (toString.call(payload) === '[object Response]') { // https://developer.mozilla.org/en-US/docs/Web/API/Response/status if (typeof payload.status === 'number') { reply.code(payload.status) } + + // https://developer.mozilla.org/en-US/docs/Web/API/Response/headers + if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') { + for (const [headerName, headerValue] of payload.headers) { + reply.header(headerName, headerValue) + } + } + + // https://developer.mozilla.org/en-US/docs/Web/API/Response/body + if (payload.body !== null) { + if (payload.bodyUsed) { + throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED() + } + } + // Keep going, body is either null or ReadableStream + payload = payload.body } const statusCode = res.statusCode @@ -622,26 +638,6 @@ function onSendEnd (reply, payload) { return } - // Response - if (isResponse) { - // https://developer.mozilla.org/en-US/docs/Web/API/Response/headers - if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') { - for (const [headerName, headerValue] of payload.headers) { - reply.header(headerName, headerValue) - } - } - - // https://developer.mozilla.org/en-US/docs/Web/API/Response/body - if (payload.body != null) { - if (payload.bodyUsed) { - throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED() - } - // Response.body always a ReadableStream - sendWebStream(payload.body, res, reply) - } - return - } - if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) { throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload) } diff --git a/test/web-api.test.js b/test/web-api.test.js index 4be5d6d5d2d..5441d2729c8 100644 --- a/test/web-api.test.js +++ b/test/web-api.test.js @@ -56,6 +56,81 @@ test('should response with a Response', async (t) => { t.equal(headers.hello, 'world') }) +test('should response with a Response 204', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.send(new Response(null, { + status: 204, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + t.equal(statusCode, 204) + t.equal(body, '') + t.equal(headers.hello, 'world') +}) + +test('should response with a Response 304', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.send(new Response(null, { + status: 304, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + t.equal(statusCode, 304) + t.equal(body, '') + t.equal(headers.hello, 'world') +}) + +test('should response with a Response without body', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.send(new Response(null, { + status: 200, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + t.equal(statusCode, 200) + t.equal(body, '') + t.equal(headers.hello, 'world') +}) + test('able to use in onSend hook - ReadableStream', async (t) => { t.plan(4) From a89efa3be893fe321d98df5d3292ccbf163080c1 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Fri, 16 Aug 2024 09:18:27 +0100 Subject: [PATCH 0736/1295] ci(.github): use latest node lts version (#5577) Co-authored-by: Manuel Spigolon --- .github/workflows/benchmark-parser.yml | 1 + .github/workflows/benchmark.yml | 1 + .github/workflows/ci.yml | 5 +++++ .github/workflows/citgm-package.yml | 1 + .github/workflows/coverage-nix.yml | 1 + .github/workflows/coverage-win.yml | 1 + .github/workflows/integration.yml | 1 + .github/workflows/md-lint.yml | 1 + .github/workflows/package-manager-ci.yml | 2 ++ 9 files changed, 14 insertions(+) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index b8aea74788f..8282c166faa 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -28,6 +28,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + check-latest: true - name: Install run: | diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b8333ab1abb..6824f625cbd 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -28,6 +28,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + check-latest: true - name: Install run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d19768984a9..99602d1bfac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,7 @@ jobs: node-version: 'lts/*' cache: 'npm' cache-dependency-path: package.json + check-latest: true - name: Install run: | @@ -77,6 +78,7 @@ jobs: node-version: 'lts/*' cache: 'npm' cache-dependency-path: package.json + check-latest: true - name: Install run: | @@ -125,6 +127,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'npm' cache-dependency-path: package.json + check-latest: true - name: Install run: | @@ -153,6 +156,7 @@ jobs: node-version: '20' cache: 'npm' cache-dependency-path: package.json + check-latest: true - name: Install run: | @@ -181,6 +185,7 @@ jobs: node-version: 'lts/*' cache: 'npm' cache-dependency-path: package.json + check-latest: true - name: install fastify run: | npm install --ignore-scripts diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index cf421bb4251..fe6dd6705c1 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -55,6 +55,7 @@ jobs: node-version: ${{ inputs.node-version }} cache: 'npm' cache-dependency-path: package.json + check-latest: true - name: Install Dependencies for Fastify run: | diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index a13c3655bab..3d19bde7902 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -18,6 +18,7 @@ jobs: node-version: 'lts/*' cache: 'npm' cache-dependency-path: package.json + check-latest: true - name: Install run: | diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index a64a0e26cb2..a963067baab 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -18,6 +18,7 @@ jobs: node-version: 'lts/*' cache: 'npm' cache-dependency-path: package.json + check-latest: true - name: Install run: | diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 52d11ef7ad3..c8ab6b322f1 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -36,6 +36,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + check-latest: true - name: Install Pnpm uses: pnpm/action-setup@v4 diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index e915c1a32c5..95862f259f0 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -32,6 +32,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 'lts/*' + check-latest: true - name: Install Linter run: npm install --ignore-scripts diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 25d77e54f88..ac7e14f2090 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -28,6 +28,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + check-latest: true - name: Install with pnpm uses: pnpm/action-setup@v4 @@ -60,6 +61,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + check-latest: true - name: Install with yarn run: | From e3ec5e2bd54c26f56b93cb29e5c6c3ae379a53b1 Mon Sep 17 00:00:00 2001 From: busybox <29630035+busybox11@users.noreply.github.com> Date: Sat, 17 Aug 2024 23:42:55 +0200 Subject: [PATCH 0737/1295] docs/Reference/Server: Show default maxParamLength value (#5630) --- docs/Reference/Server.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 19ce776809f..6fcb3b4daf4 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -270,6 +270,8 @@ fastify.get('///foo//bar//', function (req, reply) { ### `maxParamLength` ++ Default: `100` + You can set a custom length for parameters in parametric (standard, regex, and multi) routes by using `maxParamLength` option; the default value is 100 characters. If the maximum length limit is reached, the not found route will From 5365a47d5a64314f4d105f832e42113614e0aa68 Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Thu, 22 Aug 2024 00:11:20 +0200 Subject: [PATCH 0738/1295] chore: simplify `neostandard` setup (#5635) --- eslint.config.js | 35 +++++++++-------------------------- package.json | 2 +- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index a12d9b004d2..beac860e1e3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,28 +1,11 @@ 'use strict' -const neo = require('neostandard') - -module.exports = [ - { - ignores: [ - 'lib/configValidator.js', - 'lib/error-serializer.js', - 'test/same-shape.test.js', - 'test/types/import.js' - ] - }, - ...neo({ - ts: true - }), - { - rules: { - '@stylistic/comma-dangle': ['error', { - arrays: 'never', - objects: 'never', - imports: 'never', - exports: 'never', - functions: 'never' - }] - } - } -] +module.exports = require('neostandard')({ + ignores: [ + 'lib/configValidator.js', + 'lib/error-serializer.js', + 'test/same-shape.test.js', + 'test/types/import.js' + ], + ts: true +}) diff --git a/package.json b/package.json index 16ed677f1e1..2fc942af5b1 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", "markdownlint-cli2": "^0.13.0", - "neostandard": "^0.11.0", + "neostandard": "^0.11.3", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", "send": "^0.18.0", From 1a64b44c1cb1483c796c91388795dccbae5ebf4a Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 22 Aug 2024 19:15:12 +0200 Subject: [PATCH 0739/1295] chore: fix sponsor link (#5640) --- SPONSORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPONSORS.md b/SPONSORS.md index 71c4e785eb9..cbfb84a505f 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -15,7 +15,7 @@ _Be the first!_ - [Mercedes-Benz Group](https://github.com/mercedes-benz) - [Val Town, Inc.](https://opencollective.com/valtown) -- [Handsontable - JavaScript Data Grid](https://handsontable.com/) +- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) ## Tier 2 From 347e3f8ca2f9588f4f3820bbe56718615418210e Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Mon, 26 Aug 2024 01:05:05 -0300 Subject: [PATCH 0740/1295] docs: move RafaelGSS to past collaborators (#5645) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a19fa22e166..fd3e7e0c7ac 100644 --- a/README.md +++ b/README.md @@ -307,8 +307,6 @@ listed in alphabetical order. , * [__Carlos Fuentes__](https://github.com/metcoder95), , -* [__Rafael Gonzaga__](https://github.com/rafaelgss), - , * [__Vincent Le Goff__](https://github.com/zekth) * [__Luciano Mammino__](https://github.com/lmammino), , @@ -371,6 +369,8 @@ to join this group by Lead Maintainers. , * [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/), , +* [__Rafael Gonzaga__](https://github.com/rafaelgss), + , ## Hosted by From 15282575db8b869f68b863c5a75e79507bb8802b Mon Sep 17 00:00:00 2001 From: Mike Sammartino Date: Sat, 31 Aug 2024 05:25:53 -0400 Subject: [PATCH 0741/1295] docs(type-providers): fix typos (#5651) --- docs/Reference/Type-Providers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index e90babefe86..5c1c2b285b4 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -154,8 +154,8 @@ fastify.register(pluginWithJsonSchema) fastify.register(pluginWithTypebox) ``` -It's also important to mention that once the types don't propagate globally, -_currently_ is not possible to avoid multiple registrations on routes when +It's also important to mention that since the types don't propagate globally, +_currently_ it is not possible to avoid multiple registrations on routes when dealing with several scopes, see below: ```ts From f29b5f41dc7cfa386317ad86ef4afe943f3e951c Mon Sep 17 00:00:00 2001 From: arshcodemod <167029627+arshcodemod@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:29:09 +0530 Subject: [PATCH 0742/1295] feat: add fastify v4 codemods (#5642) * feat(codemod) : add fastify v4 codemods * feat: updated fastify-v4-changes codemod --- docs/Guides/Migration-Guide-V4.md | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 9900bbedf31..20f8e3765b0 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -6,6 +6,30 @@ Before migrating to v4, please ensure that you have fixed all deprecation warnings from v3. All v3 deprecations have been removed and they will no longer work after upgrading. +## Codemods +### Fastify v4 Codemods + +To help with the upgrade, we’ve worked with the team at codemod.com to +publish codemods that will automatically update your code to many of +the new APIs and patterns in Fastify v4. +Run the following codemods to automatically update your code for Fastify v4 migration: + +``` +npx codemod@latest fastify/4/migration-recipe +``` + +This will run the following codemods from the Fastify Codemod repository: + +- **fastify/4/remove-app-use** +- **fastify/4/reply-raw-access** +- **fastify/4/wrap-routes-plugin** +- **fastify/4/await-register-calls** + +Each of these codemods automates the changes listed in the v4 migration guide. +For a complete list of available Fastify codemods and further details, +see the [codemod registry](https://codemod.com/registry?q=fastify). + + ## Breaking Changes ### Error handling composition ([#3261](https://github.com/fastify/fastify/pull/3261)) @@ -55,11 +79,23 @@ If you need to use middleware, use continue to be maintained. However, it is strongly recommended that you migrate to Fastify's [hooks](../Reference/Hooks.md). +> **Note**: Codemod remove `app.use()` with: +> +> ```bash +> npx codemod@latest fastify/4/remove-app-use +> ``` + ### `reply.res` moved to `reply.raw` If you previously used the `reply.res` attribute to access the underlying Request object you will now need to use `reply.raw`. +> **Note**: Codemod `reply.res` to `reply.raw` with: +> +> ```bash +> npx codemod@latest fastify/4/reply-raw-access +> ``` + ### Need to `return reply` to signal a "fork" of the promise chain In some situations, like when a response is sent asynchronously or when you are @@ -105,6 +141,11 @@ As a result, if you specify an `onRoute` hook in a plugin you should now either: done(); }); ``` +> **Note**: Codemod synchronous route definitions with: +> +> ```bash +> npx codemod@latest fastify/4/wrap-routes-plugin +> ``` * use `await register(...)` @@ -130,6 +171,13 @@ As a result, if you specify an `onRoute` hook in a plugin you should now either: }); ``` +> **Note**: Codemod 'await register(...)' with: +> +> ```bash +> npx codemod@latest fastify/4/await-register-calls +> ``` + + ### Optional URL parameters If you've already used any implicitly optional parameters, you'll get a 404 From 4def191a2e01d342fec454afff0300a251affc31 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Sep 2024 13:18:40 +0200 Subject: [PATCH 0743/1295] Bump find-my-way to v9 (#5652) Signed-off-by: Matteo Collina Co-authored-by: Aras Abbasi --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fc942af5b1..6cd7297aeb4 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,7 @@ "abstract-logging": "^2.0.1", "avvio": "^8.3.0", "fast-json-stringify": "^6.0.0", - "find-my-way": "^8.1.0", + "find-my-way": "^9.0.0", "light-my-request": "^5.13.0", "pino": "^9.0.0", "process-warning": "^4.0.0", From a6a4b212779a9705a1819087091d5873a0365336 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Sep 2024 13:18:48 +0200 Subject: [PATCH 0744/1295] Bump ajv-compiler to v4.0.0 (#5653) Signed-off-by: Matteo Collina --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6cd7297aeb4..82e9df26a5a 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "yup": "^1.4.0" }, "dependencies": { - "@fastify/ajv-compiler": "^3.5.0", + "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", From 1bcfb4a50bdc2ebf27da6bd11b6ab5f4777fbbc6 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Sep 2024 16:09:12 +0200 Subject: [PATCH 0745/1295] Bumped light-my-request to v6.0.0 (#5655) Signed-off-by: Matteo Collina --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82e9df26a5a..d82adc29bb1 100644 --- a/package.json +++ b/package.json @@ -197,7 +197,7 @@ "avvio": "^8.3.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", - "light-my-request": "^5.13.0", + "light-my-request": "^6.0.0", "pino": "^9.0.0", "process-warning": "^4.0.0", "proxy-addr": "^2.0.7", From bd66380e397bb3f2685ef8891f3232944d36eb7b Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Sep 2024 16:16:09 +0200 Subject: [PATCH 0746/1295] Bumped avvio to v9.0.0 (#5656) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d82adc29bb1..046405bb58f 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,7 @@ "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", - "avvio": "^8.3.0", + "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", From 985b3c32c9dcd416282b31c1af2d8f7d4e310cff Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 3 Sep 2024 09:40:47 +0200 Subject: [PATCH 0747/1295] Bumped v5.0.0-alpha.4 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/configValidator.js | 63 ++++++++++++++---------------------------- package.json | 2 +- 3 files changed, 22 insertions(+), 45 deletions(-) diff --git a/fastify.js b/fastify.js index b3f94559a88..527450fd44c 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.0.0-alpha.3' +const VERSION = '5.0.0-alpha.4' const Avvio = require('avvio') const http = require('node:http') diff --git a/lib/configValidator.js b/lib/configValidator.js index 696856e1a17..2c1e8b1534d 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -1,9 +1,9 @@ -// This file is autogenerated by build\build-validation.js, do not edit +// This file is autogenerated by build/build-validation.js, do not edit /* c8 ignore start */ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -1002,54 +1002,32 @@ data["useSemicolonDelimiter"] = coerced25; } var valid0 = _errs67 === errors; if(valid0){ -if(data.versioning !== undefined){ -let data23 = data.versioning; +if(data.constraints !== undefined){ +let data23 = data.constraints; const _errs69 = errors; if(errors === _errs69){ if(data23 && typeof data23 == "object" && !Array.isArray(data23)){ -let missing1; -if(((data23.storage === undefined) && (missing1 = "storage")) || ((data23.deriveVersion === undefined) && (missing1 = "deriveVersion"))){ -validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; -return false; -} -} -else { -validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/type",keyword:"type",params:{type: "object"},message:"must be object"}]; -return false; -} -} -var valid0 = _errs69 === errors; -} -else { -var valid0 = true; -} -if(valid0){ -if(data.constraints !== undefined){ -let data24 = data.constraints; +for(const key2 in data23){ +let data24 = data23[key2]; const _errs72 = errors; if(errors === _errs72){ if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ -for(const key2 in data24){ -let data25 = data24[key2]; -const _errs75 = errors; -if(errors === _errs75){ -if(data25 && typeof data25 == "object" && !Array.isArray(data25)){ -let missing2; -if(((((data25.name === undefined) && (missing2 = "name")) || ((data25.storage === undefined) && (missing2 = "storage"))) || ((data25.validate === undefined) && (missing2 = "validate"))) || ((data25.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){ -validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing2},message:"must have required property '"+missing2+"'"}]; +let missing1; +if(((((data24.name === undefined) && (missing1 = "name")) || ((data24.storage === undefined) && (missing1 = "storage"))) || ((data24.validate === undefined) && (missing1 = "validate"))) || ((data24.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){ +validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; return false; } else { -if(data25.name !== undefined){ -let data26 = data25.name; -if(typeof data26 !== "string"){ -let dataType26 = typeof data26; +if(data24.name !== undefined){ +let data25 = data24.name; +if(typeof data25 !== "string"){ +let dataType26 = typeof data25; let coerced26 = undefined; if(!(coerced26 !== undefined)){ if(dataType26 == "number" || dataType26 == "boolean"){ -coerced26 = "" + data26; +coerced26 = "" + data25; } -else if(data26 === null){ +else if(data25 === null){ coerced26 = ""; } else { @@ -1058,9 +1036,9 @@ return false; } } if(coerced26 !== undefined){ -data26 = coerced26; -if(data25 !== undefined){ -data25["name"] = coerced26; +data25 = coerced26; +if(data24 !== undefined){ +data24["name"] = coerced26; } } } @@ -1072,7 +1050,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/ return false; } } -var valid7 = _errs75 === errors; +var valid7 = _errs72 === errors; if(!valid7){ break; } @@ -1083,7 +1061,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs72 === errors; +var valid0 = _errs69 === errors; } else { var valid0 = true; @@ -1112,7 +1090,6 @@ var valid0 = true; } } } -} else { validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; diff --git a/package.json b/package.json index 046405bb58f..2d359f4c4df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 6b1b8a78589f51a68d8c557eb3474376bac2eb05 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 4 Sep 2024 17:40:56 +0200 Subject: [PATCH 0748/1295] chore: bump fast-json-stringify-compiler to v5.0.0 (#5660) * chore: bump fast-json-stringify-compiler to v5.0.0 Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina --- package.json | 2 +- test/types/instance.test-d.ts | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 2d359f4c4df..83b3eb08e62 100644 --- a/package.json +++ b/package.json @@ -192,7 +192,7 @@ "dependencies": { "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", - "@fastify/fast-json-stringify-compiler": "^4.3.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index f41f92bb952..091970cb7f4 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -370,11 +370,12 @@ expectError(server.decorate('test', true)) expectError(server.decorate<(myNumber: number) => number>('test', function (myNumber: number): string { return '' })) -expectError(server.decorate('test', { - getter () { - return true - } -})) +// TODO(mcollina): uncomment after https://github.com/tsdjs/tsd/pull/220 lands. +// expectError(server.decorate('test', { +// getter () { +// return true +// } +// })) expectError(server.decorate('test', { setter (x) {} })) @@ -418,11 +419,12 @@ server.decorate('typedTestProperty') server.decorate('typedTestProperty', null, ['foo']) expectError(server.decorate('typedTestProperty', null)) expectError(server.decorate('typedTestProperty', 'foo')) -expectError(server.decorate('typedTestProperty', { - getter () { - return 'foo' - } -})) +// TODO(mcollina): uncomment after https://github.com/tsdjs/tsd/pull/220 lands. +// expectError(server.decorate('typedTestProperty', { +// getter () { +// return 'foo' +// } +// })) server.decorate('typedTestMethod', function (x) { expectType(x) expectType(this) From 501be899761e60dfc713ac0a415b518ba6595034 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Sat, 7 Sep 2024 03:23:42 -0400 Subject: [PATCH 0749/1295] doc: add dancastillo to Fastify Plugins team (#5668) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fd3e7e0c7ac..693d0087bbd 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,8 @@ listed in alphabetical order. , * [__Gürgün Dayıoğlu__](https://github.com/gurgunday), +* [__Dan Castillo__](https://github.com/dancastillo), + ### Great Contributors Great contributors on a specific area in the Fastify ecosystem will be invited From 07bc7cf7600e939b9d3a83338f888cfbe4a52b79 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:51:07 +0200 Subject: [PATCH 0750/1295] docs: join plugin team (#5677) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 693d0087bbd..0b897b11593 100644 --- a/README.md +++ b/README.md @@ -348,6 +348,8 @@ listed in alphabetical order. * [__Dan Castillo__](https://github.com/dancastillo), +* [__Jean Michelet__](https://github.com/jean-michelet), + ### Great Contributors Great contributors on a specific area in the Fastify ecosystem will be invited From b493fced6b7b22bd360b38b36064b0f1de310b2e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 10 Sep 2024 12:43:46 +0200 Subject: [PATCH 0751/1295] chore: list the next deprecation code (#5673) * List the next deprecation code Signed-off-by: Matteo Collina * Update lib/warnings.js Co-authored-by: Aras Abbasi Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Signed-off-by: Matteo Collina Co-authored-by: Aras Abbasi --- lib/warnings.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/warnings.js b/lib/warnings.js index 9332260062d..3863b236fa3 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -6,6 +6,8 @@ const { createWarning } = require('process-warning') * Deprecation codes: * - FSTWRN001 * - FSTSEC001 + * + * Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT not be reused. */ const FSTWRN001 = createWarning({ From 694cf4369c4d5571a11269e2b5376c53690caf4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 11 Sep 2024 15:42:49 +0200 Subject: [PATCH 0752/1295] types: remove nonexistant done parameter from onRegister (#5678) --- test/hooks.test.js | 5 +++-- test/types/hooks.test-d.ts | 5 +---- types/hooks.d.ts | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/test/hooks.test.js b/test/hooks.test.js index bb4ede541f1..c00acc6839a 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3072,13 +3072,14 @@ test('preSerialization hooks should support encapsulation', t => { }) test('onRegister hook should be called / 1', t => { - t.plan(3) + t.plan(4) const fastify = Fastify() - fastify.addHook('onRegister', (instance, opts) => { + fastify.addHook('onRegister', (instance, opts, done) => { // duck typing for the win! t.ok(instance.addHook) t.same(opts, pluginOpts) + t.notOk(done) }) const pluginOpts = { prefix: 'hello', custom: 'world' } diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 63ced1938ea..dcb2490ce11 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -131,12 +131,9 @@ server.addHook('onRoute', function (opts) { expectType(opts) }) -server.addHook('onRegister', (instance, opts, done) => { +server.addHook('onRegister', (instance, opts) => { expectType(instance) expectType(opts) - expectAssignable<(err?: FastifyError) => void>(done) - expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) - expectType(done(new Error())) }) server.addHook('onReady', function (done) { diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 6e878763810..b59aae70fc7 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -711,9 +711,8 @@ export interface onRegisterHookHandler< > { ( instance: FastifyInstance, - opts: RegisterOptions & Options, - done: HookHandlerDoneFunction - ): Promise | void; // documentation is missing the `done` method + opts: RegisterOptions & Options + ): Promise | void; } /** From 455ff7f3f7f933ec5dbaba8692e277275cbee5d6 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 11 Sep 2024 15:50:47 +0200 Subject: [PATCH 0753/1295] docs: add v5 guide (#5674) * Add v5 guide Signed-off-by: Matteo Collina * Update Migration-Guide-V5.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Matteo Collina * Update Migration-Guide-V5.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Matteo Collina * Update Migration-Guide-V5.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Matteo Collina * Update Migration-Guide-V5.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Matteo Collina * Update Migration-Guide-V5.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Matteo Collina * Update Migration-Guide-V5.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Matteo Collina * linted Signed-off-by: Matteo Collina * add missing things Signed-off-by: Matteo Collina * Update Migration-Guide-V5.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Matteo Collina * Update Migration-Guide-V5.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Simone Busoli Signed-off-by: Frazer Smith * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Simone Busoli Signed-off-by: Frazer Smith * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Simone Busoli Signed-off-by: Frazer Smith * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Carlos Fuentes Signed-off-by: Frazer Smith * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> Signed-off-by: Matteo Collina * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Simone Busoli Signed-off-by: Matteo Collina * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Simone Busoli Signed-off-by: Matteo Collina * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Simone Busoli Signed-off-by: Matteo Collina * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Simone Busoli Signed-off-by: Matteo Collina * review feedbacks Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * Apply suggestions from code review Co-authored-by: Frazer Smith Signed-off-by: Matteo Collina * Apply suggestions from code review Co-authored-by: Frazer Smith Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina * Update docs/Guides/Migration-Guide-V5.md Co-authored-by: Frazer Smith Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Signed-off-by: Matteo Collina Signed-off-by: Frazer Smith Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Co-authored-by: Frazer Smith Co-authored-by: Simone Busoli Co-authored-by: Carlos Fuentes Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --- docs/Guides/Migration-Guide-V5.md | 570 +++++++++++++++++++++++++++++- docs/Reference/LTS.md | 11 +- 2 files changed, 575 insertions(+), 6 deletions(-) diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index c4bcbb0869c..75b4ad2fe7b 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -3,11 +3,103 @@ This guide is intended to help with migration from Fastify v4 to v5. Before migrating to v5, please ensure that you have fixed all deprecation -warnings from v4. All v4 deprecations have been removed and they will no longer +warnings from v4. All v4 deprecations have been removed and will no longer work after upgrading. +## Long Term Support Cycle + +Fastify v5 will only support Node.js v20+. If you are using an older version of +Node.js, you will need to upgrade to a newer version to use Fastify v5. + +Fastify v4 is still supported until June 30, 2025. If you are unable to upgrade, +you should consider buying an end-of-life support plan from HeroDevs. + +### Why Node.js v20? + +Fastify v5 will only support Node.js v20+ because it has significant differences +compared to v18, such as +better support for `node:test`. This allows us to provide a better developer +experience and streamline maintenance. + +Node.js v18 will exit Long Term Support on April 30, 2025, so you should be planning +to upgrade to v20 anyway. + ## Breaking Changes +### Full JSON Schema is now required for `querystring`, `params` and `body` and response schemas + +Starting with v5, Fastify will require a full JSON schema for the `querystring`, +`params` and `body` schema. Note that the `jsonShortHand` option has been +removed as well. + +If the default JSON Schema validator is used, you will need +to provide a full JSON schema for the +`querystring`, `params`, `body`, and `response` schemas, +including the `type` property. + +```js +// v4 +fastify.get('/route', { + schema: { + querystring: { + name: { type: 'string' } + } + } +}, (req, reply) => { + reply.send({ hello: req.query.name }); +}); +``` + +```js +// v5 +fastify.get('/route', { + schema: { + querystring: { + type: 'object', + properties: { + name: { type: 'string' } + }, + required: ['name'] + } + } +}, (req, reply) => { + reply.send({ hello: req.query.name }); +}); +``` + +See [#5586](https://github.com/fastify/fastify/pull/5586) for more details + +Note that it's still possible to override the JSON Schema validator to +use a different format, such as Zod. This change simplifies that as well. + +This change helps with integration of other tools, such as +[`@fastify/swagger`](https://github.com/fastify/fastify-swagger). + +### New logger constructor signature + +In Fastify v4, Fastify accepted the options to build a pino +logger in the `logger` option, as well as a custom logger instance. +This was the source of significant confusion. + +As a result, the `logger` option will not accept a custom logger anymore in v5. +To use a custom logger, you should use the `loggerInstance` option instead: + +```js +// v4 +const logger = require('pino')(); +const fastify = require('fastify')({ + logger +}); +``` + +```js +// v5 +const loggerInstance = require('pino')(); +const fastify = require('fastify')({ + loggerInstance +}); +``` + ### `useSemicolonDelimiter` false by default Starting with v5, Fastify instances will no longer default to supporting the use @@ -18,3 +110,479 @@ behavior and not adhering to [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#s If you still wish to use semicolons as delimiters, you can do so by setting `useSemicolonDelimiter: true` in the server configuration. +```js +const fastify = require('fastify')({ + useSemicolonDelimiter: true +}); +``` + +### The parameters object no longer has a prototype + +In v4, the `parameters` object had a prototype. This is no longer the case in v5. +This means that you can no longer access properties inherited from `Object` on +the `parameters` object, such as `toString` or `hasOwnProperty`. + +```js +// v4 +fastify.get('/route/:name', (req, reply) => { + console.log(req.params.hasOwnProperty('name')); // true + return { hello: req.params.name }; +}); +``` + +```js +// v5 +fastify.get('/route/:name', (req, reply) => { + console.log(Object.hasOwn(req.params, 'name')); // true + return { hello: req.params.name }; +}); +``` + +This increases the security of the application by hardening against prototype +pollution attacks. + +### Type Providers now differentiate between validator and serializer schemas + +In v4, the type providers had the same types for both validation and serialization. +In v5, the type providers have been split into two separate types: `ValidatorSchema` +and `SerializerSchema`. + +[`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts) +and +[`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox) +have already been updated: upgrade to the latest version to get the new types. +If you are using a custom type provider, you will need to modify it like +the following: + +``` +--- a/index.ts ++++ b/index.ts +@@ -11,7 +11,8 @@ import { + import { FromSchema, FromSchemaDefaultOptions, FromSchemaOptions, JSONSchema } from 'json-schema-to-ts' + + export interface JsonSchemaToTsProvider< + Options extends FromSchemaOptions = FromSchemaDefaultOptions + > extends FastifyTypeProvider { +- output: this['input'] extends JSONSchema ? FromSchema : unknown; ++ validator: this['schema'] extends JSONSchema ? FromSchema : unknown; ++ serializer: this['schema'] extends JSONSchema ? FromSchema : unknown; + } + ``` + +### Changes to the .listen() method + +The variadic argument signature of the `.listen()` method has been removed. +This means that you can no longer call `.listen()` with a variable number of arguments. + +```js +// v4 +fastify.listen(8000) +``` + +Will become: + +```js +// v5 +fastify.listen({ port: 8000 }) +``` + +This was already deprecated in v4 as `FSTDEP011`, so you should have already updated +your code to use the new signature. + +### Direct return of trailers has been removed + +In v4, you could directly return trailers from a handler. +This is no longer possible in v5. + +```js +// v4 +fastify.get('/route', (req, reply) => { + reply.trailer('ETag', function (reply, payload) { + return 'custom-etag' + }) + reply.send('') +}); +``` + +```js +// v5 +fastify.get('/route', (req, reply) => { + reply.trailer('ETag', async function (reply, payload) { + return 'custom-etag' + }) + reply.send('') +}); +``` + +A callback could also be used. +This was already deprecated in v4 as `FSTDEP013`, +so you should have already updated your code to use the new signature. + +### Streamlined access to route definition + +All deprecated properties relating to accessing the route definition have been removed +and are now accessed via `request.routeOptions`. + +| Code | Description | How to solve | Discussion | +| ---- | ----------- | ------------ | ---------- | +| FSTDEP012 | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) | +| FSTDEP015 | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| FSTDEP016 | You are accessing the deprecated `request.routeConfig` property. | Use `request.routeOptions.config`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| FSTDEP017 | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) | +| FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) | + +See [#5616](https://github.com/fastify/fastify/pull/5616) for more information. + +### `reply.redirect()` has a new signature + +The `reply.redirect()` method has a new signature: +`reply.redirect(url: string, code?: number)`. + +```js +// v4 +reply.redirect(301, '/new-route') +``` + +Change it to: + +```js +// v5 +reply.redirect('/new-route', 301) +``` + +This was already deprecated in v4 as `FSTDEP021`, so you should have already +updated your code to use the new signature. + + +### Modifying `reply.sent` is now forbidden + +In v4, you could modify the `reply.sent` property to prevent the response from +being sent. +This is no longer possible in v5, use `reply.hijack()` instead. + +```js +// v4 +fastify.get('/route', (req, reply) => { + reply.sent = true; + reply.raw.end('hello'); +}); +``` + +Change it to: + +```js +// v5 +fastify.get('/route', (req, reply) => { + reply.hijack(); + reply.raw.end('hello'); +}); +``` + +This was already deprecated in v4 as `FSTDEP010`, so you should have already +updated your code to use the new signature. + +### Constraints for route versioning signature changes + +We changed the signature for route versioning constraints. +The `version` and `versioning` options have been removed and you should +use the `constraints` option instead. + +| Code | Description | How to solve | Discussion | +| ---- | ----------- | ------------ | ---------- | +| FSTDEP008 | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | +| FSTDEP009 | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) | + +### `HEAD` routes requires to register before `GET` when `exposeHeadRoutes: true` + +We have a more strict requirement for custom `HEAD` route when +`exposeHeadRoutes: true`. + +When you provides a custom `HEAD` route, you must either explicitly +set `exposeHeadRoutes` to `false` + +```js +// v4 +fastify.get('/route', { + +}, (req, reply) => { + reply.send({ hello: 'world' }); +}); + +fastify.head('/route', (req, reply) => { + // ... +}); +``` + +```js +// v5 +fastify.get('/route', { + exposeHeadRoutes: false +}, (req, reply) => { + reply.send({ hello: 'world' }); +}); + +fastify.head('/route', (req, reply) => { + // ... +}); +``` + +or place the `HEAD` route before `GET`. + +```js +// v5 +fastify.head('/route', (req, reply) => { + // ... +}); + +fastify.get('/route', { + +}, (req, reply) => { + reply.send({ hello: 'world' }); +}); +``` + +This was changed in [#2700](https://github.com/fastify/fastify/pull/2700), +and the old behavior was deprecated in v4 as `FSTDEP007`. + +### Removed `request.connection` + +The `request.connection` property has been removed in v5. +You should use `request.socket` instead. + +```js +// v4 +fastify.get('/route', (req, reply) => { + console.log(req.connection.remoteAddress); + return { hello: 'world' }; +}); +``` + +```js +// v5 +fastify.get('/route', (req, reply) => { + console.log(req.socket.remoteAddress); + return { hello: 'world' }; +}); +``` + +This was already deprecated in v4 as `FSTDEP05`, so you should +have already updated your code to use the new signature. + +### `reply.getResponseTime()` has been removed, use `reply.elapsedTime` instead + +The `reply.getResponseTime()` method has been removed in v5. +You should use `reply.elapsedTime` instead. + +```js +// v4 +fastify.get('/route', (req, reply) => { + console.log(reply.getResponseTime()); + return { hello: 'world' }; +}); +``` + +```js +// v5 +fastify.get('/route', (req, reply) => { + console.log(reply.elapsedTime); + return { hello: 'world' }; +}); +``` + +This was already deprecated in v4 as `FSTDEP20`, so you should have already +updated your code to use the new signature. + +### `fastify.hasRoute()` now matches the behavior of `find-my-way` + +The `fastify.hasRoute()` method now matches the behavior of `find-my-way` +and requires the route definition to be passed as it is defined in the route. + +```js +// v4 +fastify.get('/example/:file(^\\d+).png', function (request, reply) { }) + +console.log(fastify.hasRoute({ + method: 'GET', + url: '/example/12345.png' +)); // true +``` + +```js +// v5 + +fastify.get('/example/:file(^\\d+).png', function (request, reply) { }) + +console.log(fastify.hasRoute({ + method: 'GET', + url: '/example/:file(^\\d+).png' +)); // true +``` + +### Removal of some non-standard HTTP methods + +We have removed the following HTTP methods from Fastify: +- `PROPFIND` +- `PROPPATCH` +- `MKCOL` +- `COPY` +- `MOVE` +- `LOCK` +- `UNLOCK` +- `TRACE` +- `SEARCH` + +It's now possible to add them back using the `acceptHTTPMethod` method. + +```js +const fastify = Fastify() + +// add a new http method on top of the default ones: +fastify.acceptHTTPMethod('REBIND') + +// add a new HTTP method that accepts a body: +fastify.acceptHTTPMethod('REBIND', { hasBody: true }) + +// reads the HTTP methods list: +fastify.supportedMethods // returns a string array +``` + +See [#5567](https://github.com/fastify/fastify/pull/5567) for more +information. + +### Removed support from reference types in decorators + +Decorating Request/Reply with a reference type (`Array`, `Object`) +is now prohibited as this reference is shared amongst all requests. + +```js +// v4 +fastify.decorateRequest('myObject', { hello: 'world' }); +``` + +```js +// v5 +fastify.decorateRequest('myObject'); +fastify.addHook('onRequest', async (req, reply) => { + req.myObject = { hello: 'world' }; +}); +``` + +or turn it into a function + +```js +// v5 +fastify.decorateRequest('myObject', () => { hello: 'world' }); +``` + +or as a getter + +```js +// v5 +fastify.decorateRequest('myObject', { + getter () { + return { hello: 'world' } + } +}); +``` + +See [#5462](https://github.com/fastify/fastify/pull/5462) for more information. + +### Remove support for DELETE with a `Content-Type: application/json` header and an empty body + +In v4, Fastify allowed `DELETE` requests with a `Content-Type: application/json` +header and an empty body was accepted. +This is no longer allowed in v5. + +See [#5419](https://github.com/fastify/fastify/pull/5419) for more information. + +### Plugins cannot mix callback/promise API anymore + +In v4, plugins could mix the callback and promise API, leading to unexpected behavior. +This is no longer allowed in v5. + +```js +// v4 +fastify.register(async function (instance, opts, done) { + done(); +}); +``` + +```js +// v5 +fastify.register(async function (instance, opts) { + return; +}); +``` + +or + +```js +// v5 +fastify.register(function (instance, opts, done) { + done(); +}); +``` + +### Removes `getDefaultRoute` and `setDefaultRoute` methods + +The `getDefaultRoute` and `setDefaultRoute` methods have been removed in v5. + +See [#4485](https://github.com/fastify/fastify/pull/4485) +and [#4480](https://github.com/fastify/fastify/pull/4485) +for more information. +This was already deprecated in v4 as `FSTDEP014`, +so you should have already updated your code. + +## New Features + +### Diagnostic Channel support + +Fastify v5 now supports the [Diagnostic Channel](https://nodejs.org/api/diagnostic_channel.html) +API natively +and provides a way to trace the lifecycle of a request. + +```js +'use strict' + +const diagnostics = require('node:diagnostics_channel') +const sget = require('simple-get').concat +const Fastify = require('fastify') + +diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + console.log(msg.route.url) // '/:id' + console.log(msg.route.method) // 'GET' +}) + +diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + // msg is the same as the one emitted by the 'tracing:fastify.request.handler:start' channel + console.log(msg) +}) + +diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + // in case of error +}) + +const fastify = Fastify() +fastify.route({ + method: 'GET', + url: '/:id', + handler: function (req, reply) { + return { hello: 'world' } + } +}) + +fastify.listen({ port: 0 }, function () { + sget({ + method: 'GET', + url: fastify.listeningOrigin + '/7' + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + t.same(JSON.parse(body), { hello: 'world' }) + }) +}) +``` + +See the [documentation](https://github.com/fastify/fastify/blob/main/docs/Reference/Hooks.md#diagnostics-channel-hooks) +and [#5252](https://github.com/fastify/fastify/pull/5252) for additional details. diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 30a1b8cec79..12aee52a982 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -57,7 +57,8 @@ A "month" is defined as 30 consecutive days. | 1.0.0 | 2018-03-06 | 2019-09-01 | 6, 8, 9, 10, 11 | | | 2.0.0 | 2019-02-25 | 2021-01-31 | 6, 8, 10, 12, 14 | | | 3.0.0 | 2020-07-07 | 2023-06-30 | 10, 12, 14, 16, 18 | v5(18) | -| 4.0.0 | 2022-06-08 | TBD | 14, 16, 18, 20 | v5(18), v5(20) | +| 4.0.0 | 2022-06-08 | 2025-06-30 | 14, 16, 18, 20, 22 | v5(18), v5(20) | +| 5.0.0 | 2024-09-17 | TBD | 20, 22 | v5(20) | ### CI tested operating systems @@ -71,10 +72,10 @@ YAML workflow labels below: | OS | YAML Workflow Label | Package Manager | Node.js | Nsolid(Node) | | ------- | ------------------- | --------------- | ----------- | ------------- | -| Linux | `ubuntu-latest` | npm | 14,16,18,20 | v5(18),v5(20) | -| Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18,20 | v5(18),v5(20) | -| Windows | `windows-latest` | npm | 14,16,18,20 | v5(18),v5(20) | -| MacOS | `macos-latest` | npm | 14,16,18,20 | v5(18),v5(20) | +| Linux | `ubuntu-latest` | npm | 20 | v5(20) | +| Linux | `ubuntu-latest` | yarn,pnpm | 20 | v5(20) | +| Windows | `windows-latest` | npm | 20 | v5(20) | +| MacOS | `macos-latest` | npm | 20 | v5(20) | Using [yarn](https://yarnpkg.com/) might require passing the `--ignore-engines` flag. From d623d4710f8d2423b63ed4743d47533daa03976a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 11 Sep 2024 16:57:47 +0200 Subject: [PATCH 0754/1295] chore: bind this to instance in onclose (#5670) --- fastify.js | 2 +- test/close.test.js | 5 ++++- test/types/hooks.test-d.ts | 6 ++++-- types/hooks.d.ts | 2 ++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/fastify.js b/fastify.js index 527450fd44c..f08db27801f 100644 --- a/fastify.js +++ b/fastify.js @@ -673,7 +673,7 @@ function fastify (options) { } if (name === 'onClose') { - this.onClose(fn) + this.onClose(fn.bind(this)) } else if (name === 'onReady' || name === 'onListen' || name === 'onRoute') { this[kHooks].add(name, fn) } else { diff --git a/test/close.test.js b/test/close.test.js index 856d655f0da..51e13b28782 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -9,11 +9,14 @@ const split = require('split2') const { sleep } = require('./helper') test('close callback', t => { - t.plan(4) + t.plan(7) const fastify = Fastify() fastify.addHook('onClose', onClose) function onClose (instance, done) { + t.type(fastify, this) t.type(fastify, instance) + t.equal(fastify, this) + t.equal(fastify, instance) done() } diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index dcb2490ce11..542dfcaae09 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -149,7 +149,8 @@ server.addHook('onListen', function (done) { expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) }) -server.addHook('onClose', (instance, done) => { +server.addHook('onClose', function (instance, done) { + expectType(this) expectType(instance) expectAssignable<(err?: FastifyError) => void>(done) expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) @@ -234,7 +235,8 @@ server.addHook('onListen', async function () { expectType(this) }) -server.addHook('onClose', async (instance) => { +server.addHook('onClose', async function (instance) { + expectType(this) expectType(instance) }) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index b59aae70fc7..43744f117eb 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -781,6 +781,7 @@ export interface onCloseHookHandler< TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( + this: FastifyInstance, instance: FastifyInstance, done: HookHandlerDoneFunction ): void; @@ -794,6 +795,7 @@ export interface onCloseAsyncHookHandler< TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault > { ( + this: FastifyInstance, instance: FastifyInstance ): Promise; } From 9a1be751bac5e02a92e7ec070912489f4f962c4f Mon Sep 17 00:00:00 2001 From: Mohab Sameh <37941642+mohab-sameh@users.noreply.github.com> Date: Fri, 13 Sep 2024 19:22:45 +0300 Subject: [PATCH 0755/1295] docs: update v4 codemods (#5666) * docs: fix codemod gh url * fix(docs): linting error --- docs/Guides/Migration-Guide-V4.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 20f8e3765b0..ccabda5f044 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -9,25 +9,29 @@ work after upgrading. ## Codemods ### Fastify v4 Codemods -To help with the upgrade, we’ve worked with the team at codemod.com to +To help with the upgrade, we’ve worked with the team at +[Codemod](https://github.com/codemod-com/codemod) to publish codemods that will automatically update your code to many of the new APIs and patterns in Fastify v4. -Run the following codemods to automatically update your code for Fastify v4 migration: + +Run the following +[migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to +automatically update your code to Fastify v4: ``` npx codemod@latest fastify/4/migration-recipe ``` -This will run the following codemods from the Fastify Codemod repository: +This will run the following codemods: -- **fastify/4/remove-app-use** -- **fastify/4/reply-raw-access** -- **fastify/4/wrap-routes-plugin** -- **fastify/4/await-register-calls** +- [`fastify/4/remove-app-use`](https://go.codemod.com/fastify-4-remove-app-use) +- [`fastify/4/reply-raw-access`](https://go.codemod.com/fastify-4-reply-raw-access) +- [`fastify/4/wrap-routes-plugin`](https://go.codemod.com/fastify-4-wrap-routes-plugin) +- [`fastify/4/await-register-calls`](https://go.codemod.com/fastify-4-await-register-calls) Each of these codemods automates the changes listed in the v4 migration guide. For a complete list of available Fastify codemods and further details, -see the [codemod registry](https://codemod.com/registry?q=fastify). +see [Codemod Registry](https://go.codemod.com/fastify). ## Breaking Changes From 7239d78190bdcdac07c392757c1315dfd6cb52fe Mon Sep 17 00:00:00 2001 From: Christopher Masto Date: Tue, 17 Sep 2024 06:28:58 -0400 Subject: [PATCH 0756/1295] docs: Add required .js extension to relative ESM imports (#5685) https://nodejs.org/api/esm.html#mandatory-file-extensions Signed-off-by: Christopher Masto --- docs/Guides/Getting-Started.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index 62144315745..a4d68029a15 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -143,7 +143,7 @@ declaration](../Reference/Routes.md) docs). ```js // ESM import Fastify from 'fastify' -import firstRoute from './our-first-route' +import firstRoute from './our-first-route.js' /** * @type {import('fastify').FastifyInstance} Instance of Fastify */ @@ -232,8 +232,8 @@ npm i fastify-plugin @fastify/mongodb ```js // ESM import Fastify from 'fastify' -import dbConnector from './our-db-connector' -import firstRoute from './our-first-route' +import dbConnector from './our-db-connector.js' +import firstRoute from './our-first-route.js' /** * @type {import('fastify').FastifyInstance} Instance of Fastify From 6bde7d7b0a51010d56df901cab9be5d9351b0d91 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 17 Sep 2024 16:11:22 +0200 Subject: [PATCH 0757/1295] Bumped v5.0.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index f08db27801f..722aa358284 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.0.0-alpha.4' +const VERSION = '5.0.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 83b3eb08e62..2b721069d8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.0.0-alpha.4", + "version": "5.0.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 6ed99cff9d12f243bcb825501de5b709f9ae5822 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:53:14 -0400 Subject: [PATCH 0758/1295] chore: Update Migration-Guide-V5.md (#5688) * Update Migration-Guide-V5.md Add the contributors for the release. Signed-off-by: James Sumners <321201+jsumners@users.noreply.github.com> * Update docs/Guides/Migration-Guide-V5.md Signed-off-by: James Sumners <321201+jsumners@users.noreply.github.com> --------- Signed-off-by: James Sumners <321201+jsumners@users.noreply.github.com> --- docs/Guides/Migration-Guide-V5.md | 124 ++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index 75b4ad2fe7b..346d55ed556 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -586,3 +586,127 @@ fastify.listen({ port: 0 }, function () { See the [documentation](https://github.com/fastify/fastify/blob/main/docs/Reference/Hooks.md#diagnostics-channel-hooks) and [#5252](https://github.com/fastify/fastify/pull/5252) for additional details. + +## Contributors + +The complete list of contributors, across all of the core +Fastify packages, is provided below. Please consider +contributing to those that are capable of accepting sponsorships. + +| Contributor | Sponsor Link | Packages | +| --- | --- | --- | +| 10xLaCroixDrinker | [❤️ sponsor](https://github.com/sponsors/10xLaCroixDrinker) | fastify-cli | +| Bram-dc | | fastify; fastify-swagger | +| BrianValente | | fastify | +| BryanAbate | | fastify-cli | +| Cadienvan | [❤️ sponsor](https://github.com/sponsors/Cadienvan) | fastify | +| Cangit | | fastify | +| Cyberlane | | fastify-elasticsearch | +| Eomm | [❤️ sponsor](https://github.com/sponsors/Eomm) | ajv-compiler; fastify; fastify-awilix; fastify-diagnostics-channel; fastify-elasticsearch; fastify-hotwire; fastify-mongodb; fastify-nextjs; fastify-swagger-ui; under-pressure | +| EstebanDalelR | [❤️ sponsor](https://github.com/sponsors/EstebanDalelR) | fastify-cli | +| Fdawgs | [❤️ sponsor](https://github.com/sponsors/Fdawgs) | aws-lambda-fastify; csrf-protection; env-schema; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-cli; fastify-cookie; fastify-cors; fastify-diagnostics-channel; fastify-elasticsearch; fastify-env; fastify-error; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-hotwire; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-mongodb; fastify-multipart; fastify-mysql; fastify-nextjs; fastify-oauth2; fastify-passport; fastify-plugin; fastify-postgres; fastify-rate-limit; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-sensible; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; forwarded; middie; point-of-view; process-warning; proxy-addr; safe-regex2; secure-json-parse; under-pressure | +| Gehbt | | fastify-secure-session | +| Gesma94 | | fastify-routes-stats | +| H4ad | [❤️ sponsor](https://github.com/sponsors/H4ad) | aws-lambda-fastify | +| JohanManders | | fastify-secure-session | +| LiviaMedeiros | | fastify | +| Momy93 | | fastify-secure-session | +| MunifTanjim | | fastify-swagger-ui | +| Nanosync | | fastify-secure-session | +| RafaelGSS | [❤️ sponsor](https://github.com/sponsors/RafaelGSS) | fastify; under-pressure | +| Rantoledo | | fastify | +| SMNBLMRR | | fastify | +| SimoneDevkt | | fastify-cli | +| Tony133 | | fastify | +| Uzlopak | [❤️ sponsor](https://github.com/sponsors/Uzlopak) | fastify; fastify-autoload; fastify-diagnostics-channel; fastify-hotwire; fastify-nextjs; fastify-passport; fastify-plugin; fastify-rate-limit; fastify-routes; fastify-static; fastify-swagger-ui; point-of-view; under-pressure | +| Zamiell | | fastify-secure-session | +| aadito123 | | fastify | +| aaroncadillac | [❤️ sponsor](https://github.com/sponsors/aaroncadillac) | fastify | +| aarontravass | | fastify | +| acro5piano | [❤️ sponsor](https://github.com/sponsors/acro5piano) | fastify-secure-session | +| adamward459 | | fastify-cli | +| adrai | [❤️ sponsor](https://github.com/sponsors/adrai) | aws-lambda-fastify | +| alenap93 | | fastify | +| alexandrucancescu | | fastify-nextjs | +| anthonyringoet | | aws-lambda-fastify | +| arshcodemod | | fastify | +| autopulated | | point-of-view | +| barbieri | | fastify | +| beyazit | | fastify | +| big-kahuna-burger | [❤️ sponsor](https://github.com/sponsors/big-kahuna-burger) | fastify-cli; fastify-compress; fastify-helmet | +| bilalshareef | | fastify-routes | +| blue86321 | | fastify-swagger-ui | +| bodinsamuel | | fastify-rate-limit | +| busybox11 | [❤️ sponsor](https://github.com/sponsors/busybox11) | fastify | +| climba03003 | | csrf-protection; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-compress; fastify-cors; fastify-env; fastify-etag; fastify-flash; fastify-formbody; fastify-http-proxy; fastify-mongodb; fastify-swagger-ui; fastify-url-data; fastify-websocket; middie | +| dancastillo | [❤️ sponsor](https://github.com/sponsors/dancastillo) | fastify; fastify-basic-auth; fastify-caching; fastify-circuit-breaker; fastify-cors; fastify-helmet; fastify-passport; fastify-response-validation; fastify-routes; fastify-schedule | +| danny-andrews | | fastify-kafka | +| davidcralph | [❤️ sponsor](https://github.com/sponsors/davidcralph) | csrf-protection | +| davideroffo | | under-pressure | +| dhensby | | fastify-cli | +| dmkng | | fastify | +| domdomegg | | fastify | +| faustman | | fastify-cli | +| floridemai | | fluent-json-schema | +| fox1t | | fastify-autoload | +| giuliowaitforitdavide | | fastify | +| gunters63 | | fastify-reply-from | +| gurgunday | | fastify; fastify-circuit-breaker; fastify-cookie; fastify-multipart; fastify-mysql; fastify-rate-limit; fastify-response-validation; fastify-sensible; fastify-swagger-ui; fluent-json-schema; middie; proxy-addr; safe-regex2; secure-json-parse | +| ildella | | under-pressure | +| james-kaguru | | fastify | +| jcbain | | fastify-http-proxy | +| jdhollander | | fastify-swagger-ui | +| jean-michelet | | fastify; fastify-autoload; fastify-cli; fastify-mysql; fastify-sensible | +| johaven | | fastify-multipart | +| jordanebelanger | | fastify-plugin | +| jscheffner | | fastify | +| jsprw | | fastify-secure-session | +| jsumners | [❤️ sponsor](https://github.com/sponsors/jsumners) | ajv-compiler; avvio; csrf-protection; env-schema; fast-json-stringify; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-autoload; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-compress; fastify-cookie; fastify-cors; fastify-env; fastify-error; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-multipart; fastify-mysql; fastify-oauth2; fastify-plugin; fastify-postgres; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-sensible; fastify-static; fastify-swagger; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; forwarded; light-my-request; middie; process-warning; proxy-addr; safe-regex2; secure-json-parse; under-pressure | +| karankraina | | under-pressure | +| kerolloz | [❤️ sponsor](https://github.com/sponsors/kerolloz) | fastify-jwt | +| kibertoad | | fastify-rate-limit | +| kukidon-dev | | fastify-passport | +| kunal097 | | fastify | +| lamweili | | fastify-sensible | +| lemonclown | | fastify-mongodb | +| liuhanqu | | fastify | +| matthyk | | fastify-plugin | +| mch-dsk | | fastify | +| mcollina | [❤️ sponsor](https://github.com/sponsors/mcollina) | ajv-compiler; avvio; csrf-protection; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-autoload; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-cli; fastify-compress; fastify-cookie; fastify-cors; fastify-diagnostics-channel; fastify-elasticsearch; fastify-env; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-multipart; fastify-mysql; fastify-oauth2; fastify-passport; fastify-plugin; fastify-postgres; fastify-rate-limit; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-static; fastify-swagger; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; light-my-request; middie; point-of-view; proxy-addr; secure-json-parse; under-pressure | +| melroy89 | [❤️ sponsor](https://github.com/sponsors/melroy89) | under-pressure | +| metcoder95 | [❤️ sponsor](https://github.com/sponsors/metcoder95) | fastify-elasticsearch | +| mhamann | | fastify-cli | +| mihaur | | fastify-elasticsearch | +| mikesamm | | fastify | +| mikhael-abdallah | | secure-json-parse | +| miquelfire | [❤️ sponsor](https://github.com/sponsors/miquelfire) | fastify-routes | +| miraries | | fastify-swagger-ui | +| mohab-sameh | | fastify | +| monish001 | | fastify | +| moradebianchetti81 | | fastify | +| mouhannad-sh | | aws-lambda-fastify | +| multivoltage | | point-of-view | +| muya | [❤️ sponsor](https://github.com/sponsors/muya) | under-pressure | +| mweberxyz | | point-of-view | +| nflaig | | fastify | +| nickfla1 | | avvio | +| o-az | | process-warning | +| ojeytonwilliams | | csrf-protection | +| onosendi | | fastify-formbody | +| philippviereck | | fastify | +| pip77 | | fastify-mongodb | +| puskin94 | | fastify | +| remidewitte | | fastify | +| rozzilla | | fastify | +| samialdury | | fastify-cli | +| sknetl | | fastify-cors | +| sourcecodeit | | fastify | +| synapse | | env-schema | +| timursaurus | | secure-json-parse | +| tlhunter | | fastify | +| tlund101 | | fastify-rate-limit | +| ttshivers | | fastify-http-proxy | +| voxpelli | [❤️ sponsor](https://github.com/sponsors/voxpelli) | fastify | +| weixinwu | | fastify-cli | +| zetaraku | | fastify-cli | + From 75dc1ad4337bfa74ee74dcca03ea69aeaa59286d Mon Sep 17 00:00:00 2001 From: Corrado Petrelli Date: Wed, 18 Sep 2024 12:16:53 +0200 Subject: [PATCH 0759/1295] fix: wrong link for diagnostics channel (#5693) --- docs/Guides/Migration-Guide-V5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index 346d55ed556..efe395fb065 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -538,7 +538,7 @@ so you should have already updated your code. ### Diagnostic Channel support -Fastify v5 now supports the [Diagnostic Channel](https://nodejs.org/api/diagnostic_channel.html) +Fastify v5 now supports the [Diagnostics Channel](https://nodejs.org/api/diagnostics_channel.html) API natively and provides a way to trace the lifecycle of a request. From fc0d4ea11868e169095f852e65a6d5f92417809c Mon Sep 17 00:00:00 2001 From: Jan Mooij Date: Wed, 18 Sep 2024 12:39:48 +0200 Subject: [PATCH 0760/1295] chore: fix typo in reply-serialize.test.js (#5692) Signed-off-by: Jan Mooij --- test/internals/reply-serialize.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/internals/reply-serialize.test.js b/test/internals/reply-serialize.test.js index 1c370023aaf..9a67fad5b65 100644 --- a/test/internals/reply-serialize.test.js +++ b/test/internals/reply-serialize.test.js @@ -268,7 +268,7 @@ test('Reply#getSerializationFunction', t => { { params: { type: 'object', - properites: { + properties: { id: { type: 'integer' } @@ -363,7 +363,7 @@ test('Reply#getSerializationFunction', t => { { params: { type: 'object', - properites: { + properties: { id: { type: 'integer' } @@ -475,7 +475,7 @@ test('Reply#serializeInput', t => { 'application/json': { schema: { type: 'object', - properites: { + properties: { fullName: { type: 'string' }, phone: { type: 'number' } } @@ -538,7 +538,7 @@ test('Reply#serializeInput', t => { { params: { type: 'object', - properites: { + properties: { id: { type: 'integer' } From 60a0c90d0318e53ff310472927770dd6fefa6c5e Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 19 Sep 2024 03:43:27 +0800 Subject: [PATCH 0761/1295] chore: remove unused dev dependencies (#5696) --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 2b721069d8a..09a8f63ea30 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,6 @@ "neostandard": "^0.11.3", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", - "send": "^0.18.0", "simple-get": "^4.0.1", "split2": "^4.2.0", "tap": "^21.0.0", From ca39a53ebea6d22d4afd6e2affa6acdd1b4d21ec Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 19 Sep 2024 06:12:34 +0800 Subject: [PATCH 0762/1295] docs: fix schema (#5695) --- docs/Reference/Routes.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 2f78c3f5bf5..1e6496e04c7 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -151,8 +151,11 @@ fastify.route({ url: '/', schema: { querystring: { - name: { type: 'string' }, - excitement: { type: 'integer' } + type: 'object', + properties: { + name: { type: 'string' }, + excitement: { type: 'integer' } + } }, response: { 200: { From 68d0ed21205a220ae5d246e08d9aa65b5c28c75f Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 20 Sep 2024 07:13:19 +0200 Subject: [PATCH 0763/1295] fix(docs): migration guide (#5701) Signed-off-by: Manuel Spigolon --- docs/Guides/Migration-Guide-V5.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index efe395fb065..7848bdb1ed3 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -432,16 +432,16 @@ We have removed the following HTTP methods from Fastify: - `TRACE` - `SEARCH` -It's now possible to add them back using the `acceptHTTPMethod` method. +It's now possible to add them back using the `addHttpMethod` method. ```js const fastify = Fastify() // add a new http method on top of the default ones: -fastify.acceptHTTPMethod('REBIND') +fastify.addHttpMethod('REBIND') // add a new HTTP method that accepts a body: -fastify.acceptHTTPMethod('REBIND', { hasBody: true }) +fastify.addHttpMethod('REBIND', { hasBody: true }) // reads the HTTP methods list: fastify.supportedMethods // returns a string array From 2695adacfc0963c82d2dd3f2d237f7d5ca7bfc14 Mon Sep 17 00:00:00 2001 From: Mauro Accornero Date: Fri, 20 Sep 2024 12:52:42 +0200 Subject: [PATCH 0764/1295] fix: add childLoggerFactory option to FastifyHttpOptions type (#5665) - add childLoggerFactory option to FastifyHttpOptions type - add test for childLoggerFactory option --- fastify.d.ts | 11 ++++++++++- test/childLoggerFactory.test.js | 29 +++++++++++++++++++++++++++++ test/types/fastify.test-d.ts | 12 ++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/fastify.d.ts b/fastify.d.ts index e24ed604fca..6c46fd9144b 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -14,7 +14,15 @@ import { FastifyContextConfig, FastifyReplyContext, FastifyRequestContext } from import { FastifyErrorCodes } from './types/errors' import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onListenAsyncHookHandler, onListenHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, RequestPayload } from './types/hooks' import { FastifyInstance, FastifyListenOptions, PrintRoutesOptions } from './types/instance' -import { FastifyBaseLogger, FastifyLogFn, FastifyLoggerInstance, FastifyLoggerOptions, LogLevel, PinoLoggerOptions } from './types/logger' +import { + FastifyBaseLogger, + FastifyChildLoggerFactory, + FastifyLogFn, + FastifyLoggerInstance, + FastifyLoggerOptions, + LogLevel, + PinoLoggerOptions +} from './types/logger' import { FastifyPlugin, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions } from './types/plugin' import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './types/register' import { FastifyReply } from './types/reply' @@ -149,6 +157,7 @@ declare namespace fastify { * listener to error events emitted by client connections */ clientErrorHandler?: (error: ConnectionError, socket: Socket) => void, + childLoggerFactory?: FastifyChildLoggerFactory } /** diff --git a/test/childLoggerFactory.test.js b/test/childLoggerFactory.test.js index 38817c81920..8471a562501 100644 --- a/test/childLoggerFactory.test.js +++ b/test/childLoggerFactory.test.js @@ -31,6 +31,35 @@ test('Should accept a custom childLoggerFactory function', t => { }) }) +test('Should accept a custom childLoggerFactory function as option', t => { + t.plan(2) + + const fastify = Fastify({ + childLoggerFactory: function (logger, bindings, opts) { + t.ok(bindings.reqId) + t.ok(opts) + this.log.debug(bindings, 'created child logger') + return logger.child(bindings, opts) + } + }) + + fastify.get('/', (req, reply) => { + req.log.info('log message') + reply.send() + }) + + fastify.listen({ port: 0 }, err => { + t.error(err) + fastify.inject({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, res) => { + t.error(err) + fastify.close() + }) + }) +}) + test('req.log should be the instance returned by the factory', t => { t.plan(3) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index ed536325f81..4dc7ac74fac 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -21,6 +21,7 @@ import fastify, { RouteGenericInterface, SafePromiseLike } from '../../fastify' +import { Bindings, ChildLoggerOptions } from '../../types/logger' // FastifyInstance // http server @@ -219,6 +220,17 @@ expectAssignable(fastify({ } })) +expectAssignable(fastify({ + childLoggerFactory: function (this: FastifyInstance, logger: FastifyBaseLogger, bindings: Bindings, opts: ChildLoggerOptions, req: RawRequestDefaultExpression) { + expectType(logger) + expectType(bindings) + expectType(opts) + expectType(req) + expectAssignable(this) + return logger.child(bindings, opts) + } +})) + // Thenable expectAssignable>(fastify({ return503OnClosing: true })) fastify().then(fastifyInstance => expectAssignable(fastifyInstance)) From 7ddced69c45c294450ab12fe7f92f0d969fe86d7 Mon Sep 17 00:00:00 2001 From: _sss <66111030+inyourtime@users.noreply.github.com> Date: Sat, 21 Sep 2024 23:36:27 +0700 Subject: [PATCH 0765/1295] docs: correct errors fragment (#5705) --- docs/Reference/Errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 6543f1e622b..34f1621b2e6 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -5,7 +5,7 @@ **Table of contents** - [Errors](#errors) - - [Error Handling In Node.js](#error-handling-in-node.js) + - [Error Handling In Node.js](#error-handling-in-nodejs) - [Uncaught Errors](#uncaught-errors) - [Catching Errors In Promises](#catching-errors-in-promises) - [Errors In Fastify](#errors-in-fastify) From b1b9a75cc08c00c27bae286ef85f71f259892176 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 24 Sep 2024 16:54:35 +0100 Subject: [PATCH 0766/1295] docs(guides/ecosystem): remove archived core plugins (#5712) Signed-off-by: Frazer Smith --- docs/Guides/Ecosystem.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 3453a425512..81e4ed99f1a 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -12,8 +12,6 @@ section. [accepts](https://www.npmjs.com/package/accepts) in your request object. - [`@fastify/accepts-serializer`](https://github.com/fastify/fastify-accepts-serializer) to serialize to output according to the `Accept` header. -- [`@fastify/any-schema`](https://github.com/fastify/any-schema-you-like) Save - multiple schemas and decide which one to use to serialize the payload. - [`@fastify/auth`](https://github.com/fastify/fastify-auth) Run multiple auth functions in Fastify. - [`@fastify/autoload`](https://github.com/fastify/fastify-autoload) Require all @@ -43,10 +41,7 @@ section. [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) protection to Fastify. - [`@fastify/diagnostics-channel`](https://github.com/fastify/fastify-diagnostics-channel) - Plugin to deal with `diagnostics_channel` on Fastify -- [`@fastify/early-hints`](https://github.com/fastify/fastify-early-hints) Plugin - to add HTTP 103 feature based on [RFC - 8297](https://datatracker.ietf.org/doc/html/rfc8297). + Plugin to deal with `diagnostics_channel` on Fastify. - [`@fastify/elasticsearch`](https://github.com/fastify/fastify-elasticsearch) Plugin to share the same ES client. - [`@fastify/env`](https://github.com/fastify/fastify-env) Load and check @@ -124,8 +119,6 @@ section. HTTP errors and assertions, but also more request and reply methods. - [`@fastify/session`](https://github.com/fastify/session) a session plugin for Fastify. -- [`@fastify/soap-client`](https://github.com/fastify/fastify-soap-client) a SOAP - client plugin for Fastify. - [`@fastify/static`](https://github.com/fastify/fastify-static) Plugin for serving static files as fast as possible. - [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) Plugin for From eb3deddee77fea2a0d99988a72738a09c9d4ef4e Mon Sep 17 00:00:00 2001 From: Dorgeles N'ZI <8544562+dorgelesnzi@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:03:39 +0200 Subject: [PATCH 0767/1295] fix: make shallow copy of fastify options before mutations (#5706) --- fastify.js | 7 ++++--- test/internals/initialConfig.test.js | 11 +++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index 722aa358284..6bcf7824301 100644 --- a/fastify.js +++ b/fastify.js @@ -97,10 +97,11 @@ function defaultBuildPrettyMeta (route) { */ function fastify (options) { // Options validations - options = options || {} - - if (typeof options !== 'object') { + if (options && typeof options !== 'object') { throw new FST_ERR_OPTIONS_NOT_OBJ() + } else { + // Shallow copy options object to prevent mutations outside of this function + options = Object.assign({}, options) } if (options.querystringParser && typeof options.querystringParser !== 'function') { diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index 1455cc01039..f54e296bb02 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -367,3 +367,14 @@ test('pluginTimeout should be parsed correctly', t => { t.equal(withInvalidTimeout.initialConfig.pluginTimeout, 10000) t.end() }) + +test('Should not mutate the options object outside Fastify', async t => { + const options = Object.freeze({}) + + try { + Fastify(options) + t.pass() + } catch (error) { + t.fail(error.message) + } +}) From 38289e12e7f7e301df3576b503cf5df5865527b1 Mon Sep 17 00:00:00 2001 From: Erik N <382783+erkaperka@users.noreply.github.com> Date: Mon, 7 Oct 2024 08:22:27 +0200 Subject: [PATCH 0768/1295] docs(reply): update to clarify elapsedTime behaviour and remove link to removed getResponseTime method (#5728) --- docs/Reference/Reply.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 889488a6a9d..9e8396301e7 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -19,7 +19,6 @@ - [.removeTrailer(key)](#removetrailerkey) - [.redirect(dest, [code ,])](#redirectdest--code) - [.callNotFound()](#callnotfound) - - [.getResponseTime()](#getresponsetime) - [.type(contentType)](#typecontenttype) - [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus) - [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus) @@ -114,9 +113,6 @@ If not set via `reply.code`, the resulting `statusCode` will be `200`. Invokes the custom response time getter to calculate the amount of time passed since the request was received by Fastify. -Note that unless this function is called in the [`onResponse` -hook](./Hooks.md#onresponse) it will always return `0`. - ```js const milliseconds = reply.elapsedTime ``` From db7e3826b52fed5f0ee2660a2988dee33a95a970 Mon Sep 17 00:00:00 2001 From: Igor Shevchenko <39371503+bnzone@users.noreply.github.com> Date: Sun, 13 Oct 2024 03:28:28 -0500 Subject: [PATCH 0769/1295] chore(tests): uncomment server.decorate test case (#5736) --- test/types/instance.test-d.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 091970cb7f4..f41f92bb952 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -370,12 +370,11 @@ expectError(server.decorate('test', true)) expectError(server.decorate<(myNumber: number) => number>('test', function (myNumber: number): string { return '' })) -// TODO(mcollina): uncomment after https://github.com/tsdjs/tsd/pull/220 lands. -// expectError(server.decorate('test', { -// getter () { -// return true -// } -// })) +expectError(server.decorate('test', { + getter () { + return true + } +})) expectError(server.decorate('test', { setter (x) {} })) @@ -419,12 +418,11 @@ server.decorate('typedTestProperty') server.decorate('typedTestProperty', null, ['foo']) expectError(server.decorate('typedTestProperty', null)) expectError(server.decorate('typedTestProperty', 'foo')) -// TODO(mcollina): uncomment after https://github.com/tsdjs/tsd/pull/220 lands. -// expectError(server.decorate('typedTestProperty', { -// getter () { -// return 'foo' -// } -// })) +expectError(server.decorate('typedTestProperty', { + getter () { + return 'foo' + } +})) server.decorate('typedTestMethod', function (x) { expectType(x) expectType(this) From 025fa7ae284406c45a757dd1aebb5f5d5d5b3851 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Sun, 13 Oct 2024 07:56:31 -0400 Subject: [PATCH 0770/1295] chore: setup borp reporter for switch to node test (#5720) * chore: setup borp reporter for switch to node test * chore: update scripts and test-reporter * update scripts * fix * bump borp to v0.18.0 * pr feedback * pr feedback * fix file name to .borp.yaml --- .borp.yaml | 3 ++ lib/logger.js | 2 ++ package.json | 18 +++++------ test/same-shape.test.js | 10 +++--- test/test-reporter.mjs | 68 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 .borp.yaml create mode 100644 test/test-reporter.mjs diff --git a/.borp.yaml b/.borp.yaml new file mode 100644 index 00000000000..8d6f28179b5 --- /dev/null +++ b/.borp.yaml @@ -0,0 +1,3 @@ +files: + - 'test/**/*.test.js' + - 'test/**/*.test.mjs' diff --git a/lib/logger.js b/lib/logger.js index 75c2288e972..8643be6520f 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,10 +1,12 @@ 'use strict' +/* c8 ignore start */ /** * Code imported from `pino-http` * Repo: https://github.com/pinojs/pino-http * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE) */ +/* c8 ignore stop */ const nullLogger = require('abstract-logging') const pino = require('pino') diff --git a/package.json b/package.json index 09a8f63ea30..e776f6f7830 100644 --- a/package.json +++ b/package.json @@ -10,24 +10,23 @@ "benchmark": "concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"autocannon -c 100 -d 30 -p 10 localhost:3000/\"", "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", - "coverage": "npm run unit -- --coverage-report=html", - "coverage:ci": "tap --coverage-report=html --coverage-report=lcov --allow-incomplete-coverage", - "coverage:ci-check-coverage": "tap replay", + "coverage": "c8 --reporter html borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --lines 100 ", + "coverage:ci": "c8 --reporter lcov --reporter html borp --reporter=./test/test-reporter.mjs", + "coverage:ci-check-coverage": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --lines 100", "lint": "npm run lint:eslint", "lint:fix": "eslint --fix", "lint:markdown": "markdownlint-cli2", "lint:eslint": "eslint", - "prepublishOnly": "cross-env PREPUBLISH=true tap --allow-incomplete-coverage test/build/**.test.js && npm run test:validator:integrity", + "prepublishOnly": "cross-env PREPUBLISH=true borp --reporter=./test/test-reporter.mjs && npm run test:validator:integrity", "test": "npm run lint && npm run unit && npm run test:typescript", - "test:ci": "npm run unit -- --coverage-report=lcovonly && npm run test:typescript", + "test:ci": "npm run unit && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", "test:typescript": "tsc test/types/import.ts --noEmit && tsd", "test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse", - "unit": "tap", - "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml", - "unit:report": "tap --coverage-report=html --coverage-report=cobertura | tee out.tap", - "citgm": "tap --jobs=1 --timeout=120" + "unit": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage", + "unit:report": "c8 --reporter html borp --reporter=./test/test-reporter.mjs", + "citgm": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --concurrency=1" }, "repository": { "type": "git", @@ -163,6 +162,7 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "autocannon": "^7.15.0", + "borp": "^0.18.0", "branch-comparer": "^1.1.0", "concurrently": "^8.2.2", "cross-env": "^7.0.3", diff --git a/test/same-shape.test.js b/test/same-shape.test.js index 3157c71eaf7..f4162713339 100644 --- a/test/same-shape.test.js +++ b/test/same-shape.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const fastify = require('..') test('same shape on Request', async (t) => { @@ -21,7 +21,7 @@ test('same shape on Request', async (t) => { app.get('/', (req, reply) => { if (request) { - t.equal(%HaveSameMap(request, req), true) + t.assert.deepStrictEqual(request, req) } request = req @@ -51,7 +51,7 @@ test('same shape on Request when object', async (t) => { app.get('/', (req, reply) => { if (request) { - t.equal(%HaveSameMap(request, req), true) + t.assert.deepStrictEqual(request, req) } request = req @@ -81,7 +81,7 @@ test('same shape on Reply', async (t) => { app.get('/', (req, reply) => { if (_reply) { - t.equal(%HaveSameMap(_reply, reply), true) + t.assert.deepStrictEqual(_reply, reply) } _reply = reply @@ -111,7 +111,7 @@ test('same shape on Reply when object', async (t) => { app.get('/', (req, reply) => { if (_reply) { - t.equal(%HaveSameMap(_reply, reply), true) + t.assert.deepStrictEqual(_reply, reply) } _reply = reply diff --git a/test/test-reporter.mjs b/test/test-reporter.mjs new file mode 100644 index 00000000000..bf7087ec81f --- /dev/null +++ b/test/test-reporter.mjs @@ -0,0 +1,68 @@ +function colorize (type, text) { + if (type === 'pass') { + const blackText = `\x1b[30m${text}` + const boldblackText = `\x1b[1m${blackText}` + // Green background with black text + return `\x1b[42m${boldblackText}\x1b[0m` + } + + if (type === 'fail') { + const whiteText = `\x1b[37m${text}` + const boldWhiteText = `\x1b[1m${whiteText}` + // Red background with white text + return `\x1b[41m${boldWhiteText}\x1b[0m` + } + + return text +} + +function formatDiagnosticStr (str) { + return str.replace(/^(\w+)(\s*\d*)/i, (_, firstWord, rest) => { + return firstWord.charAt(0).toUpperCase() + firstWord.slice(1).toLowerCase() + ':' + rest + }) +} + +async function * reporter (source) { + const failed = new Set() + const diagnostics = new Set() + + for await (const event of source) { + switch (event.type) { + case 'test:pass': { + yield `${colorize('pass', 'PASSED')}: ${event.data.file || event.data.name}\n` + break + } + + case 'test:fail': { + failed.add(event.data.name || event.data.file) + yield `${colorize('fail', 'FAILED')}: ${event.data.file || event.data.name}\n` + break + } + + case 'test:diagnostic': { + diagnostics.add(`${formatDiagnosticStr(event.data.message)}\n`) + break + } + + default: { + yield '' + } + } + } + + if (failed.size > 0) { + yield `\n\n${colorize('fail', 'Failed tests:')}\n` + for (const file of failed) { + yield `${file}\n` + } + } + + yield '\n' + + for (const diagnostic of diagnostics) { + yield `${diagnostic}` + } + yield '\n' +} + +export default reporter From 153b7df279570e898690b6e8d8fccf29d30658e4 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Tue, 15 Oct 2024 09:05:21 -0400 Subject: [PATCH 0771/1295] chore: update Testing guide to use node:test (#5738) * chore: update Testing guide to use node:test * fix: liniting --- docs/Guides/Testing.md | 101 ++++++++++++++++++------------------ docs/Guides/Write-Plugin.md | 2 +- lib/logger.js | 2 - 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/docs/Guides/Testing.md b/docs/Guides/Testing.md index 7101ffd6430..1dfc59247f6 100644 --- a/docs/Guides/Testing.md +++ b/docs/Guides/Testing.md @@ -5,15 +5,15 @@ Testing is one of the most important parts of developing an application. Fastify is very flexible when it comes to testing and is compatible with most testing -frameworks (such as [Tap](https://www.npmjs.com/package/tap), which is used in -the examples below). +frameworks (such as [Node Test Runner](https://nodejs.org/api/test.html), +which is used in the examples below). ## Application Let's `cd` into a fresh directory called 'testing-example' and type `npm init -y` in our terminal. -Run `npm i fastify && npm i tap pino-pretty -D` +Run `npm i fastify && npm i pino-pretty -D` ### Separating concerns makes testing easy @@ -113,24 +113,25 @@ Now we can replace our `console.log` calls with actual tests! In your `package.json` change the "test" script to: -`"test": "tap --reporter=list --watch"` +`"test": "node --test --watch"` **app.test.js**: ```js 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const build = require('./app') test('requests the "/" route', async t => { + t.plan(1) const app = build() const response = await app.inject({ method: 'GET', url: '/' }) - t.equal(response.statusCode, 200, 'returns a status code of 200') + t.assert.strictEqual(response.statusCode, 200, 'returns a status code of 200') }) ``` @@ -214,26 +215,26 @@ module.exports = buildFastify **test.js** ```js -const tap = require('tap') +const { test } = require('node:test') const buildFastify = require('./app') -tap.test('GET `/` route', t => { +test('GET `/` route', t => { t.plan(4) const fastify = buildFastify() // At the end of your tests it is highly recommended to call `.close()` // to ensure that all connections to external services get closed. - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) fastify.inject({ method: 'GET', url: '/' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.same(response.json(), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(response.json(), { hello: 'world' }) }) }) ``` @@ -248,11 +249,11 @@ Uses **app.js** from the previous example. **test-listen.js** (testing with [`undici`](https://www.npmjs.com/package/undici)) ```js -const tap = require('tap') +const { test } = require('node:test') const { Client } = require('undici') const buildFastify = require('./app') -tap.test('should work with undici', async t => { +test('should work with undici', async t => { t.plan(2) const fastify = buildFastify() @@ -266,15 +267,15 @@ tap.test('should work with undici', async t => { } ) - t.teardown(() => { + t.after(() => { fastify.close() client.close() }) const response = await client.request({ method: 'GET', path: '/' }) - t.equal(await response.body.text(), '{"hello":"world"}') - t.equal(response.statusCode, 200) + t.assert.strictEqual(await response.body.text(), '{"hello":"world"}') + t.assert.strictEqual(response.statusCode, 200) }) ``` @@ -284,15 +285,15 @@ may be used without requiring any extra dependencies: **test-listen.js** ```js -const tap = require('tap') +const { test } = require('node:test') const buildFastify = require('./app') -tap.test('should work with fetch', async t => { +test('should work with fetch', async t => { t.plan(3) const fastify = buildFastify() - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) await fastify.listen() @@ -300,26 +301,27 @@ tap.test('should work with fetch', async t => { 'http://localhost:' + fastify.server.address().port ) - t.equal(response.status, 200) - t.equal( + t.assert.strictEqual(response.status, 200) + t.assert.strictEqual( response.headers.get('content-type'), 'application/json; charset=utf-8' ) - t.has(await response.json(), { hello: 'world' }) + const jsonResult = await response.json() + t.assert.strictEqual(jsonResult.hello, 'world') }) ``` **test-ready.js** (testing with [`SuperTest`](https://www.npmjs.com/package/supertest)) ```js -const tap = require('tap') +const { test } = require('node:test') const supertest = require('supertest') const buildFastify = require('./app') -tap.test('GET `/` route', async (t) => { +test('GET `/` route', async (t) => { const fastify = buildFastify() - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) await fastify.ready() @@ -327,21 +329,20 @@ tap.test('GET `/` route', async (t) => { .get('/') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') - t.same(response.body, { hello: 'world' }) + t.assert.deepStrictEqual(response.body, { hello: 'world' }) }) ``` -### How to inspect tap tests +### How to inspect node tests 1. Isolate your test by passing the `{only: true}` option ```javascript test('should ...', {only: true}, t => ...) ``` -2. Run `tap` using `npx` +2. Run `node --test` ```bash -> npx tap -O -T --node-arg=--inspect-brk test/ +> node --test --test-only --node-arg=--inspect-brk test/ ``` -- `-O` specifies to run tests with the `only` option enabled -- `-T` specifies not to timeout (while you're debugging) +- `--test-only` specifies to run tests with the `only` option enabled - `--node-arg=--inspect-brk` will launch the node debugger 3. In VS Code, create and launch a `Node.js: Attach` debug configuration. No modification should be necessary. @@ -355,7 +356,7 @@ Now you should be able to step through your test file (and the rest of Let's `cd` into a fresh directory called 'testing-plugin-example' and type `npm init -y` in our terminal. -Run `npm i fastify fastify-plugin && npm i tap -D` +Run `npm i fastify fastify-plugin` **plugin/myFirstPlugin.js**: @@ -376,10 +377,10 @@ A basic example of a Plugin. See [Plugin Guide](./Plugins-Guide.md) ```js const Fastify = require("fastify"); -const tap = require("tap"); +const { test } = require("node:test"); const myPlugin = require("../plugin/myFirstPlugin"); -tap.test("Test the Plugin Route", async t => { +test("Test the Plugin Route", async t => { // Create a mock fastify application to test the plugin const fastify = Fastify() @@ -412,18 +413,18 @@ Now we can replace our `console.log` calls with actual tests! In your `package.json` change the "test" script to: -`"test": "tap --reporter=list --watch"` +`"test": "node --test --watch"` -Create the tap test for the endpoint. +Create the test for the endpoint. **test/myFirstPlugin.test.js**: ```js const Fastify = require("fastify"); -const tap = require("tap"); +const { test } = require("node:test"); const myPlugin = require("../plugin/myFirstPlugin"); -tap.test("Test the Plugin Route", async t => { +test("Test the Plugin Route", async t => { // Specifies the number of test t.plan(2) @@ -440,8 +441,8 @@ tap.test("Test the Plugin Route", async t => { url: "/" }) - t.equal(fastifyResponse.statusCode, 200) - t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" }) + t.assert.strictEqual(fastifyResponse.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" }) }) ``` @@ -453,10 +454,10 @@ Test the ```.decorate()``` and ```.decorateRequest()```. ```js const Fastify = require("fastify"); -const tap = require("tap"); +const { test }= require("node:test"); const myPlugin = require("../plugin/myFirstPlugin"); -tap.test("Test the Plugin Route", async t => { +test("Test the Plugin Route", async t => { t.plan(5) const fastify = Fastify() @@ -464,9 +465,9 @@ tap.test("Test the Plugin Route", async t => { fastify.get("/", async (request, reply) => { // Testing the fastify decorators - t.not(request.helloRequest, null) - t.ok(request.helloRequest, "Hello World") - t.ok(fastify.helloInstance, "Hello Fastify Instance") + t.assert.ifError(request.helloRequest) + t.assert.ok(request.helloRequest, "Hello World") + t.assert.ok(fastify.helloInstance, "Hello Fastify Instance") return ({ message: request.helloRequest }) }) @@ -474,7 +475,7 @@ tap.test("Test the Plugin Route", async t => { method: "GET", url: "/" }) - t.equal(fastifyResponse.statusCode, 200) - t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" }) + t.assert.strictEqual(fastifyResponse.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" }) }) -``` \ No newline at end of file +``` diff --git a/docs/Guides/Write-Plugin.md b/docs/Guides/Write-Plugin.md index eae024b62ed..cc5b70266be 100644 --- a/docs/Guides/Write-Plugin.md +++ b/docs/Guides/Write-Plugin.md @@ -60,7 +60,7 @@ A plugin without tests will not be accepted to the ecosystem list. A lack of tests does not inspire trust nor guarantee that the code will continue to work among different versions of its dependencies. -We do not enforce any testing library. We use [`tap`](https://www.node-tap.org/) +We do not enforce any testing library. We use [`node:test`](https://nodejs.org/api/test.html) since it offers out-of-the-box parallel testing and code coverage, but it is up to you to choose your library of preference. We highly recommend you read the [Plugin Testing](./Testing.md#plugins) to diff --git a/lib/logger.js b/lib/logger.js index 8643be6520f..75c2288e972 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,12 +1,10 @@ 'use strict' -/* c8 ignore start */ /** * Code imported from `pino-http` * Repo: https://github.com/pinojs/pino-http * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE) */ -/* c8 ignore stop */ const nullLogger = require('abstract-logging') const pino = require('pino') From 3affc3410129224c42aa732211ab8ed335e92d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Tue, 15 Oct 2024 22:47:36 +0200 Subject: [PATCH 0772/1295] fix: point this to parent at onRegister (#5675) --- lib/pluginOverride.js | 2 +- test/hooks.test.js | 12 ++++++------ test/types/hooks.test-d.ts | 3 ++- types/hooks.d.ts | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/pluginOverride.js b/lib/pluginOverride.js index 0b2adfbbf25..17f8c0893b0 100644 --- a/lib/pluginOverride.js +++ b/lib/pluginOverride.js @@ -66,7 +66,7 @@ module.exports = function override (old, fn, opts) { instance[kFourOhFour].arrange404(instance) } - for (const hook of instance[kHooks].onRegister) hook.call(this, instance, opts) + for (const hook of instance[kHooks].onRegister) hook.call(old, instance, opts) return instance } diff --git a/test/hooks.test.js b/test/hooks.test.js index c00acc6839a..2e92b753f3a 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -3072,11 +3072,11 @@ test('preSerialization hooks should support encapsulation', t => { }) test('onRegister hook should be called / 1', t => { - t.plan(4) + t.plan(5) const fastify = Fastify() - fastify.addHook('onRegister', (instance, opts, done) => { - // duck typing for the win! + fastify.addHook('onRegister', function (instance, opts, done) { + t.ok(this.addHook) t.ok(instance.addHook) t.same(opts, pluginOpts) t.notOk(done) @@ -3091,11 +3091,11 @@ test('onRegister hook should be called / 1', t => { }) test('onRegister hook should be called / 2', t => { - t.plan(4) + t.plan(7) const fastify = Fastify() - fastify.addHook('onRegister', instance => { - // duck typing for the win! + fastify.addHook('onRegister', function (instance) { + t.ok(this.addHook) t.ok(instance.addHook) }) diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 542dfcaae09..72491fb4cc0 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -131,7 +131,8 @@ server.addHook('onRoute', function (opts) { expectType(opts) }) -server.addHook('onRegister', (instance, opts) => { +server.addHook('onRegister', function (instance, opts) { + expectType(this) expectType(instance) expectType(opts) }) diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 43744f117eb..7b939698a32 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -710,6 +710,7 @@ export interface onRegisterHookHandler< Options extends FastifyPluginOptions = FastifyPluginOptions > { ( + this: FastifyInstance, instance: FastifyInstance, opts: RegisterOptions & Options ): Promise | void; From c1789dfd5febd996c4307e1ad7ff97e421e0e1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 16 Oct 2024 16:57:27 +0200 Subject: [PATCH 0773/1295] fix: make content-type case-insensitive (#5742) * fix: make content-type case-insensitive * cache missed case insensitive content types * move test to getParser --- lib/contentTypeParser.js | 10 +++++++--- test/content-parser.test.js | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 99ca5f0399b..ab2254808a8 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -109,16 +109,20 @@ ContentTypeParser.prototype.existingParser = function (contentType) { ContentTypeParser.prototype.getParser = function (contentType) { let parser = this.customParsers.get(contentType) if (parser !== undefined) return parser - parser = this.cache.get(contentType) if (parser !== undefined) return parser + const caseInsensitiveContentType = contentType.toLowerCase() // eslint-disable-next-line no-var for (var i = 0; i !== this.parserList.length; ++i) { const parserListItem = this.parserList[i] if ( - contentType.slice(0, parserListItem.length) === parserListItem && - (contentType.length === parserListItem.length || contentType.charCodeAt(parserListItem.length) === 59 /* `;` */ || contentType.charCodeAt(parserListItem.length) === 32 /* ` ` */) + caseInsensitiveContentType.slice(0, parserListItem.length) === parserListItem && + ( + caseInsensitiveContentType.length === parserListItem.length || + caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ || + caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */ + ) ) { parser = this.customParsers.get(parserListItem) this.cache.set(contentType, parser) diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 88356c7741f..228be10818c 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -79,6 +79,23 @@ test('getParser', t => { }) test('should return matching parser with caching /2', t => { + t.plan(8) + + const fastify = Fastify() + + fastify.addContentTypeParser('text/html', first) + + t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 0) + t.equal(fastify[keys.kContentTypeParser].getParser('text/HTML').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 1) + t.equal(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 2) + t.equal(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first) + t.equal(fastify[keys.kContentTypeParser].cache.size, 2) + }) + + test('should return matching parser with caching /3', t => { t.plan(6) const fastify = Fastify() From 343b69b98f7cc4cc2a549d934f4739375f9887bf Mon Sep 17 00:00:00 2001 From: Bruno Mollo <48107910+BrunoMollo@users.noreply.github.com> Date: Thu, 17 Oct 2024 05:18:01 -0300 Subject: [PATCH 0774/1295] docs: changed var for let and const (#5745) --- docs/Reference/ContentTypeParser.md | 8 ++++---- docs/Reference/Reply.md | 2 +- docs/Reference/Routes.md | 8 ++++---- docs/Reference/Server.md | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index d86a2e190a1..c33c50d9df9 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -53,7 +53,7 @@ fastify.addContentTypeParser(['text/xml', 'application/xml'], function (request, // Async is also supported in Node versions >= 8.0.0 fastify.addContentTypeParser('application/jsoff', async function (request, payload) { - var res = await jsoffParserAsync(payload) + const res = await jsoffParserAsync(payload) return res }) @@ -181,7 +181,7 @@ limit is exceeded the custom parser will not be invoked. ```js fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { try { - var json = JSON.parse(body) + const json = JSON.parse(body) done(null, json) } catch (err) { err.statusCode = 400 @@ -206,7 +206,7 @@ There are some cases where you need to catch all requests regardless of their content type. With Fastify, you can just use the `'*'` content type. ```js fastify.addContentTypeParser('*', function (request, payload, done) { - var data = '' + let data = '' payload.on('data', chunk => { data += chunk }) payload.on('end', () => { done(null, data) @@ -266,7 +266,7 @@ only on those that don't have a specific one, you should call the fastify.removeAllContentTypeParsers() fastify.addContentTypeParser('*', function (request, payload, done) { - var data = '' + const data = '' payload.on('data', chunk => { data += chunk }) payload.on('end', () => { done(null, data) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 9e8396301e7..31e6c0f19da 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -876,7 +876,7 @@ API: ```js fastify.setErrorHandler(function (error, request, reply) { request.log.warn(error) - var statusCode = error.statusCode >= 400 ? error.statusCode : 500 + const statusCode = error.statusCode >= 400 ? error.statusCode : 500 reply .code(statusCode) .type('text/plain') diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 1e6496e04c7..9a030f20c79 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -334,8 +334,8 @@ fastify.post('/name::verb') // will be interpreted as /name:verb Are you an `async/await` user? We have you covered! ```js fastify.get('/', options, async function (request, reply) { - var data = await getData() - var processed = await processData(data) + const data = await getData() + const processed = await processData(data) return processed }) ``` @@ -349,8 +349,8 @@ handler or you will introduce a race condition in certain situations. ```js fastify.get('/', options, async function (request, reply) { - var data = await getData() - var processed = await processData(data) + const data = await getData() + const processed = await processData(data) return reply.send(processed) }) ``` diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 6fcb3b4daf4..8376ae88cbd 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1559,7 +1559,7 @@ is set. It can be accessed using `fastify.errorHandler` and it logs the error with respect to its `statusCode`. ```js -var statusCode = error.statusCode +const statusCode = error.statusCode if (statusCode >= 500) { log.error(error) } else if (statusCode >= 400) { From ef1c7184099a6ab869ebf17b651b2d6517a6f75d Mon Sep 17 00:00:00 2001 From: Ran Toledo Date: Thu, 17 Oct 2024 13:21:41 +0300 Subject: [PATCH 0775/1295] chore: Migrate to node:test (#5714) * migrate-sample-tests-to-node-test * replaced-deep-equal-to-deep-strict-equal-and-not-equal-to-not-strict-equal * remove-new-promise-use-done-instead-use-await-when-multiple-injects * added-diagnostic-channel-and-bundler-tests * added-esm-tests * add-http-methods-tests * added-https-tests * added-done-in-bundler * changed-new-test-in-internals-initialconfig --------- Co-authored-by: Toledo --- test/500s.test.js | 135 +- test/bundler/esbuild/bundler-test.js | 19 +- test/bundler/webpack/bundler-test.js | 19 +- test/diagnostics-channel/404.test.js | 30 +- .../async-delay-request.test.js | 50 +- .../diagnostics-channel/async-request.test.js | 48 +- .../error-before-handler.test.js | 9 +- .../diagnostics-channel/error-request.test.js | 38 +- test/diagnostics-channel/error-status.test.js | 16 +- test/diagnostics-channel/init.test.js | 13 +- .../sync-delay-request.test.js | 32 +- .../sync-request-reply.test.js | 32 +- test/diagnostics-channel/sync-request.test.js | 38 +- test/esm/errorCodes.test.mjs | 10 +- test/esm/esm.test.mjs | 6 +- test/esm/named-exports.mjs | 6 +- test/esm/other.mjs | 4 +- test/http-methods/copy.test.js | 46 +- test/http-methods/custom-http-methods.test.js | 45 +- test/http-methods/get.test.js | 181 +-- test/http-methods/head.test.js | 117 +- test/http-methods/lock.test.js | 41 +- test/http-methods/mkcalendar.test.js | 58 +- test/http-methods/mkcol.test.js | 20 +- test/http-methods/move.test.js | 22 +- test/http-methods/propfind.test.js | 59 +- test/http-methods/proppatch.test.js | 40 +- test/http-methods/report.test.js | 59 +- test/http-methods/search.test.js | 99 +- test/http-methods/trace.test.js | 7 +- test/http-methods/unlock.test.js | 20 +- test/https/custom-https-server.test.js | 13 +- test/https/https.test.js | 156 +-- test/internals/all.test.js | 19 +- test/internals/contentTypeParser.test.js | 11 +- test/internals/context.test.js | 20 +- test/internals/decorator.test.js | 41 +- test/internals/errors.test.js | 854 ++++++------ test/internals/handleRequest.test.js | 95 +- test/internals/hookRunner.test.js | 199 ++- test/internals/hooks.test.js | 66 +- test/internals/initialConfig.test.js | 165 +-- test/internals/logger.test.js | 51 +- test/internals/plugin.test.js | 35 +- test/internals/reply-serialize.test.js | 204 +-- test/internals/reply.test.js | 1205 +++++++++-------- test/internals/reqIdGenFactory.test.js | 62 +- test/internals/request-validate.test.js | 439 +++--- test/internals/request.test.js | 196 ++- test/internals/server.test.js | 27 +- test/internals/validation.test.js | 71 +- 51 files changed, 2664 insertions(+), 2584 deletions(-) diff --git a/test/500s.test.js b/test/500s.test.js index 3c915dba0a2..e6ee7e631d0 100644 --- a/test/500s.test.js +++ b/test/500s.test.js @@ -1,15 +1,14 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const symbols = require('../lib/symbols.js') -test('default 500', t => { +test('default 500', (t, done) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { reply.send(new Error('kaboom')) @@ -19,30 +18,31 @@ test('default 500', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', message: 'kaboom', statusCode: 500 }) + done() }) }) -test('custom 500', t => { +test('custom 500', (t, done) => { t.plan(6) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { reply.send(new Error('kaboom')) }) fastify.setErrorHandler(function (err, request, reply) { - t.type(request, 'object') - t.type(request, fastify[symbols.kRequest].parent) + t.assert.ok(typeof request === 'object') + t.assert.ok(request instanceof fastify[symbols.kRequest].parent) reply .code(500) .type('text/plain') @@ -53,18 +53,19 @@ test('custom 500', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal(res.headers['content-type'], 'text/plain') - t.same(res.payload.toString(), 'an error happened: kaboom') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(res.payload.toString(), 'an error happened: kaboom') + done() }) }) -test('encapsulated 500', t => { - t.plan(10) +test('encapsulated 500', async t => { + t.plan(8) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { reply.send(new Error('kaboom')) @@ -76,8 +77,8 @@ test('encapsulated 500', t => { }) f.setErrorHandler(function (err, request, reply) { - t.type(request, 'object') - t.type(request, fastify[symbols.kRequest].parent) + t.assert.ok(typeof request === 'object') + t.assert.ok(request instanceof fastify[symbols.kRequest].parent) reply .code(500) .type('text/plain') @@ -87,36 +88,38 @@ test('encapsulated 500', t => { done() }, { prefix: 'test' }) - fastify.inject({ - method: 'GET', - url: '/test' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal(res.headers['content-type'], 'text/plain') - t.same(res.payload.toString(), 'an error happened: kaboom') - }) + { + const response = await fastify.inject({ + method: 'GET', + url: '/test' + }) - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { + t.assert.strictEqual(response.statusCode, 500) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(response.payload.toString(), 'an error happened: kaboom') + } + + { + const response = await fastify.inject({ + method: 'GET', + url: '/' + }) + + t.assert.strictEqual(response.statusCode, 500) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Internal Server Error', message: 'kaboom', statusCode: 500 }) - }) + } }) -test('custom 500 with hooks', t => { +test('custom 500 with hooks', (t, done) => { t.plan(7) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { reply.send(new Error('kaboom')) @@ -130,15 +133,15 @@ test('custom 500 with hooks', t => { }) fastify.addHook('onSend', (req, res, payload, done) => { - t.ok('called', 'onSend') + t.assert.ok('called', 'onSend') done() }) fastify.addHook('onRequest', (req, res, done) => { - t.ok('called', 'onRequest') + t.assert.ok('called', 'onRequest') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called', 'onResponse') + t.assert.ok('called', 'onResponse') done() }) @@ -146,54 +149,59 @@ test('custom 500 with hooks', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal(res.headers['content-type'], 'text/plain') - t.same(res.payload.toString(), 'an error happened: kaboom') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(res.payload.toString(), 'an error happened: kaboom') + done() }) }) -test('cannot set errorHandler after binding', t => { +test('cannot set errorHandler after binding', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) try { fastify.setErrorHandler(() => { }) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) + } finally { + done() } }) }) -test('cannot set childLoggerFactory after binding', t => { +test('cannot set childLoggerFactory after binding', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) try { fastify.setChildLoggerFactory(() => { }) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) + } finally { + done() } }) }) -test('catch synchronous errors', t => { +test('catch synchronous errors', (t, done) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.setErrorHandler((_, req, reply) => { throw new Error('kaboom2') @@ -211,12 +219,13 @@ test('catch synchronous errors', t => { }, payload: JSON.stringify({ hello: 'world' }).substring(0, 5) }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { error: 'Internal Server Error', message: 'kaboom2', statusCode: 500 }) + done() }) }) diff --git a/test/bundler/esbuild/bundler-test.js b/test/bundler/esbuild/bundler-test.js index 12df3cd567c..8958694504c 100644 --- a/test/bundler/esbuild/bundler-test.js +++ b/test/bundler/esbuild/bundler-test.js @@ -1,31 +1,32 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const fastifySuccess = require('./dist/success') const fastifyFailPlugin = require('./dist/failPlugin') -test('Bundled package should work', (t) => { +test('Bundled package should work', (t, done) => { t.plan(4) fastifySuccess.ready((err) => { - t.error(err) + t.assert.ifError(err) fastifySuccess.inject( { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.same(res.json(), { hello: 'world' }) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + done() } ) }) }) -test('Bundled package should not work with bad plugin version', (t) => { +test('Bundled package should not work with bad plugin version', (t, done) => { t.plan(1) fastifyFailPlugin.ready((err) => { - t.match(err.message, /expected '9.x' fastify version/i) + t.assert.match(err.message, /expected '9.x' fastify version/i) + done() }) }) diff --git a/test/bundler/webpack/bundler-test.js b/test/bundler/webpack/bundler-test.js index 12df3cd567c..8958694504c 100644 --- a/test/bundler/webpack/bundler-test.js +++ b/test/bundler/webpack/bundler-test.js @@ -1,31 +1,32 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const fastifySuccess = require('./dist/success') const fastifyFailPlugin = require('./dist/failPlugin') -test('Bundled package should work', (t) => { +test('Bundled package should work', (t, done) => { t.plan(4) fastifySuccess.ready((err) => { - t.error(err) + t.assert.ifError(err) fastifySuccess.inject( { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.same(res.json(), { hello: 'world' }) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + done() } ) }) }) -test('Bundled package should not work with bad plugin version', (t) => { +test('Bundled package should not work with bad plugin version', (t, done) => { t.plan(1) fastifyFailPlugin.ready((err) => { - t.match(err.message, /expected '9.x' fastify version/i) + t.assert.match(err.message, /expected '9.x' fastify version/i) + done() }) }) diff --git a/test/diagnostics-channel/404.test.js b/test/diagnostics-channel/404.test.js index 2832dd71b1d..8eff7dc779b 100644 --- a/test/diagnostics-channel/404.test.js +++ b/test/diagnostics-channel/404.test.js @@ -1,35 +1,34 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel sync events fire in expected order', t => { +test('diagnostics channel sync events fire in expected order', (t, done) => { t.plan(9) let callOrder = 0 let firstEncounteredMessage diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { - t.equal(callOrder++, 0) + t.assert.strictEqual(callOrder++, 0) firstEncounteredMessage = msg - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) }) diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(callOrder++, 1) - t.equal(msg, firstEncounteredMessage) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.fail('should not trigger error channel') + t.assert.fail('should not trigger error channel') }) const fastify = Fastify() @@ -42,16 +41,17 @@ test('diagnostics channel sync events fire in expected order', t => { }) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) + if (err) t.assert.ifError(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) }) diff --git a/test/diagnostics-channel/async-delay-request.test.js b/test/diagnostics-channel/async-delay-request.test.js index bf64f1bc6ef..6b3303bc1ad 100644 --- a/test/diagnostics-channel/async-delay-request.test.js +++ b/test/diagnostics-channel/async-delay-request.test.js @@ -1,50 +1,49 @@ 'use strict' -const t = require('tap') const diagnostics = require('node:diagnostics_channel') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../..') const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel async events fire in expected order', t => { +test('diagnostics channel async events fire in expected order', (t, done) => { t.plan(19) let callOrder = 0 let firstEncounteredMessage diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { - t.equal(callOrder++, 0) + t.assert.strictEqual(callOrder++, 0) firstEncounteredMessage = msg - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) }) diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { - t.equal(callOrder++, 1) - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(msg, firstEncounteredMessage) - t.equal(msg.async, true) + t.assert.strictEqual(callOrder++, 1) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) + t.assert.strictEqual(msg.async, true) }) diagnostics.subscribe('tracing:fastify.request.handler:asyncStart', (msg) => { - t.equal(callOrder++, 2) - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(msg, firstEncounteredMessage) + t.assert.strictEqual(callOrder++, 2) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:asyncEnd', (msg) => { - t.equal(callOrder++, 3) - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(msg, firstEncounteredMessage) + t.assert.strictEqual(callOrder++, 3) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.fail('should not trigger error channel') + t.assert.fail('should not trigger error channel') }) const fastify = Fastify() @@ -58,17 +57,18 @@ test('diagnostics channel async events fire in expected order', t => { }) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) + if (err) t.assert.ifError(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) diff --git a/test/diagnostics-channel/async-request.test.js b/test/diagnostics-channel/async-request.test.js index 86e2b5cf6f6..368c3e6614c 100644 --- a/test/diagnostics-channel/async-request.test.js +++ b/test/diagnostics-channel/async-request.test.js @@ -1,49 +1,48 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel async events fire in expected order', t => { +test('diagnostics channel async events fire in expected order', (t, done) => { t.plan(18) let callOrder = 0 let firstEncounteredMessage diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { - t.equal(callOrder++, 0) + t.assert.strictEqual(callOrder++, 0) firstEncounteredMessage = msg - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) }) diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { - t.equal(callOrder++, 1) - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(msg, firstEncounteredMessage) + t.assert.strictEqual(callOrder++, 1) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:asyncStart', (msg) => { - t.equal(callOrder++, 2) - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(msg, firstEncounteredMessage) + t.assert.strictEqual(callOrder++, 2) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:asyncEnd', (msg) => { - t.equal(callOrder++, 3) - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(msg, firstEncounteredMessage) + t.assert.strictEqual(callOrder++, 3) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.fail('should not trigger error channel') + t.assert.fail('should not trigger error channel') }) const fastify = Fastify() @@ -56,17 +55,18 @@ test('diagnostics channel async events fire in expected order', t => { }) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) + if (err) t.assert.ifError(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) diff --git a/test/diagnostics-channel/error-before-handler.test.js b/test/diagnostics-channel/error-before-handler.test.js index 11b5287cd55..4d15c3dfdfd 100644 --- a/test/diagnostics-channel/error-before-handler.test.js +++ b/test/diagnostics-channel/error-before-handler.test.js @@ -1,8 +1,7 @@ 'use strict' -const t = require('tap') const diagnostics = require('node:diagnostics_channel') -const test = t.test +const { test } = require('node:test') require('../../lib/hooks').onSendHookRunner = function Stub () {} const Request = require('../../lib/request') const Reply = require('../../lib/reply') @@ -14,12 +13,12 @@ test('diagnostics channel handles an error before calling context handler', t => let callOrder = 0 diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { - t.equal(callOrder++, 0) + t.assert.strictEqual(callOrder++, 0) }) diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.equal(callOrder++, 1) - t.equal(msg.error.message, 'oh no') + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg.error.message, 'oh no') }) const error = new Error('oh no') diff --git a/test/diagnostics-channel/error-request.test.js b/test/diagnostics-channel/error-request.test.js index 8af11c543e7..ed035a6f57e 100644 --- a/test/diagnostics-channel/error-request.test.js +++ b/test/diagnostics-channel/error-request.test.js @@ -1,39 +1,38 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel events report on errors', t => { +test('diagnostics channel events report on errors', (t, done) => { t.plan(14) let callOrder = 0 let firstEncounteredMessage diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { - t.equal(callOrder++, 0) + t.assert.strictEqual(callOrder++, 0) firstEncounteredMessage = msg - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) }) diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(callOrder++, 2) - t.equal(msg, firstEncounteredMessage) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 2) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.ok(msg.error instanceof Error) - t.equal(callOrder++, 1) - t.equal(msg.error.message, 'borked') + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.ok(msg.error instanceof Error) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg.error.message, 'borked') }) const fastify = Fastify() @@ -46,16 +45,17 @@ test('diagnostics channel events report on errors', t => { }) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) + if (err) t.assert.ifError(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + done() }) }) }) diff --git a/test/diagnostics-channel/error-status.test.js b/test/diagnostics-channel/error-status.test.js index dbded479d95..e1776f8f418 100644 --- a/test/diagnostics-channel/error-status.test.js +++ b/test/diagnostics-channel/error-status.test.js @@ -1,20 +1,19 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const statusCodes = require('node:http').STATUS_CODES const diagnostics = require('node:diagnostics_channel') -test('Error.status property support', t => { +test('Error.status property support', (t, done) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const err = new Error('winter is coming') err.status = 418 diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.equal(msg.error.message, 'winter is coming') + t.assert.strictEqual(msg.error.message, 'winter is coming') }) fastify.get('/', () => { @@ -25,9 +24,9 @@ test('Error.status property support', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same( + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual( { error: statusCodes['418'], message: err.message, @@ -35,5 +34,6 @@ test('Error.status property support', t => { }, JSON.parse(res.payload) ) + done() }) }) diff --git a/test/diagnostics-channel/init.test.js b/test/diagnostics-channel/init.test.js index a6b5e8646ae..295cacc8297 100644 --- a/test/diagnostics-channel/init.test.js +++ b/test/diagnostics-channel/init.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const proxyquire = require('proxyquire') test('diagnostics_channel when present and subscribers', t => { @@ -11,11 +10,11 @@ test('diagnostics_channel when present and subscribers', t => { const diagnostics = { channel (name) { - t.equal(name, 'fastify.initialization') + t.assert.strictEqual(name, 'fastify.initialization') return { hasSubscribers: true, publish (event) { - t.ok(event.fastify) + t.assert.ok(event.fastify) fastifyInHook = event.fastify } } @@ -26,7 +25,7 @@ test('diagnostics_channel when present and subscribers', t => { const fastify = proxyquire('../../fastify', { 'node:diagnostics_channel': diagnostics })() - t.equal(fastifyInHook, fastify) + t.assert.strictEqual(fastifyInHook, fastify) }) test('diagnostics_channel when present and no subscribers', t => { @@ -34,11 +33,11 @@ test('diagnostics_channel when present and no subscribers', t => { const diagnostics = { channel (name) { - t.equal(name, 'fastify.initialization') + t.assert.strictEqual(name, 'fastify.initialization') return { hasSubscribers: false, publish () { - t.fail('publish should not be called') + t.assert.fail('publish should not be called') } } }, diff --git a/test/diagnostics-channel/sync-delay-request.test.js b/test/diagnostics-channel/sync-delay-request.test.js index 8a712b2820d..f9b9f6f5c7e 100644 --- a/test/diagnostics-channel/sync-delay-request.test.js +++ b/test/diagnostics-channel/sync-delay-request.test.js @@ -1,35 +1,34 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel sync events fire in expected order', t => { +test('diagnostics channel sync events fire in expected order', (t, done) => { t.plan(10) let callOrder = 0 let firstEncounteredMessage diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { - t.equal(callOrder++, 0) + t.assert.strictEqual(callOrder++, 0) firstEncounteredMessage = msg - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) }) diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(callOrder++, 1) - t.equal(msg, firstEncounteredMessage) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.fail('should not trigger error channel') + t.assert.fail('should not trigger error channel') }) const fastify = Fastify() @@ -42,17 +41,18 @@ test('diagnostics channel sync events fire in expected order', t => { }) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) + if (err) t.assert.ifError(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) diff --git a/test/diagnostics-channel/sync-request-reply.test.js b/test/diagnostics-channel/sync-request-reply.test.js index 02811e83cd6..61d5774a523 100644 --- a/test/diagnostics-channel/sync-request-reply.test.js +++ b/test/diagnostics-channel/sync-request-reply.test.js @@ -1,35 +1,34 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel sync events fire in expected order', t => { +test('diagnostics channel sync events fire in expected order', (t, done) => { t.plan(10) let callOrder = 0 let firstEncounteredMessage diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { - t.equal(callOrder++, 0) + t.assert.strictEqual(callOrder++, 0) firstEncounteredMessage = msg - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) }) diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(callOrder++, 1) - t.equal(msg, firstEncounteredMessage) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.fail('should not trigger error channel') + t.assert.fail('should not trigger error channel') }) const fastify = Fastify() @@ -42,17 +41,18 @@ test('diagnostics channel sync events fire in expected order', t => { }) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) + if (err) t.assert.ifError(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) diff --git a/test/diagnostics-channel/sync-request.test.js b/test/diagnostics-channel/sync-request.test.js index 7377228f54c..fed1d319488 100644 --- a/test/diagnostics-channel/sync-request.test.js +++ b/test/diagnostics-channel/sync-request.test.js @@ -1,38 +1,37 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const test = t.test const sget = require('simple-get').concat const Fastify = require('../..') const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel sync events fire in expected order', t => { +test('diagnostics channel sync events fire in expected order', (t, done) => { t.plan(13) let callOrder = 0 let firstEncounteredMessage diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { - t.equal(callOrder++, 0) + t.assert.strictEqual(callOrder++, 0) firstEncounteredMessage = msg - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.ok(msg.route) - t.equal(msg.route.url, '/:id') - t.equal(msg.route.method, 'GET') + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.ok(msg.route) + t.assert.strictEqual(msg.route.url, '/:id') + t.assert.strictEqual(msg.route.method, 'GET') }) diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { - t.ok(msg.request instanceof Request) - t.ok(msg.reply instanceof Reply) - t.equal(callOrder++, 1) - t.equal(msg, firstEncounteredMessage) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg, firstEncounteredMessage) }) diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { - t.fail('should not trigger error channel') + t.assert.fail('should not trigger error channel') }) const fastify = Fastify() @@ -45,17 +44,18 @@ test('diagnostics channel sync events fire in expected order', t => { }) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) + if (err) t.assert.ifError(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/7' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) diff --git a/test/esm/errorCodes.test.mjs b/test/esm/errorCodes.test.mjs index 6dc0d266f5d..cfeaf261355 100644 --- a/test/esm/errorCodes.test.mjs +++ b/test/esm/errorCodes.test.mjs @@ -1,10 +1,10 @@ import { errorCodes } from '../../fastify.js' -import t from 'tap' +import { test } from 'node:test' -t.test('errorCodes in ESM', async t => { +test('errorCodes in ESM', async t => { // test a custom fastify error using errorCodes with ESM const customError = errorCodes.FST_ERR_VALIDATION('custom error message') - t.ok(typeof customError !== 'undefined') - t.ok(customError instanceof errorCodes.FST_ERR_VALIDATION) - t.equal(customError.message, 'custom error message') + t.assert.ok(typeof customError !== 'undefined') + t.assert.ok(customError instanceof errorCodes.FST_ERR_VALIDATION) + t.assert.strictEqual(customError.message, 'custom error message') }) diff --git a/test/esm/esm.test.mjs b/test/esm/esm.test.mjs index aab4b614acf..49c6bfebf20 100644 --- a/test/esm/esm.test.mjs +++ b/test/esm/esm.test.mjs @@ -1,7 +1,7 @@ -import t from 'tap' +import { test } from 'node:test' import Fastify from '../../fastify.js' -t.test('esm support', async t => { +test('esm support', async t => { const fastify = Fastify() fastify.register(import('./plugin.mjs'), { foo: 'bar' }) @@ -9,5 +9,5 @@ t.test('esm support', async t => { await fastify.ready() - t.equal(fastify.foo, 'bar') + t.assert.strictEqual(fastify.foo, 'bar') }) diff --git a/test/esm/named-exports.mjs b/test/esm/named-exports.mjs index 854e2e14505..84323e92402 100644 --- a/test/esm/named-exports.mjs +++ b/test/esm/named-exports.mjs @@ -1,8 +1,8 @@ -import t from 'tap' +import { test } from 'node:test' import { fastify } from '../../fastify.js' // This test is executed in index.test.js -t.test('named exports support', async t => { +test('named exports support', async t => { const app = fastify() app.register(import('./plugin.mjs'), { foo: 'bar' }) @@ -10,5 +10,5 @@ t.test('named exports support', async t => { await app.ready() - t.equal(app.foo, 'bar') + t.assert.strictEqual(app.foo, 'bar') }) diff --git a/test/esm/other.mjs b/test/esm/other.mjs index 26aa7fed777..448064cbaf4 100644 --- a/test/esm/other.mjs +++ b/test/esm/other.mjs @@ -1,8 +1,8 @@ // Imported in both index.test.js & esm.test.mjs -import t from 'tap' +import { strictEqual } from 'node:assert' async function other (fastify, opts) { - t.equal(fastify.foo, 'bar') + strictEqual(fastify.foo, 'bar') } export default other diff --git a/test/http-methods/copy.test.js b/test/http-methods/copy.test.js index 2865f3c4f4c..3dbb1b41afd 100644 --- a/test/http-methods/copy.test.js +++ b/test/http-methods/copy.test.js @@ -1,33 +1,17 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('COPY') -test('can be created - copy', t => { - t.plan(1) - try { - fastify.route({ - method: 'COPY', - url: '*', - handler: function (req, reply) { - reply.code(204).send() - } - }) - t.pass() - } catch (e) { - t.fail() - } -}) +test('can be created - copy', (t, done) => { + t.plan(4) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => { fastify.close() }) - test('request - copy', t => { - t.plan(2) sget({ url: `http://localhost:${fastify.server.address().port}/test.txt`, method: 'COPY', @@ -35,8 +19,22 @@ fastify.listen({ port: 0 }, err => { Destination: '/test2.txt' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 204) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 204) + done() }) }) + + try { + fastify.route({ + method: 'COPY', + url: '*', + handler: function (req, reply) { + reply.code(204).send() + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } }) diff --git a/test/http-methods/custom-http-methods.test.js b/test/http-methods/custom-http-methods.test.js index fe27d7c1da0..61feed4e318 100644 --- a/test/http-methods/custom-http-methods.test.js +++ b/test/http-methods/custom-http-methods.test.js @@ -1,7 +1,7 @@ 'use strict' const http = require('node:http') -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('../../fastify') function addEcho (fastify, method) { @@ -14,12 +14,12 @@ function addEcho (fastify, method) { }) } -test('missing method from http client', t => { +test('missing method from http client', (t, done) => { t.plan(2) const fastify = Fastify() fastify.listen({ port: 3000 }, (err) => { - t.error(err) + t.assert.ifError(err) const port = fastify.server.address().port const req = http.request({ @@ -27,26 +27,27 @@ test('missing method from http client', t => { method: 'REBIND', path: '/' }, (res) => { - t.equal(res.statusCode, 404) + t.assert.strictEqual(res.statusCode, 404) fastify.close() + done() }) req.end() }) }) -test('addHttpMethod increase the supported HTTP methods supported', t => { +test('addHttpMethod increase the supported HTTP methods supported', (t, done) => { t.plan(8) const app = Fastify() - t.throws(() => { addEcho(app, 'REBIND') }, /REBIND method is not supported./) - t.notOk(app.supportedMethods.includes('REBIND')) - t.notOk(app.rebind) + t.assert.throws(() => { addEcho(app, 'REBIND') }, /REBIND method is not supported./) + t.assert.ok(!app.supportedMethods.includes('REBIND')) + t.assert.ok(!app.rebind) app.addHttpMethod('REBIND') - t.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') - t.ok(app.supportedMethods.includes('REBIND')) - t.ok(app.rebind) + t.assert.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + t.assert.ok(app.supportedMethods.includes('REBIND')) + t.assert.ok(app.rebind) app.rebind('/foo', () => 'hello') @@ -54,8 +55,9 @@ test('addHttpMethod increase the supported HTTP methods supported', t => { method: 'REBIND', url: '/foo' }, (err, response) => { - t.error(err) - t.equal(response.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(response.payload, 'hello') + done() }) }) @@ -63,12 +65,12 @@ test('addHttpMethod adds a new custom method without body', t => { t.plan(3) const app = Fastify() - t.throws(() => { addEcho(app, 'REBIND') }, /REBIND method is not supported./) + t.assert.throws(() => { addEcho(app, 'REBIND') }, /REBIND method is not supported./) app.addHttpMethod('REBIND') - t.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + t.assert.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') - t.throws(() => { + t.assert.throws(() => { app.route({ url: '/', method: 'REBIND', @@ -87,25 +89,26 @@ test('addHttpMethod adds a new custom method without body', t => { }, /Body validation schema for REBIND:\/ route is not supported!/) }) -test('addHttpMethod adds a new custom method with body', t => { +test('addHttpMethod adds a new custom method with body', (t, done) => { t.plan(3) const app = Fastify() app.addHttpMethod('REBIND', { hasBody: true }) - t.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + t.assert.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') app.inject({ method: 'REBIND', url: '/', payload: { hello: 'world' } }, (err, response) => { - t.error(err) - t.same(response.json(), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(response.json(), { hello: 'world' }) + done() }) }) test('addHttpMethod rejects fake http method', t => { t.plan(1) const fastify = Fastify() - t.throws(() => { fastify.addHttpMethod('FOOO') }, /Provided method is invalid!/) + t.assert.throws(() => { fastify.addHttpMethod('FOOO') }, /Provided method is invalid!/) }) diff --git a/test/http-methods/get.test.js b/test/http-methods/get.test.js index 49a04f72564..ae02f6aeb91 100644 --- a/test/http-methods/get.test.js +++ b/test/http-methods/get.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../fastify')() @@ -96,9 +95,9 @@ test('shorthand - get', t => { fastify.get('/', schema, function (req, reply) { reply.code(200).send({ hello: 'world' }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -108,9 +107,9 @@ test('shorthand - get (return null)', t => { fastify.get('/null', nullSchema, function (req, reply) { reply.code(200).send(null) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -120,9 +119,9 @@ test('shorthand - get params', t => { fastify.get('/params/:foo/:test', paramsSchema, function (req, reply) { reply.code(200).send(req.params) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -132,9 +131,9 @@ test('shorthand - get, querystring schema', t => { fastify.get('/query', querySchema, function (req, reply) { reply.code(200).send(req.query) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -144,9 +143,9 @@ test('shorthand - get, headers schema', t => { fastify.get('/headers', headersSchema, function (req, reply) { reply.code(200).send(req.headers) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -156,9 +155,9 @@ test('missing schema - get', t => { fastify.get('/missing', function (req, reply) { reply.code(200).send({ hello: 'world' }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -173,9 +172,9 @@ test('custom serializer - get', t => { fastify.get('/custom-serializer', numberSchema, function (req, reply) { reply.code(200).serializer(customSerializer).send({ hello: 'world' }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -185,9 +184,9 @@ test('empty response', t => { fastify.get('/empty', function (req, reply) { reply.code(200).send() }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -197,9 +196,9 @@ test('send a falsy boolean', t => { fastify.get('/boolean', function (req, reply) { reply.code(200).send(false) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -209,60 +208,64 @@ test('shorthand - get, set port', t => { fastify.get('/port', headersSchema, function (req, reply) { reply.code(200).send({ port: req.port }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) +test('get test', async t => { + t.after(() => { fastify.close() }) - test('shorthand - request get', t => { + await fastify.listen({ port: 0 }) + + await t.test('shorthand - request get', (t, done) => { t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) - test('shorthand - request get params schema', t => { + await t.test('shorthand - request get params schema', (t, done) => { t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/params/world/123' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { foo: 'world', test: 123 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) + done() }) }) - test('shorthand - request get params schema error', t => { + await t.test('shorthand - request get params schema error', (t, done) => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/params/world/string' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'params/test must be integer', statusCode: 400 }) + done() }) }) - test('shorthand - request get headers schema', t => { + await t.test('shorthand - request get headers schema', (t, done) => { t.plan(4) sget({ method: 'GET', @@ -273,14 +276,15 @@ fastify.listen({ port: 0 }, err => { json: true, url: 'http://localhost:' + fastify.server.address().port + '/headers' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body['x-test'], 1) - t.equal(body['y-test'], 3) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body['x-test'], 1) + t.assert.strictEqual(body['y-test'], 3) + done() }) }) - test('shorthand - request get headers schema error', t => { + await t.test('shorthand - request get headers schema error', (t, done) => { t.plan(3) sget({ method: 'GET', @@ -289,111 +293,119 @@ fastify.listen({ port: 0 }, err => { }, url: 'http://localhost:' + fastify.server.address().port + '/headers' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'headers/x-test must be number', statusCode: 400 }) + done() }) }) - test('shorthand - request get querystring schema', t => { + await t.test('shorthand - request get querystring schema', (t, done) => { t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/query?hello=123' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 123 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) + done() }) }) - test('shorthand - request get querystring schema error', t => { + await t.test('shorthand - request get querystring schema error', (t, done) => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/query?hello=world' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'querystring/hello must be integer', statusCode: 400 }) + done() }) }) - test('shorthand - request get missing schema', t => { + await t.test('shorthand - request get missing schema', (t, done) => { t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/missing' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) - test('shorthand - custom serializer', t => { + await t.test('shorthand - custom serializer', (t, done) => { t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/custom-serializer' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) - test('shorthand - empty response', t => { + await t.test('shorthand - empty response', (t, done) => { t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/empty' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '0') - t.same(body.toString(), '') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '0') + t.assert.deepStrictEqual(body.toString(), '') + done() }) }) - test('shorthand - send a falsy boolean', t => { + await t.test('shorthand - send a falsy boolean', (t, done) => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/boolean' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'false') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), 'false') + done() }) }) - test('shorthand - send null value', t => { + await t.test('shorthand - send null value', (t, done) => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/null' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'null') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), 'null') + done() }) }) - test('shorthand - request get headers - test fall back port', t => { + await t.test('shorthand - request get headers - test fall back port', (t, done) => { t.plan(3) sget({ method: 'GET', @@ -403,9 +415,10 @@ fastify.listen({ port: 0 }, err => { json: true, url: 'http://localhost:' + fastify.server.address().port + '/port' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.port, null) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.port, null) + done() }) }) }) diff --git a/test/http-methods/head.test.js b/test/http-methods/head.test.js index 0e1a25a9139..f9d39b58bfc 100644 --- a/test/http-methods/head.test.js +++ b/test/http-methods/head.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../fastify')() @@ -50,9 +49,9 @@ test('shorthand - head', t => { fastify.head('/', schema, function (req, reply) { reply.code(200).send(null) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -68,9 +67,9 @@ test('shorthand - custom head', t => { reply.code(200).send(null) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -86,9 +85,9 @@ test('shorthand - custom head with constraints', t => { reply.code(200).send(null) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -103,9 +102,9 @@ test('shorthand - should not reset a head route', t => { reply.code(200).send(null) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -121,10 +120,10 @@ test('shorthand - should set get and head route in the same api call', t => { } }) - t.pass() + t.assert.ok(true) } catch (e) { console.log(e) - t.fail() + t.assert.fail() } }) @@ -134,9 +133,9 @@ test('shorthand - head params', t => { fastify.head('/params/:foo/:test', paramsSchema, function (req, reply) { reply.send(null) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -146,10 +145,10 @@ test('shorthand - head, querystring schema', t => { fastify.head('/query', querySchema, function (req, reply) { reply.code(200).send(null) }) - t.pass() + t.assert.ok(true) } catch (e) { console.log(e) - t.fail() + t.assert.fail() } }) @@ -159,96 +158,103 @@ test('missing schema - head', t => { fastify.head('/missing', function (req, reply) { reply.code(200).send(null) }) - t.pass() + t.assert.ok(true) } catch (e) { console.log(e) - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) +test('head test', async t => { + t.after(() => { fastify.close() }) + await fastify.listen({ port: 0 }) - test('shorthand - request head', t => { + await t.test('shorthand - request head', (t, done) => { t.plan(2) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - test('shorthand - request head params schema', t => { + await t.test('shorthand - request head params schema', (t, done) => { t.plan(2) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port + '/params/world/123' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - test('shorthand - request head params schema error', t => { + await t.test('shorthand - request head params schema error', (t, done) => { t.plan(2) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port + '/params/world/string' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + done() }) }) - test('shorthand - request head querystring schema', t => { + await t.test('shorthand - request head querystring schema', (t, done) => { t.plan(2) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port + '/query?hello=123' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - test('shorthand - request head querystring schema error', t => { + await t.test('shorthand - request head querystring schema error', (t, done) => { t.plan(2) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port + '/query?hello=world' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + done() }) }) - test('shorthand - request head missing schema', t => { + await t.test('shorthand - request head missing schema', (t, done) => { t.plan(2) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port + '/missing' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - test('shorthand - request head custom head', t => { + await t.test('shorthand - request head custom head', (t, done) => { t.plan(3) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port + '/proxy/test' }, (err, response) => { - t.error(err) - t.equal(response.headers['x-foo'], 'bar') - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['x-foo'], 'bar') + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - test('shorthand - request head custom head with constraints', t => { + await t.test('shorthand - request head custom head with constraints', (t, done) => { t.plan(3) sget({ method: 'HEAD', @@ -257,32 +263,35 @@ fastify.listen({ port: 0 }, err => { version: '1.0.0' } }, (err, response) => { - t.error(err) - t.equal(response.headers['x-foo'], 'bar') - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['x-foo'], 'bar') + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - test('shorthand - should not reset a head route', t => { + await t.test('shorthand - should not reset a head route', (t, done) => { t.plan(2) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port + '/query1' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - test('shorthand - should set get and head route in the same api call', t => { + await t.test('shorthand - should set get and head route in the same api call', (t, done) => { t.plan(3) sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port + '/query4' }, (err, response) => { - t.error(err) - t.equal(response.headers['x-foo'], 'bar') - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['x-foo'], 'bar') + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) diff --git a/test/http-methods/lock.test.js b/test/http-methods/lock.test.js index 2a06b1b11ea..dad5c486670 100644 --- a/test/http-methods/lock.test.js +++ b/test/http-methods/lock.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('LOCK', { hasBody: true }) @@ -51,21 +50,20 @@ test('can be created - lock', t => { ) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { +test('lock test', async t => { + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - test('request with body - lock', t => { + await t.test('request with body - lock', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, @@ -73,35 +71,38 @@ fastify.listen({ port: 0 }, err => { body: bodySample, method: 'LOCK' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) - test('request with body and no content type (415 error) - lock', t => { + await t.test('request with body and no content type (415 error) - lock', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, body: bodySample, method: 'LOCK' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) - test('request without body - lock', t => { + await t.test('request without body - lock', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, headers: { 'content-type': 'text/plain' }, method: 'LOCK' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) }) diff --git a/test/http-methods/mkcalendar.test.js b/test/http-methods/mkcalendar.test.js index 510fee98ddd..91780a707fa 100644 --- a/test/http-methods/mkcalendar.test.js +++ b/test/http-methods/mkcalendar.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('MKCALENDAR', { hasBody: true }) @@ -72,19 +71,19 @@ test('can be created - mkcalendar', (t) => { `) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.teardown(() => { +test('mkcalendar test', async t => { + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - test('request - mkcalendar', (t) => { + await t.test('request - mkcalendar', (t, done) => { t.plan(3) sget( { @@ -92,14 +91,15 @@ fastify.listen({ port: 0 }, (err) => { method: 'MKCALENDAR' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) - test('request with other path - mkcalendar', (t) => { + await t.test('request with other path - mkcalendar', (t, done) => { t.plan(3) sget( { @@ -107,16 +107,17 @@ fastify.listen({ port: 0 }, (err) => { method: 'MKCALENDAR' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - test('request with body - mkcalendar', (t) => { + await t.test('request with body - mkcalendar', (t, done) => { t.plan(3) sget( { @@ -126,14 +127,15 @@ fastify.listen({ port: 0 }, (err) => { method: 'MKCALENDAR' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) - test('request with body and no content type (415 error) - mkcalendar', (t) => { + await t.test('request with body and no content type (415 error) - mkcalendar', (t, done) => { t.plan(3) sget( { @@ -142,14 +144,15 @@ fastify.listen({ port: 0 }, (err) => { method: 'MKCALENDAR' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) - test('request without body - mkcalendar', (t) => { + await t.test('request without body - mkcalendar', (t, done) => { t.plan(3) sget( { @@ -157,9 +160,10 @@ fastify.listen({ port: 0 }, (err) => { method: 'MKCALENDAR' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) diff --git a/test/http-methods/mkcol.test.js b/test/http-methods/mkcol.test.js index 4c703051368..449671c48b5 100644 --- a/test/http-methods/mkcol.test.js +++ b/test/http-methods/mkcol.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../')() fastify.addHttpMethod('MKCOL') @@ -16,24 +15,25 @@ test('can be created - mkcol', t => { reply.code(201).send() } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) +test('mkcol test', async t => { + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - test('request - mkcol', t => { + await t.test('request - mkcol', (t, done) => { t.plan(2) sget({ url: `http://localhost:${fastify.server.address().port}/test/`, method: 'MKCOL' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 201) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 201) + done() }) }) }) diff --git a/test/http-methods/move.test.js b/test/http-methods/move.test.js index 8a0fec0aa25..4f9bb561a40 100644 --- a/test/http-methods/move.test.js +++ b/test/http-methods/move.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../')() fastify.addHttpMethod('MOVE') @@ -19,17 +18,17 @@ test('shorthand - move', t => { .send() } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) +test('move test', async t => { + await fastify.listen({ port: 0 }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) - test('request - move', t => { + await t.test('request - move', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test.txt`, @@ -38,9 +37,10 @@ fastify.listen({ port: 0 }, err => { Destination: '/test2.txt' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 201) - t.equal(response.headers.location, '/test2.txt') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 201) + t.assert.strictEqual(response.headers.location, '/test2.txt') + done() }) }) }) diff --git a/test/http-methods/propfind.test.js b/test/http-methods/propfind.test.js index 20cf57ea63f..43ff6e7c9ab 100644 --- a/test/http-methods/propfind.test.js +++ b/test/http-methods/propfind.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../')() fastify.addHttpMethod('PROPFIND', { hasBody: true }) @@ -62,45 +61,48 @@ test('can be created - propfind', t => { ) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { +test('propfind test', async t => { + await fastify.listen({ port: 0 }) + + t.after(() => { fastify.close() }) - test('request - propfind', t => { + await t.test('request - propfind', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/`, method: 'PROPFIND' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) - test('request with other path - propfind', t => { + await t.test('request with other path - propfind', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test`, method: 'PROPFIND' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - test('request with body - propfind', t => { + await t.test('request with body - propfind', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test`, @@ -108,34 +110,37 @@ fastify.listen({ port: 0 }, err => { body: bodySample, method: 'PROPFIND' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) - test('request with body and no content type (415 error) - propfind', t => { + await t.test('request with body and no content type (415 error) - propfind', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test`, body: bodySample, method: 'PROPFIND' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) - test('request without body - propfind', t => { + await t.test('request without body - propfind', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test`, method: 'PROPFIND' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) }) diff --git a/test/http-methods/proppatch.test.js b/test/http-methods/proppatch.test.js index 4f81b0c08e0..0f4417b2535 100644 --- a/test/http-methods/proppatch.test.js +++ b/test/http-methods/proppatch.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../')() fastify.addHttpMethod('PROPPATCH', { hasBody: true }) @@ -56,19 +55,19 @@ test('shorthand - proppatch', t => { ) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) +test('proppatch test', async t => { + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - test('request with body - proppatch', t => { + await t.test('request with body - proppatch', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, @@ -76,34 +75,37 @@ fastify.listen({ port: 0 }, err => { body: bodySample, method: 'PROPPATCH' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) - test('request with body and no content type (415 error) - proppatch', t => { + await t.test('request with body and no content type (415 error) - proppatch', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, body: bodySample, method: 'PROPPATCH' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) - test('request without body - proppatch', t => { + await t.test('request without body - proppatch', (t, done) => { t.plan(3) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, method: 'PROPPATCH' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() }) }) }) diff --git a/test/http-methods/report.test.js b/test/http-methods/report.test.js index 39ddab77535..78847c4cbab 100644 --- a/test/http-methods/report.test.js +++ b/test/http-methods/report.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('REPORT', { hasBody: true }) @@ -68,19 +67,20 @@ test('can be created - report', (t) => { `) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.teardown(() => { +test('report test', async t => { + await fastify.listen({ port: 0 }) + + t.after(() => { fastify.close() }) - test('request - report', (t) => { + await t.test('request - report', (t, done) => { t.plan(3) sget( { @@ -88,14 +88,15 @@ fastify.listen({ port: 0 }, (err) => { method: 'REPORT' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) - test('request with other path - report', (t) => { + await t.test('request with other path - report', (t, done) => { t.plan(3) sget( { @@ -103,16 +104,17 @@ fastify.listen({ port: 0 }, (err) => { method: 'REPORT' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - test('request with body - report', (t) => { + await t.test('request with body - report', (t, done) => { t.plan(3) sget( { @@ -122,14 +124,15 @@ fastify.listen({ port: 0 }, (err) => { method: 'REPORT' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) - test('request with body and no content type (415 error) - report', (t) => { + await t.test('request with body and no content type (415 error) - report', (t, done) => { t.plan(3) sget( { @@ -138,14 +141,15 @@ fastify.listen({ port: 0 }, (err) => { method: 'REPORT' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) - test('request without body - report', (t) => { + await t.test('request without body - report', (t, done) => { t.plan(3) sget( { @@ -153,9 +157,10 @@ fastify.listen({ port: 0 }, (err) => { method: 'REPORT' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 207) - t.equal(response.headers['content-length'], '' + body.length) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 207) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + done() } ) }) diff --git a/test/http-methods/search.test.js b/test/http-methods/search.test.js index ff237327b94..43fcd442d45 100644 --- a/test/http-methods/search.test.js +++ b/test/http-methods/search.test.js @@ -1,8 +1,7 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const sget = require('simple-get').concat -const test = t.test const fastify = require('../../fastify')() fastify.addHttpMethod('SEARCH', { hasBody: true }) @@ -69,9 +68,9 @@ test('search', t => { reply.code(200).send({ hello: 'world' }) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -86,9 +85,9 @@ test('search, params schema', t => { reply.code(200).send(request.params) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -103,9 +102,9 @@ test('search, querystring schema', t => { reply.code(200).send(request.query) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -120,92 +119,96 @@ test('search, body schema', t => { reply.code(200).send(request.body) } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - +test('search test', async t => { + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) const url = `http://localhost:${fastify.server.address().port}` - test('request - search', t => { + await t.test('request - search', (t, done) => { t.plan(4) sget({ method: 'SEARCH', url }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) - test('request search params schema', t => { + await t.test('request search params schema', (t, done) => { t.plan(4) sget({ method: 'SEARCH', url: `${url}/params/world/123` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { foo: 'world', test: 123 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) + done() }) }) - test('request search params schema error', t => { + await t.test('request search params schema error', (t, done) => { t.plan(3) sget({ method: 'SEARCH', url: `${url}/params/world/string` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'params/test must be integer', statusCode: 400 }) + done() }) }) - test('request search querystring schema', t => { + await t.test('request search querystring schema', (t, done) => { t.plan(4) sget({ method: 'SEARCH', url: `${url}/query?hello=123` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 123 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) + done() }) }) - test('request search querystring schema error', t => { + await t.test('request search querystring schema error', (t, done) => { t.plan(3) sget({ method: 'SEARCH', url: `${url}/query?hello=world` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'querystring/hello must be integer', statusCode: 400 }) + done() }) }) - test('request search body schema', t => { + await t.test('request search body schema', (t, done) => { t.plan(4) const replyBody = { foo: 'bar', test: 5 } sget({ @@ -214,14 +217,15 @@ fastify.listen({ port: 0 }, err => { body: JSON.stringify(replyBody), headers: { 'content-type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), replyBody) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), replyBody) + done() }) }) - test('request search body schema error', t => { + await t.test('request search body schema error', (t, done) => { t.plan(4) sget({ method: 'SEARCH', @@ -229,15 +233,16 @@ fastify.listen({ port: 0 }, err => { body: JSON.stringify({ foo: 'bar', test: 'test' }), headers: { 'content-type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'body/test must be integer', statusCode: 400 }) + done() }) }) }) diff --git a/test/http-methods/trace.test.js b/test/http-methods/trace.test.js index f1503ba6f5e..07f869c2675 100644 --- a/test/http-methods/trace.test.js +++ b/test/http-methods/trace.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const fastify = require('../../fastify')() fastify.addHttpMethod('TRACE') @@ -15,8 +14,8 @@ test('shorthand - trace', t => { reply.code(200).send('TRACE OK') } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) diff --git a/test/http-methods/unlock.test.js b/test/http-methods/unlock.test.js index 909c55089e6..0da02a838ea 100644 --- a/test/http-methods/unlock.test.js +++ b/test/http-methods/unlock.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('UNLOCK') @@ -16,17 +15,17 @@ test('can be created - unlock', t => { reply.code(204).send() } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) +test('unlock test', async t => { + await fastify.listen({ port: 0 }) - test('request - unlock', t => { + t.after(() => { fastify.close() }) + await t.test('request - unlock', (t, done) => { t.plan(2) sget({ url: `http://localhost:${fastify.server.address().port}/test/a.txt`, @@ -35,8 +34,9 @@ fastify.listen({ port: 0 }, err => { 'Lock-Token': 'urn:uuid:a515cfa4-5da4-22e1-f5b5-00a0451e6bf7' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 204) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 204) + done() }) }) }) diff --git a/test/https/custom-https-server.test.js b/test/https/custom-https-server.test.js index d236589a00e..d1aee450063 100644 --- a/test/https/custom-https-server.test.js +++ b/test/https/custom-https-server.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const https = require('node:https') const dns = require('node:dns').promises @@ -18,7 +17,7 @@ async function setup () { const fastify = Fastify({ serverFactory: (handler, opts) => { - t.ok(opts.serverFactory, 'it is called once for localhost') + t.assert.ok(opts.serverFactory, 'it is called once for localhost') const options = { key: global.context.key, @@ -34,10 +33,10 @@ async function setup () { } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.get('/', (req, reply) => { - t.ok(req.raw.custom) + t.assert.ok(req.raw.custom) reply.send({ hello: 'world' }) }) @@ -52,8 +51,8 @@ async function setup () { if (err) { return reject(err) } - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) resolve() }) }) diff --git a/test/https/https.test.js b/test/https/https.test.js index ceaea513d27..2c33ab55081 100644 --- a/test/https/https.test.js +++ b/test/https/https.test.js @@ -1,15 +1,14 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../..') const { buildCertificate } = require('../build-certificate') -t.before(buildCertificate) +test.before(buildCertificate) -test('https', (t) => { - t.plan(4) +test('https', async (t) => { + t.plan(3) let fastify try { @@ -19,9 +18,9 @@ test('https', (t) => { cert: global.context.cert } }) - t.pass('Key/cert successfully loaded') + t.assert.ok('Key/cert successfully loaded') } catch (e) { - t.fail('Key/cert loading failed', e) + t.assert.fail('Key/cert loading failed', e) } fastify.get('/', function (req, reply) { @@ -32,51 +31,52 @@ test('https', (t) => { reply.code(200).send({ proto: req.protocol }) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + await fastify.listen({ port: 0 }) - t.test('https get request', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - }) + t.after(() => { fastify.close() }) + + await t.test('https get request', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'https://localhost:' + fastify.server.address().port, + rejectUnauthorized: false + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) + }) - t.test('https get request without trust proxy - protocol', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port + '/proto', - rejectUnauthorized: false - }, (err, response, body) => { - t.error(err) - t.same(JSON.parse(body), { proto: 'https' }) - }) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port + '/proto', - rejectUnauthorized: false, - headers: { - 'x-forwarded-proto': 'lorem' - } - }, (err, response, body) => { - t.error(err) - t.same(JSON.parse(body), { proto: 'https' }) - }) + await t.test('https get request without trust proxy - protocol', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'https://localhost:' + fastify.server.address().port + '/proto', + rejectUnauthorized: false + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body), { proto: 'https' }) + }) + sget({ + method: 'GET', + url: 'https://localhost:' + fastify.server.address().port + '/proto', + rejectUnauthorized: false, + headers: { + 'x-forwarded-proto': 'lorem' + } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body), { proto: 'https' }) + done() }) }) }) -test('https - headers', (t) => { - t.plan(4) +test('https - headers', async (t) => { + t.plan(3) let fastify try { fastify = Fastify({ @@ -85,49 +85,49 @@ test('https - headers', (t) => { cert: global.context.cert } }) - t.pass('Key/cert successfully loaded') + t.assert.ok('Key/cert successfully loaded') } catch (e) { - t.fail('Key/cert loading failed', e) + t.assert.fail('Key/cert loading failed', e) } fastify.get('/', function (req, reply) { reply.code(200).send({ hello: 'world', hostname: req.hostname, port: req.port }) }) - t.teardown(async () => { await fastify.close() }) + t.after(async () => { await fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.error(err) + await fastify.listen({ port: 0 }) - t.test('https get request', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - const parsedBody = JSON.parse(body) - t.equal(parsedBody.hostname, 'localhost') - t.equal(parsedBody.port, fastify.server.address().port) - }) + await t.test('https get request', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'https://localhost:' + fastify.server.address().port, + rejectUnauthorized: false + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + const parsedBody = JSON.parse(body) + t.assert.strictEqual(parsedBody.hostname, 'localhost') + t.assert.strictEqual(parsedBody.port, fastify.server.address().port) + done() }) - t.test('https get request - test port fall back', t => { - t.plan(3) - sget({ - method: 'GET', - headers: { - host: 'example.com' - }, - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - const parsedBody = JSON.parse(body) - t.equal(parsedBody.port, null) - }) + }) + await t.test('https get request - test port fall back', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + headers: { + host: 'example.com' + }, + url: 'https://localhost:' + fastify.server.address().port, + rejectUnauthorized: false + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + const parsedBody = JSON.parse(body) + t.assert.strictEqual(parsedBody.port, null) + done() }) }) }) diff --git a/test/internals/all.test.js b/test/internals/all.test.js index 3fc4b100f6d..14d3370145a 100644 --- a/test/internals/all.test.js +++ b/test/internals/all.test.js @@ -1,10 +1,9 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') -test('fastify.all should add all the methods to the same url', t => { +test('fastify.all should add all the methods to the same url', async t => { const fastify = Fastify() const requirePayload = [ @@ -14,15 +13,15 @@ test('fastify.all should add all the methods to the same url', t => { ] const supportedMethods = fastify.supportedMethods - t.plan(supportedMethods.length * 2) + t.plan(supportedMethods.length) fastify.all('/', (req, reply) => { reply.send({ method: req.raw.method }) }) - supportedMethods.forEach(injectRequest) + await Promise.all(supportedMethods.map(async method => injectRequest(method))) - function injectRequest (method) { + async function injectRequest (method) { const options = { url: '/', method @@ -32,10 +31,8 @@ test('fastify.all should add all the methods to the same url', t => { options.payload = { hello: 'world' } } - fastify.inject(options, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.same(payload, { method }) - }) + const res = await fastify.inject(options) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { method }) } }) diff --git a/test/internals/contentTypeParser.test.js b/test/internals/contentTypeParser.test.js index a7a0956fbce..a6fc8a2cd3d 100644 --- a/test/internals/contentTypeParser.test.js +++ b/test/internals/contentTypeParser.test.js @@ -1,8 +1,7 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const proxyquire = require('proxyquire') -const test = t.test const { Readable } = require('node:stream') const { kTestInternals, kRouteContext } = require('../../lib/symbols') const Request = require('../../lib/request') @@ -17,8 +16,8 @@ test('rawBody function', t => { asString: true, asBuffer: false, fn (req, bodyInString, done) { - t.equal(bodyInString, body.toString()) - t.equal(typeof done, 'function') + t.assert.strictEqual(bodyInString, body.toString()) + t.assert.strictEqual(typeof done, 'function') return { then (cb) { cb() @@ -70,8 +69,8 @@ test('Should support Webpack and faux modules', t => { asString: true, asBuffer: false, fn (req, bodyInString, done) { - t.equal(bodyInString, body.toString()) - t.equal(typeof done, 'function') + t.assert.strictEqual(bodyInString, body.toString()) + t.assert.strictEqual(typeof done, 'function') return { then (cb) { cb() diff --git a/test/internals/context.test.js b/test/internals/context.test.js index 71c7cd40236..1bab2bf3762 100644 --- a/test/internals/context.test.js +++ b/test/internals/context.test.js @@ -1,23 +1,23 @@ 'use strict' -const { test } = require('tap') - +const { test } = require('node:test') const { kRouteContext } = require('../../lib/symbols') const Context = require('../../lib/context') const Fastify = require('../..') -test('context', context => { +test('context', async context => { context.plan(1) - context.test('Should not contain undefined as key prop', async t => { + await context.test('Should not contain undefined as key prop', async t => { + t.plan(4) const app = Fastify() app.get('/', (req, reply) => { - t.type(req[kRouteContext], Context) - t.type(reply[kRouteContext], Context) - t.notOk('undefined' in reply[kRouteContext]) - t.notOk('undefined' in req[kRouteContext]) + t.assert.ok(req[kRouteContext] instanceof Context) + t.assert.ok(reply[kRouteContext] instanceof Context) + t.assert.ok(!('undefined' in reply[kRouteContext])) + t.assert.ok(!('undefined' in req[kRouteContext])) reply.send('hello world!') }) @@ -25,9 +25,7 @@ test('context', context => { try { await app.inject('/') } catch (e) { - t.fail(e) + t.assert.fail(e) } - - t.plan(4) }) }) diff --git a/test/internals/decorator.test.js b/test/internals/decorator.test.js index 95585b6ed80..c83f2c926ae 100644 --- a/test/internals/decorator.test.js +++ b/test/internals/decorator.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const decorator = require('../../lib/decorate') const { kState @@ -22,7 +21,7 @@ test('decorate should add the given method to its instance', t => { const server = build() server.add('test', () => {}) - t.ok(server.test) + t.assert.ok(server.test) }) test('decorate is chainable', t => { @@ -44,15 +43,15 @@ test('decorate is chainable', t => { .add('test2', () => {}) .add('test3', () => {}) - t.ok(server.test1) - t.ok(server.test2) - t.ok(server.test3) + t.assert.ok(server.test1) + t.assert.ok(server.test2) + t.assert.ok(server.test3) }) test('checkExistence should check if a property is part of the given instance', t => { t.plan(1) const instance = { test: () => {} } - t.ok(decorator.exist(instance, 'test')) + t.assert.ok(decorator.exist(instance, 'test')) }) test('checkExistence should find the instance if not given', t => { @@ -71,7 +70,7 @@ test('checkExistence should find the instance if not given', t => { const server = build() server.add('test', () => {}) - t.ok(server.check('test')) + t.assert.ok(server.check('test')) }) test('checkExistence should check the prototype as well', t => { @@ -80,7 +79,7 @@ test('checkExistence should check the prototype as well', t => { Instance.prototype.test = () => {} const instance = new Instance() - t.ok(decorator.exist(instance, 'test')) + t.assert.ok(decorator.exist(instance, 'test')) }) test('checkDependencies should throw if a dependency is not present', t => { @@ -88,10 +87,10 @@ test('checkDependencies should throw if a dependency is not present', t => { const instance = {} try { decorator.dependencies(instance, 'foo', ['test']) - t.fail() + t.assert.fail() } catch (e) { - t.equal(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') - t.equal(e.message, 'The decorator is missing dependency \'test\'.') + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'test\'.') } }) @@ -112,10 +111,10 @@ test('decorate should internally call checkDependencies', t => { try { server.add('method', () => {}, ['test']) - t.fail() + t.assert.fail() } catch (e) { - t.equal(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') - t.equal(e.message, 'The decorator is missing dependency \'test\'.') + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'test\'.') } }) @@ -132,14 +131,14 @@ test('decorate should recognize getter/setter objects', t => { decorator.add.call(one, 'foo', { getter: () => this._a, setter: (val) => { - t.pass() + t.assert.ok(true) this._a = val } }) - t.equal(Object.hasOwn(one, 'foo'), true) - t.equal(one.foo, undefined) + t.assert.strictEqual(Object.hasOwn(one, 'foo'), true) + t.assert.strictEqual(one.foo, undefined) one.foo = 'a' - t.equal(one.foo, 'a') + t.assert.strictEqual(one.foo, 'a') // getter only const two = { @@ -152,6 +151,6 @@ test('decorate should recognize getter/setter objects', t => { decorator.add.call(two, 'foo', { getter: () => 'a getter' }) - t.equal(Object.hasOwn(two, 'foo'), true) - t.equal(two.foo, 'a getter') + t.assert.strictEqual(Object.hasOwn(two, 'foo'), true) + t.assert.strictEqual(two.foo, 'a getter') }) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 92365cc55dc..ed84a4fef26 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') @@ -14,7 +14,7 @@ test('should expose 83 errors', t => { counter++ } } - t.equal(counter, 83) + t.assert.strictEqual(counter, 83) }) test('ensure name and codes of Errors are identical', t => { @@ -22,7 +22,7 @@ test('ensure name and codes of Errors are identical', t => { const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { - t.equal(key, new errors[key]().code, key) + t.assert.strictEqual(key, new errors[key]().code, key) } } }) @@ -30,841 +30,841 @@ test('ensure name and codes of Errors are identical', t => { test('FST_ERR_NOT_FOUND', t => { t.plan(5) const error = new errors.FST_ERR_NOT_FOUND() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_NOT_FOUND') - t.equal(error.message, 'Not Found') - t.equal(error.statusCode, 404) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_NOT_FOUND') + t.assert.strictEqual(error.message, 'Not Found') + t.assert.strictEqual(error.statusCode, 404) + t.assert.ok(error instanceof Error) }) test('FST_ERR_OPTIONS_NOT_OBJ', t => { t.plan(5) const error = new errors.FST_ERR_OPTIONS_NOT_OBJ() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_OPTIONS_NOT_OBJ') - t.equal(error.message, 'Options must be an object') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_OPTIONS_NOT_OBJ') + t.assert.strictEqual(error.message, 'Options must be an object') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_QSP_NOT_FN', t => { t.plan(5) const error = new errors.FST_ERR_QSP_NOT_FN() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_QSP_NOT_FN') - t.equal(error.message, "querystringParser option should be a function, instead got '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_QSP_NOT_FN') + t.assert.strictEqual(error.message, "querystringParser option should be a function, instead got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN', t => { t.plan(5) const error = new errors.FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN') - t.equal(error.message, "schemaController.bucket option should be a function, instead got '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN') + t.assert.strictEqual(error.message, "schemaController.bucket option should be a function, instead got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN', t => { t.plan(5) const error = new errors.FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') - t.equal(error.message, "schemaErrorFormatter option should be a non async function. Instead got '%s'.") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + t.assert.strictEqual(error.message, "schemaErrorFormatter option should be a non async function. Instead got '%s'.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ', t => { t.plan(5) const error = new errors.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ') - t.equal(error.message, "ajv.customOptions option should be an object, instead got '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ') + t.assert.strictEqual(error.message, "ajv.customOptions option should be an object, instead got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR', t => { t.plan(5) const error = new errors.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR') - t.equal(error.message, "ajv.plugins option should be an array, instead got '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR') + t.assert.strictEqual(error.message, "ajv.plugins option should be an array, instead got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_CTP_ALREADY_PRESENT', t => { t.plan(5) const error = new errors.FST_ERR_CTP_ALREADY_PRESENT() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_ALREADY_PRESENT') - t.equal(error.message, "Content type parser '%s' already present.") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.assert.strictEqual(error.message, "Content type parser '%s' already present.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_CTP_INVALID_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_CTP_INVALID_TYPE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_INVALID_TYPE') - t.equal(error.message, 'The content type should be a string or a RegExp') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_TYPE') + t.assert.strictEqual(error.message, 'The content type should be a string or a RegExp') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_CTP_EMPTY_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_CTP_EMPTY_TYPE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_EMPTY_TYPE') - t.equal(error.message, 'The content type cannot be an empty string') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_EMPTY_TYPE') + t.assert.strictEqual(error.message, 'The content type cannot be an empty string') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_CTP_INVALID_HANDLER', t => { t.plan(5) const error = new errors.FST_ERR_CTP_INVALID_HANDLER() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_INVALID_HANDLER') - t.equal(error.message, 'The content type handler should be a function') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_HANDLER') + t.assert.strictEqual(error.message, 'The content type handler should be a function') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_CTP_INVALID_PARSE_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_CTP_INVALID_PARSE_TYPE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') - t.equal(error.message, "The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') + t.assert.strictEqual(error.message, "The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_CTP_BODY_TOO_LARGE', t => { t.plan(5) const error = new errors.FST_ERR_CTP_BODY_TOO_LARGE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_BODY_TOO_LARGE') - t.equal(error.message, 'Request body is too large') - t.equal(error.statusCode, 413) - t.ok(error instanceof RangeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_BODY_TOO_LARGE') + t.assert.strictEqual(error.message, 'Request body is too large') + t.assert.strictEqual(error.statusCode, 413) + t.assert.ok(error instanceof RangeError) }) test('FST_ERR_CTP_INVALID_MEDIA_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_CTP_INVALID_MEDIA_TYPE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') - t.equal(error.message, 'Unsupported Media Type: %s') - t.equal(error.statusCode, 415) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + t.assert.strictEqual(error.message, 'Unsupported Media Type: %s') + t.assert.strictEqual(error.statusCode, 415) + t.assert.ok(error instanceof Error) }) test('FST_ERR_CTP_INVALID_CONTENT_LENGTH', t => { t.plan(5) const error = new errors.FST_ERR_CTP_INVALID_CONTENT_LENGTH() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_INVALID_CONTENT_LENGTH') - t.equal(error.message, 'Request body size did not match Content-Length') - t.equal(error.statusCode, 400) - t.ok(error instanceof RangeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_CONTENT_LENGTH') + t.assert.strictEqual(error.message, 'Request body size did not match Content-Length') + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof RangeError) }) test('FST_ERR_CTP_EMPTY_JSON_BODY', t => { t.plan(5) const error = new errors.FST_ERR_CTP_EMPTY_JSON_BODY() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_EMPTY_JSON_BODY') - t.equal(error.message, "Body cannot be empty when content-type is set to 'application/json'") - t.equal(error.statusCode, 400) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_EMPTY_JSON_BODY') + t.assert.strictEqual(error.message, "Body cannot be empty when content-type is set to 'application/json'") + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) }) test('FST_ERR_CTP_INSTANCE_ALREADY_STARTED', t => { t.plan(5) const error = new errors.FST_ERR_CTP_INSTANCE_ALREADY_STARTED() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED') - t.equal(error.message, 'Cannot call "%s" when fastify instance is already started!') - t.equal(error.statusCode, 400) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED') + t.assert.strictEqual(error.message, 'Cannot call "%s" when fastify instance is already started!') + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) }) test('FST_ERR_DEC_ALREADY_PRESENT', t => { t.plan(5) const error = new errors.FST_ERR_DEC_ALREADY_PRESENT() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_DEC_ALREADY_PRESENT') - t.equal(error.message, "The decorator '%s' has already been added!") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_ALREADY_PRESENT') + t.assert.strictEqual(error.message, "The decorator '%s' has already been added!") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_DEC_DEPENDENCY_INVALID_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_DEC_DEPENDENCY_INVALID_TYPE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE') - t.equal(error.message, "The dependencies of decorator '%s' must be of type Array.") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE') + t.assert.strictEqual(error.message, "The dependencies of decorator '%s' must be of type Array.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_DEC_MISSING_DEPENDENCY', t => { t.plan(5) const error = new errors.FST_ERR_DEC_MISSING_DEPENDENCY() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') - t.equal(error.message, "The decorator is missing dependency '%s'.") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(error.message, "The decorator is missing dependency '%s'.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_DEC_AFTER_START', t => { t.plan(5) const error = new errors.FST_ERR_DEC_AFTER_START() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_DEC_AFTER_START') - t.equal(error.message, "The decorator '%s' has been added after start!") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_AFTER_START') + t.assert.strictEqual(error.message, "The decorator '%s' has been added after start!") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_DEC_REFERENCE_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_DEC_REFERENCE_TYPE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_DEC_REFERENCE_TYPE') - t.equal(error.message, "The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead.") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.assert.strictEqual(error.message, "The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_HOOK_INVALID_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_HOOK_INVALID_TYPE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_HOOK_INVALID_TYPE') - t.equal(error.message, 'The hook name must be a string') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_INVALID_TYPE') + t.assert.strictEqual(error.message, 'The hook name must be a string') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_HOOK_INVALID_HANDLER', t => { t.plan(5) const error = new errors.FST_ERR_HOOK_INVALID_HANDLER() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_HOOK_INVALID_HANDLER') - t.equal(error.message, '%s hook should be a function, instead got %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.assert.strictEqual(error.message, '%s hook should be a function, instead got %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_HOOK_INVALID_ASYNC_HANDLER', t => { t.plan(5) const error = new errors.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(error.message, "Async function has too many arguments. Async hooks should not use the 'done' argument.") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(error.message, "Async function has too many arguments. Async hooks should not use the 'done' argument.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_HOOK_NOT_SUPPORTED', t => { t.plan(5) const error = new errors.FST_ERR_HOOK_NOT_SUPPORTED() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_HOOK_NOT_SUPPORTED') - t.equal(error.message, '%s hook not supported!') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_NOT_SUPPORTED') + t.assert.strictEqual(error.message, '%s hook not supported!') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_MISSING_MIDDLEWARE', t => { t.plan(5) const error = new errors.FST_ERR_MISSING_MIDDLEWARE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_MISSING_MIDDLEWARE') - t.equal(error.message, 'You must register a plugin for handling middlewares, visit fastify.dev/docs/latest/Reference/Middleware/ for more info.') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_MISSING_MIDDLEWARE') + t.assert.strictEqual(error.message, 'You must register a plugin for handling middlewares, visit fastify.dev/docs/latest/Reference/Middleware/ for more info.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_HOOK_TIMEOUT', t => { t.plan(5) const error = new errors.FST_ERR_HOOK_TIMEOUT() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_HOOK_TIMEOUT') - t.equal(error.message, "A callback for '%s' hook timed out. You may have forgotten to call 'done' function or to resolve a Promise") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_TIMEOUT') + t.assert.strictEqual(error.message, "A callback for '%s' hook timed out. You may have forgotten to call 'done' function or to resolve a Promise") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_LOG_INVALID_DESTINATION', t => { t.plan(5) const error = new errors.FST_ERR_LOG_INVALID_DESTINATION() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_LOG_INVALID_DESTINATION') - t.equal(error.message, 'Cannot specify both logger.stream and logger.file options') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_INVALID_DESTINATION') + t.assert.strictEqual(error.message, 'Cannot specify both logger.stream and logger.file options') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_LOG_INVALID_LOGGER', t => { t.plan(5) const error = new errors.FST_ERR_LOG_INVALID_LOGGER() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_LOG_INVALID_LOGGER') - t.equal(error.message, "Invalid logger object provided. The logger instance should have these functions(s): '%s'.") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_INVALID_LOGGER') + t.assert.strictEqual(error.message, "Invalid logger object provided. The logger instance should have these functions(s): '%s'.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_LOG_INVALID_LOGGER_INSTANCE', t => { t.plan(5) const error = new errors.FST_ERR_LOG_INVALID_LOGGER_INSTANCE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE') - t.equal(error.message, 'loggerInstance only accepts a logger instance.') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE') + t.assert.strictEqual(error.message, 'loggerInstance only accepts a logger instance.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_LOG_INVALID_LOGGER_CONFIG', t => { t.plan(5) const error = new errors.FST_ERR_LOG_INVALID_LOGGER_CONFIG() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_LOG_INVALID_LOGGER_CONFIG') - t.equal(error.message, 'logger options only accepts a configuration object.') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_INVALID_LOGGER_CONFIG') + t.assert.strictEqual(error.message, 'logger options only accepts a configuration object.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED', t => { t.plan(5) const error = new errors.FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED') - t.equal(error.message, 'You cannot provide both logger and loggerInstance. Please provide only one.') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED') + t.assert.strictEqual(error.message, 'You cannot provide both logger and loggerInstance. Please provide only one.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_REP_INVALID_PAYLOAD_TYPE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') - t.equal(error.message, "Attempted to send payload of invalid type '%s'. Expected a string or Buffer.") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') + t.assert.strictEqual(error.message, "Attempted to send payload of invalid type '%s'. Expected a string or Buffer.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_REP_RESPONSE_BODY_CONSUMED', t => { t.plan(5) const error = new errors.FST_ERR_REP_RESPONSE_BODY_CONSUMED() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') - t.equal(error.message, 'Response.body is already consumed.') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') + t.assert.strictEqual(error.message, 'Response.body is already consumed.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_REP_ALREADY_SENT', t => { t.plan(5) const error = new errors.FST_ERR_REP_ALREADY_SENT('/hello', 'GET') - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_REP_ALREADY_SENT') - t.equal(error.message, 'Reply was already sent, did you forget to "return reply" in "/hello" (GET)?') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_ALREADY_SENT') + t.assert.strictEqual(error.message, 'Reply was already sent, did you forget to "return reply" in "/hello" (GET)?') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_REP_SENT_VALUE', t => { t.plan(5) const error = new errors.FST_ERR_REP_SENT_VALUE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_REP_SENT_VALUE') - t.equal(error.message, 'The only possible value for reply.sent is true.') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_SENT_VALUE') + t.assert.strictEqual(error.message, 'The only possible value for reply.sent is true.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_SEND_INSIDE_ONERR', t => { t.plan(5) const error = new errors.FST_ERR_SEND_INSIDE_ONERR() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SEND_INSIDE_ONERR') - t.equal(error.message, 'You cannot use `send` inside the `onError` hook') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SEND_INSIDE_ONERR') + t.assert.strictEqual(error.message, 'You cannot use `send` inside the `onError` hook') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_SEND_UNDEFINED_ERR', t => { t.plan(5) const error = new errors.FST_ERR_SEND_UNDEFINED_ERR() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SEND_UNDEFINED_ERR') - t.equal(error.message, 'Undefined error has occurred') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SEND_UNDEFINED_ERR') + t.assert.strictEqual(error.message, 'Undefined error has occurred') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_BAD_STATUS_CODE', t => { t.plan(5) const error = new errors.FST_ERR_BAD_STATUS_CODE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_BAD_STATUS_CODE') - t.equal(error.message, 'Called reply with an invalid status code: %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_BAD_STATUS_CODE') + t.assert.strictEqual(error.message, 'Called reply with an invalid status code: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_BAD_TRAILER_NAME', t => { t.plan(5) const error = new errors.FST_ERR_BAD_TRAILER_NAME() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_BAD_TRAILER_NAME') - t.equal(error.message, 'Called reply.trailer with an invalid header name: %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_BAD_TRAILER_NAME') + t.assert.strictEqual(error.message, 'Called reply.trailer with an invalid header name: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_BAD_TRAILER_VALUE', t => { t.plan(5) const error = new errors.FST_ERR_BAD_TRAILER_VALUE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_BAD_TRAILER_VALUE') - t.equal(error.message, "Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function.") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_BAD_TRAILER_VALUE') + t.assert.strictEqual(error.message, "Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_FAILED_ERROR_SERIALIZATION', t => { t.plan(5) const error = new errors.FST_ERR_FAILED_ERROR_SERIALIZATION() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_FAILED_ERROR_SERIALIZATION') - t.equal(error.message, 'Failed to serialize an error. Error: %s. Original error: %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_FAILED_ERROR_SERIALIZATION') + t.assert.strictEqual(error.message, 'Failed to serialize an error. Error: %s. Original error: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_MISSING_SERIALIZATION_FN', t => { t.plan(5) const error = new errors.FST_ERR_MISSING_SERIALIZATION_FN() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_MISSING_SERIALIZATION_FN') - t.equal(error.message, 'Missing serialization function. Key "%s"') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_MISSING_SERIALIZATION_FN') + t.assert.strictEqual(error.message, 'Missing serialization function. Key "%s"') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN', t => { t.plan(5) const error = new errors.FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN') - t.equal(error.message, 'Missing serialization function. Key "%s:%s"') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN') + t.assert.strictEqual(error.message, 'Missing serialization function. Key "%s:%s"') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_REQ_INVALID_VALIDATION_INVOCATION', t => { t.plan(5) const error = new errors.FST_ERR_REQ_INVALID_VALIDATION_INVOCATION() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') - t.equal(error.message, 'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + t.assert.strictEqual(error.message, 'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_SCH_MISSING_ID', t => { t.plan(5) const error = new errors.FST_ERR_SCH_MISSING_ID() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCH_MISSING_ID') - t.equal(error.message, 'Missing schema $id property') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_MISSING_ID') + t.assert.strictEqual(error.message, 'Missing schema $id property') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_SCH_ALREADY_PRESENT', t => { t.plan(5) const error = new errors.FST_ERR_SCH_ALREADY_PRESENT() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCH_ALREADY_PRESENT') - t.equal(error.message, "Schema with id '%s' already declared!") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_ALREADY_PRESENT') + t.assert.strictEqual(error.message, "Schema with id '%s' already declared!") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_SCH_CONTENT_MISSING_SCHEMA', t => { t.plan(5) const error = new errors.FST_ERR_SCH_CONTENT_MISSING_SCHEMA() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') - t.equal(error.message, "Schema is missing for the content type '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + t.assert.strictEqual(error.message, "Schema is missing for the content type '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_SCH_DUPLICATE', t => { t.plan(5) const error = new errors.FST_ERR_SCH_DUPLICATE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCH_DUPLICATE') - t.equal(error.message, "Schema with '%s' already present!") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_DUPLICATE') + t.assert.strictEqual(error.message, "Schema with '%s' already present!") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_SCH_VALIDATION_BUILD', t => { t.plan(5) const error = new errors.FST_ERR_SCH_VALIDATION_BUILD() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.equal(error.message, 'Failed building the validation schema for %s: %s, due to error %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.assert.strictEqual(error.message, 'Failed building the validation schema for %s: %s, due to error %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_SCH_SERIALIZATION_BUILD', t => { t.plan(5) const error = new errors.FST_ERR_SCH_SERIALIZATION_BUILD() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') - t.equal(error.message, 'Failed building the serialization schema for %s: %s, due to error %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + t.assert.strictEqual(error.message, 'Failed building the serialization schema for %s: %s, due to error %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX', t => { t.plan(5) const error = new errors.FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX') - t.equal(error.message, 'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX') + t.assert.strictEqual(error.message, 'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_HTTP2_INVALID_VERSION', t => { t.plan(5) const error = new errors.FST_ERR_HTTP2_INVALID_VERSION() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_HTTP2_INVALID_VERSION') - t.equal(error.message, 'HTTP2 is available only from node >= 8.8.1') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HTTP2_INVALID_VERSION') + t.assert.strictEqual(error.message, 'HTTP2 is available only from node >= 8.8.1') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_INIT_OPTS_INVALID', t => { t.plan(5) const error = new errors.FST_ERR_INIT_OPTS_INVALID() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_INIT_OPTS_INVALID') - t.equal(error.message, "Invalid initialization options: '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_INIT_OPTS_INVALID') + t.assert.strictEqual(error.message, "Invalid initialization options: '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE', t => { t.plan(5) const error = new errors.FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE') - t.equal(error.message, "Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE') + t.assert.strictEqual(error.message, "Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_DUPLICATED_ROUTE', t => { t.plan(5) const error = new errors.FST_ERR_DUPLICATED_ROUTE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') - t.equal(error.message, "Method '%s' already declared for route '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DUPLICATED_ROUTE') + t.assert.strictEqual(error.message, "Method '%s' already declared for route '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_BAD_URL', t => { t.plan(5) const error = new errors.FST_ERR_BAD_URL() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_BAD_URL') - t.equal(error.message, "'%s' is not a valid url component") - t.equal(error.statusCode, 400) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_BAD_URL') + t.assert.strictEqual(error.message, "'%s' is not a valid url component") + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) }) test('FST_ERR_ASYNC_CONSTRAINT', t => { t.plan(5) const error = new errors.FST_ERR_ASYNC_CONSTRAINT() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ASYNC_CONSTRAINT') - t.equal(error.message, 'Unexpected error from async constraint') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ASYNC_CONSTRAINT') + t.assert.strictEqual(error.message, 'Unexpected error from async constraint') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_INVALID_URL', t => { t.plan(5) const error = new errors.FST_ERR_INVALID_URL() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_INVALID_URL') - t.equal(error.message, "URL must be a string. Received '%s'") - t.equal(error.statusCode, 400) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_INVALID_URL') + t.assert.strictEqual(error.message, "URL must be a string. Received '%s'") + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_OPTIONS_NOT_OBJ', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_OPTIONS_NOT_OBJ() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ') - t.equal(error.message, 'Options for "%s:%s" route must be an object') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ') + t.assert.strictEqual(error.message, 'Options for "%s:%s" route must be an object') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_DUPLICATED_HANDLER', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_DUPLICATED_HANDLER() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_DUPLICATED_HANDLER') - t.equal(error.message, 'Duplicate handler for "%s:%s" route is not allowed!') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_DUPLICATED_HANDLER') + t.assert.strictEqual(error.message, 'Duplicate handler for "%s:%s" route is not allowed!') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_ROUTE_HANDLER_NOT_FN', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_HANDLER_NOT_FN() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_HANDLER_NOT_FN') - t.equal(error.message, 'Error Handler for %s:%s route, if defined, must be a function') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_HANDLER_NOT_FN') + t.assert.strictEqual(error.message, 'Error Handler for %s:%s route, if defined, must be a function') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_MISSING_HANDLER', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_MISSING_HANDLER() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_MISSING_HANDLER') - t.equal(error.message, 'Missing handler function for "%s:%s" route.') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_MISSING_HANDLER') + t.assert.strictEqual(error.message, 'Missing handler function for "%s:%s" route.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_ROUTE_METHOD_INVALID', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_METHOD_INVALID() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_METHOD_INVALID') - t.equal(error.message, 'Provided method is invalid!') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_METHOD_INVALID') + t.assert.strictEqual(error.message, 'Provided method is invalid!') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_METHOD_NOT_SUPPORTED() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED') - t.equal(error.message, '%s method is not supported.') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED') + t.assert.strictEqual(error.message, '%s method is not supported.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED') - t.equal(error.message, 'Body validation schema for %s:%s route is not supported!') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED') + t.assert.strictEqual(error.message, 'Body validation schema for %s:%s route is not supported!') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') - t.equal(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') + t.assert.strictEqual(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') - t.equal(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') + t.assert.strictEqual(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_ROUTE_REWRITE_NOT_STR', t => { t.plan(5) const error = new errors.FST_ERR_ROUTE_REWRITE_NOT_STR() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROUTE_REWRITE_NOT_STR') - t.equal(error.message, 'Rewrite url for "%s" needs to be of type "string" but received "%s"') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_REWRITE_NOT_STR') + t.assert.strictEqual(error.message, 'Rewrite url for "%s" needs to be of type "string" but received "%s"') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_REOPENED_CLOSE_SERVER', t => { t.plan(5) const error = new errors.FST_ERR_REOPENED_CLOSE_SERVER() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_REOPENED_CLOSE_SERVER') - t.equal(error.message, 'Fastify has already been closed and cannot be reopened') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + t.assert.strictEqual(error.message, 'Fastify has already been closed and cannot be reopened') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_REOPENED_SERVER', t => { t.plan(5) const error = new errors.FST_ERR_REOPENED_SERVER() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_REOPENED_SERVER') - t.equal(error.message, 'Fastify is already listening') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REOPENED_SERVER') + t.assert.strictEqual(error.message, 'Fastify is already listening') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_INSTANCE_ALREADY_LISTENING', t => { t.plan(5) const error = new errors.FST_ERR_INSTANCE_ALREADY_LISTENING() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') - t.equal(error.message, 'Fastify instance is already listening. %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') + t.assert.strictEqual(error.message, 'Fastify instance is already listening. %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_PLUGIN_VERSION_MISMATCH', t => { t.plan(5) const error = new errors.FST_ERR_PLUGIN_VERSION_MISMATCH() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') - t.equal(error.message, "fastify-plugin: %s - expected '%s' fastify version, '%s' is installed") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + t.assert.strictEqual(error.message, "fastify-plugin: %s - expected '%s' fastify version, '%s' is installed") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE', t => { t.plan(5) const error = new errors.FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE') - t.equal(error.message, "The decorator '%s'%s is not present in %s") - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE') + t.assert.strictEqual(error.message, "The decorator '%s'%s is not present in %s") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER', t => { t.plan(5) const error = new errors.FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER('easter-egg') - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER') - t.equal(error.message, 'The easter-egg plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(error.message, 'The easter-egg plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_PLUGIN_CALLBACK_NOT_FN', t => { t.plan(5) const error = new errors.FST_ERR_PLUGIN_CALLBACK_NOT_FN() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_PLUGIN_CALLBACK_NOT_FN') - t.equal(error.message, 'fastify-plugin: %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_CALLBACK_NOT_FN') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_PLUGIN_NOT_VALID', t => { t.plan(5) const error = new errors.FST_ERR_PLUGIN_NOT_VALID() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_PLUGIN_NOT_VALID') - t.equal(error.message, 'fastify-plugin: %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_NOT_VALID') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_ROOT_PLG_BOOTED', t => { t.plan(5) const error = new errors.FST_ERR_ROOT_PLG_BOOTED() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ROOT_PLG_BOOTED') - t.equal(error.message, 'fastify-plugin: %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROOT_PLG_BOOTED') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_PARENT_PLUGIN_BOOTED', t => { t.plan(5) const error = new errors.FST_ERR_PARENT_PLUGIN_BOOTED() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_PARENT_PLUGIN_BOOTED') - t.equal(error.message, 'fastify-plugin: %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PARENT_PLUGIN_BOOTED') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_PLUGIN_TIMEOUT', t => { t.plan(5) const error = new errors.FST_ERR_PLUGIN_TIMEOUT() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_PLUGIN_TIMEOUT') - t.equal(error.message, 'fastify-plugin: %s') - t.equal(error.statusCode, 500) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) }) test('FST_ERR_VALIDATION', t => { t.plan(5) const error = new errors.FST_ERR_VALIDATION() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_VALIDATION') - t.equal(error.message, '%s') - t.equal(error.statusCode, 400) - t.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(error.message, '%s') + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) }) test('FST_ERR_LISTEN_OPTIONS_INVALID', t => { t.plan(5) const error = new errors.FST_ERR_LISTEN_OPTIONS_INVALID() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_LISTEN_OPTIONS_INVALID') - t.equal(error.message, "Invalid listen options: '%s'") - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LISTEN_OPTIONS_INVALID') + t.assert.strictEqual(error.message, "Invalid listen options: '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('FST_ERR_ERROR_HANDLER_NOT_FN', t => { t.plan(5) const error = new errors.FST_ERR_ERROR_HANDLER_NOT_FN() - t.equal(error.name, 'FastifyError') - t.equal(error.code, 'FST_ERR_ERROR_HANDLER_NOT_FN') - t.equal(error.message, 'Error Handler must be a function') - t.equal(error.statusCode, 500) - t.ok(error instanceof TypeError) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ERROR_HANDLER_NOT_FN') + t.assert.strictEqual(error.message, 'Error Handler must be a function') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) }) test('Ensure that all errors are in Errors.md TOC', t => { @@ -874,7 +874,7 @@ test('Ensure that all errors are in Errors.md TOC', t => { const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { - t.ok(errorsMd.includes(` - [${key.toUpperCase()}](#${key.toLowerCase()})`), key) + t.assert.ok(errorsMd.includes(` - [${key.toUpperCase()}](#${key.toLowerCase()})`), key) } } }) @@ -888,7 +888,7 @@ test('Ensure that non-existing errors are not in Errors.md TOC', t => { const exportedKeys = Object.keys(errors) for (const match of matches) { - t.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) + t.assert.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) } }) @@ -899,7 +899,7 @@ test('Ensure that all errors are in Errors.md documented', t => { const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { - t.ok(errorsMd.includes(`${key.toUpperCase()}`), key) + t.assert.ok(errorsMd.includes(`${key.toUpperCase()}`), key) } } }) @@ -913,6 +913,6 @@ test('Ensure that non-existing errors are not in Errors.md documented', t => { const exportedKeys = Object.keys(errors) for (const match of matches) { - t.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) + t.assert.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) } }) diff --git a/test/internals/handleRequest.test.js b/test/internals/handleRequest.test.js index 1c0e742f2f8..e9947f2c9df 100644 --- a/test/internals/handleRequest.test.js +++ b/test/internals/handleRequest.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const handleRequest = require('../../lib/handleRequest') const internals = require('../../lib/handleRequest')[Symbol.for('internals')] const Request = require('../../lib/request') @@ -28,14 +28,14 @@ test('handleRequest function - sent reply', t => { const request = {} const reply = { sent: true } const res = handleRequest(null, request, reply) - t.equal(res, undefined) + t.assert.strictEqual(res, undefined) }) test('handleRequest function - invoke with error', t => { t.plan(1) const request = {} const reply = {} - reply.send = (err) => t.equal(err.message, 'Kaboom') + reply.send = (err) => t.assert.strictEqual(err.message, 'Kaboom') handleRequest(new Error('Kaboom'), request, reply) }) @@ -56,7 +56,7 @@ test('handler function - invalid schema', t => { } } }, - errorHandler: { func: () => { t.pass('errorHandler called') } }, + errorHandler: { func: () => { t.assert.ok('errorHandler called') } }, handler: () => {}, Reply, Request, @@ -79,13 +79,13 @@ test('handler function - reply', t => { t.plan(3) const res = {} res.end = () => { - t.equal(res.statusCode, 204) - t.pass() + t.assert.strictEqual(res.statusCode, 204) + t.assert.ok(true) } res.writeHead = () => {} const context = { handler: (req, reply) => { - t.equal(typeof reply, 'object') + t.assert.strictEqual(typeof reply, 'object') reply.code(204) reply.send(undefined) }, @@ -110,12 +110,12 @@ test('handler function - preValidationCallback with finished response', t => { // Be sure to check only `writableEnded` where is available res.writableEnded = true res.end = () => { - t.fail() + t.assert.fail() } res.writeHead = () => {} const context = { handler: (req, reply) => { - t.fail() + t.assert.fail() reply.send(undefined) }, Reply, @@ -129,25 +129,28 @@ test('handler function - preValidationCallback with finished response', t => { internals.handler({ [kRouteContext]: context }, new Reply(res, { [kRouteContext]: context })) }) -test('request should be defined in onSend Hook on post request with content type application/json', t => { +test('request should be defined in onSend Hook on post request with content type application/json', (t, done) => { t.plan(8) const fastify = require('../..')() + t.after(() => { + fastify.close() + }) + fastify.addHook('onSend', (request, reply, payload, done) => { - t.ok(request) - t.ok(request.raw) - t.ok(request.id) - t.ok(request.params) - t.ok(request.query) + t.assert.ok(request) + t.assert.ok(request.raw) + t.assert.ok(request.id) + t.assert.ok(request.params) + t.assert.ok(request.query) done() }) fastify.post('/', (request, reply) => { reply.send(200) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) sget({ method: 'POST', url: 'http://localhost:' + fastify.server.address().port, @@ -155,30 +158,33 @@ test('request should be defined in onSend Hook on post request with content type 'content-type': 'application/json' } }, (err, response, body) => { - t.error(err) + t.assert.ifError(err) // a 400 error is expected because of no body - t.equal(response.statusCode, 400) + t.assert.strictEqual(response.statusCode, 400) + done() }) }) }) -test('request should be defined in onSend Hook on post request with content type application/x-www-form-urlencoded', t => { +test('request should be defined in onSend Hook on post request with content type application/x-www-form-urlencoded', (t, done) => { t.plan(7) const fastify = require('../..')() + t.after(() => { fastify.close() }) + fastify.addHook('onSend', (request, reply, payload, done) => { - t.ok(request) - t.ok(request.raw) - t.ok(request.params) - t.ok(request.query) + t.assert.ok(request) + t.assert.ok(request.raw) + t.assert.ok(request.params) + t.assert.ok(request.query) done() }) fastify.post('/', (request, reply) => { reply.send(200) }) + fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) sget({ method: 'POST', @@ -187,30 +193,33 @@ test('request should be defined in onSend Hook on post request with content type 'content-type': 'application/x-www-form-urlencoded' } }, (err, response, body) => { - t.error(err) + t.assert.ifError(err) // a 415 error is expected because of missing content type parser - t.equal(response.statusCode, 415) + t.assert.strictEqual(response.statusCode, 415) + done() }) }) }) -test('request should be defined in onSend Hook on options request with content type application/x-www-form-urlencoded', t => { +test('request should be defined in onSend Hook on options request with content type application/x-www-form-urlencoded', (t, done) => { t.plan(7) const fastify = require('../..')() + t.after(() => { fastify.close() }) + fastify.addHook('onSend', (request, reply, payload, done) => { - t.ok(request) - t.ok(request.raw) - t.ok(request.params) - t.ok(request.query) + t.assert.ok(request) + t.assert.ok(request.raw) + t.assert.ok(request.params) + t.assert.ok(request.query) done() }) fastify.options('/', (request, reply) => { reply.send(200) }) + fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) sget({ method: 'OPTIONS', @@ -219,14 +228,15 @@ test('request should be defined in onSend Hook on options request with content t 'content-type': 'application/x-www-form-urlencoded' } }, (err, response, body) => { - t.error(err) + t.assert.ifError(err) // Body parsing skipped, so no body sent - t.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('request should respond with an error if an unserialized payload is sent inside an async handler', t => { +test('request should respond with an error if an unserialized payload is sent inside an async handler', (t, done) => { t.plan(3) const fastify = require('../..')() @@ -240,13 +250,14 @@ test('request should respond with an error if an unserialized payload is sent in method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.strictSame(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE', message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.', statusCode: 500 }) + done() }) }) diff --git a/test/internals/hookRunner.test.js b/test/internals/hookRunner.test.js index 5dbb4d5e2a0..f8319fe29a4 100644 --- a/test/internals/hookRunner.test.js +++ b/test/internals/hookRunner.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const { hookRunnerGenerator, onSendHookRunner } = require('../../lib/hooks') test('hookRunner - Basic', t => { @@ -16,27 +15,27 @@ test('hookRunner - Basic', t => { } function fn1 (a, b, done) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') done() } function fn2 (a, b, done) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') done() } function fn3 (a, b, done) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') done() } function done (err, a, b) { - t.error(err) - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.ifError(err) + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') } }) @@ -52,25 +51,25 @@ test('hookRunner - In case of error should skip to done', t => { } function fn1 (a, b, done) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') done() } function fn2 (a, b, done) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') done(new Error('kaboom')) } function fn3 () { - t.fail('We should not be here') + t.assert.fail('We should not be here') } function done (err, a, b) { - t.equal(err.message, 'kaboom') - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(err.message, 'kaboom') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') } }) @@ -86,25 +85,25 @@ test('hookRunner - Should handle throw', t => { } function fn1 (a, b, done) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') done() } function fn2 (a, b, done) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') throw new Error('kaboom') } function fn3 () { - t.fail('We should not be here') + t.assert.fail('We should not be here') } function done (err, a, b) { - t.equal(err.message, 'kaboom') - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(err.message, 'kaboom') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') } }) @@ -120,27 +119,27 @@ test('hookRunner - Should handle promises', t => { } function fn1 (a, b) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') return Promise.resolve() } function fn2 (a, b) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') return Promise.resolve() } function fn3 (a, b) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') return Promise.resolve() } function done (err, a, b) { - t.error(err) - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.ifError(err) + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') } }) @@ -156,25 +155,25 @@ test('hookRunner - In case of error should skip to done (with promises)', t => { } function fn1 (a, b) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') return Promise.resolve() } function fn2 (a, b) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') return Promise.reject(new Error('kaboom')) } function fn3 () { - t.fail('We should not be here') + t.assert.fail('We should not be here') } function done (err, a, b) { - t.equal(err.message, 'kaboom') - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(err.message, 'kaboom') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') } }) @@ -194,24 +193,24 @@ test('hookRunner - Be able to exit before its natural end', t => { } function fn1 (a, b, done) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') done() } function fn2 (a, b) { - t.equal(a, 'a') - t.equal(b, 'b') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') shouldStop = true return Promise.resolve() } function fn3 () { - t.fail('this should not be called') + t.assert.fail('this should not be called') } function done () { - t.fail('this should not be called') + t.assert.fail('this should not be called') } }) @@ -229,23 +228,23 @@ test('hookRunner - Promises that resolve to a value do not change the state', t } function fn1 (state, b, done) { - t.equal(state, originalState) + t.assert.strictEqual(state, originalState) return Promise.resolve(null) } function fn2 (state, b, done) { - t.equal(state, originalState) + t.assert.strictEqual(state, originalState) return Promise.resolve('string') } function fn3 (state, b, done) { - t.equal(state, originalState) + t.assert.strictEqual(state, originalState) return Promise.resolve({ object: true }) } function done (err, state, b) { - t.error(err) - t.equal(state, originalState) + t.assert.ifError(err) + t.assert.strictEqual(state, originalState) } }) @@ -259,31 +258,31 @@ test('onSendHookRunner - Basic', t => { onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, originalPayload, done) function fn1 (request, reply, payload, done) { - t.same(request, originalRequest) - t.same(reply, originalReply) - t.equal(payload, originalPayload) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.strictEqual(payload, originalPayload) done() } function fn2 (request, reply, payload, done) { - t.same(request, originalRequest) - t.same(reply, originalReply) - t.equal(payload, originalPayload) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.strictEqual(payload, originalPayload) done() } function fn3 (request, reply, payload, done) { - t.same(request, originalRequest) - t.same(reply, originalReply) - t.equal(payload, originalPayload) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.strictEqual(payload, originalPayload) done() } function done (err, request, reply, payload) { - t.error(err) - t.same(request, originalRequest) - t.same(reply, originalReply) - t.equal(payload, originalPayload) + t.assert.ifError(err) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.strictEqual(payload, originalPayload) } }) @@ -300,25 +299,25 @@ test('onSendHookRunner - Can change the payload', t => { onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) function fn1 (request, reply, payload, done) { - t.same(payload, v1) + t.assert.deepStrictEqual(payload, v1) done(null, v2) } function fn2 (request, reply, payload, done) { - t.same(payload, v2) + t.assert.deepStrictEqual(payload, v2) done(null, v3) } function fn3 (request, reply, payload, done) { - t.same(payload, v3) + t.assert.deepStrictEqual(payload, v3) done(null, v4) } function done (err, request, reply, payload) { - t.error(err) - t.same(request, originalRequest) - t.same(reply, originalReply) - t.same(payload, v4) + t.assert.ifError(err) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.deepStrictEqual(payload, v4) } }) @@ -333,24 +332,24 @@ test('onSendHookRunner - In case of error should skip to done', t => { onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) function fn1 (request, reply, payload, done) { - t.same(payload, v1) + t.assert.deepStrictEqual(payload, v1) done(null, v2) } function fn2 (request, reply, payload, done) { - t.same(payload, v2) + t.assert.deepStrictEqual(payload, v2) done(new Error('kaboom')) } function fn3 () { - t.fail('We should not be here') + t.assert.fail('We should not be here') } function done (err, request, reply, payload) { - t.equal(err.message, 'kaboom') - t.same(request, originalRequest) - t.same(reply, originalReply) - t.same(payload, v2) + t.assert.strictEqual(err.message, 'kaboom') + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.deepStrictEqual(payload, v2) } }) @@ -367,25 +366,25 @@ test('onSendHookRunner - Should handle promises', t => { onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) function fn1 (request, reply, payload) { - t.same(payload, v1) + t.assert.deepStrictEqual(payload, v1) return Promise.resolve(v2) } function fn2 (request, reply, payload) { - t.same(payload, v2) + t.assert.deepStrictEqual(payload, v2) return Promise.resolve(v3) } function fn3 (request, reply, payload) { - t.same(payload, v3) + t.assert.deepStrictEqual(payload, v3) return Promise.resolve(v4) } function done (err, request, reply, payload) { - t.error(err) - t.same(request, originalRequest) - t.same(reply, originalReply) - t.same(payload, v4) + t.assert.ifError(err) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.deepStrictEqual(payload, v4) } }) @@ -400,24 +399,24 @@ test('onSendHookRunner - In case of error should skip to done (with promises)', onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) function fn1 (request, reply, payload) { - t.same(payload, v1) + t.assert.deepStrictEqual(payload, v1) return Promise.resolve(v2) } function fn2 (request, reply, payload) { - t.same(payload, v2) + t.assert.deepStrictEqual(payload, v2) return Promise.reject(new Error('kaboom')) } function fn3 () { - t.fail('We should not be here') + t.assert.fail('We should not be here') } function done (err, request, reply, payload) { - t.equal(err.message, 'kaboom') - t.same(request, originalRequest) - t.same(reply, originalReply) - t.same(payload, v2) + t.assert.strictEqual(err.message, 'kaboom') + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.deepStrictEqual(payload, v2) } }) @@ -432,19 +431,19 @@ test('onSendHookRunner - Be able to exit before its natural end', t => { onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) function fn1 (request, reply, payload, done) { - t.same(payload, v1) + t.assert.deepStrictEqual(payload, v1) done(null, v2) } function fn2 (request, reply, payload) { - t.same(payload, v2) + t.assert.deepStrictEqual(payload, v2) } function fn3 () { - t.fail('this should not be called') + t.assert.fail('this should not be called') } function done () { - t.fail('this should not be called') + t.assert.fail('this should not be called') } }) diff --git a/test/internals/hooks.test.js b/test/internals/hooks.test.js index 5db3854078b..2f91196d710 100644 --- a/test/internals/hooks.test.js +++ b/test/internals/hooks.test.js @@ -1,54 +1,50 @@ 'use strict' -const t = require('tap') -const test = t.test - +const { test } = require('node:test') const { Hooks } = require('../../lib/hooks') const noop = () => {} test('hooks should have 4 array with the registered hooks', t => { const hooks = new Hooks() - t.equal(typeof hooks, 'object') - t.ok(Array.isArray(hooks.onRequest)) - t.ok(Array.isArray(hooks.onSend)) - t.ok(Array.isArray(hooks.preParsing)) - t.ok(Array.isArray(hooks.preValidation)) - t.ok(Array.isArray(hooks.preHandler)) - t.ok(Array.isArray(hooks.onResponse)) - t.ok(Array.isArray(hooks.onError)) - t.end() + t.assert.strictEqual(typeof hooks, 'object') + t.assert.ok(Array.isArray(hooks.onRequest)) + t.assert.ok(Array.isArray(hooks.onSend)) + t.assert.ok(Array.isArray(hooks.preParsing)) + t.assert.ok(Array.isArray(hooks.preValidation)) + t.assert.ok(Array.isArray(hooks.preHandler)) + t.assert.ok(Array.isArray(hooks.onResponse)) + t.assert.ok(Array.isArray(hooks.onError)) }) test('hooks.add should add a hook to the given hook', t => { const hooks = new Hooks() hooks.add('onRequest', noop) - t.equal(hooks.onRequest.length, 1) - t.equal(typeof hooks.onRequest[0], 'function') + t.assert.strictEqual(hooks.onRequest.length, 1) + t.assert.strictEqual(typeof hooks.onRequest[0], 'function') hooks.add('preParsing', noop) - t.equal(hooks.preParsing.length, 1) - t.equal(typeof hooks.preParsing[0], 'function') + t.assert.strictEqual(hooks.preParsing.length, 1) + t.assert.strictEqual(typeof hooks.preParsing[0], 'function') hooks.add('preValidation', noop) - t.equal(hooks.preValidation.length, 1) - t.equal(typeof hooks.preValidation[0], 'function') + t.assert.strictEqual(hooks.preValidation.length, 1) + t.assert.strictEqual(typeof hooks.preValidation[0], 'function') hooks.add('preHandler', noop) - t.equal(hooks.preHandler.length, 1) - t.equal(typeof hooks.preHandler[0], 'function') + t.assert.strictEqual(hooks.preHandler.length, 1) + t.assert.strictEqual(typeof hooks.preHandler[0], 'function') hooks.add('onResponse', noop) - t.equal(hooks.onResponse.length, 1) - t.equal(typeof hooks.onResponse[0], 'function') + t.assert.strictEqual(hooks.onResponse.length, 1) + t.assert.strictEqual(typeof hooks.onResponse[0], 'function') hooks.add('onSend', noop) - t.equal(hooks.onSend.length, 1) - t.equal(typeof hooks.onSend[0], 'function') + t.assert.strictEqual(hooks.onSend.length, 1) + t.assert.strictEqual(typeof hooks.onSend[0], 'function') hooks.add('onError', noop) - t.equal(hooks.onError.length, 1) - t.equal(typeof hooks.onError[0], 'function') - t.end() + t.assert.strictEqual(hooks.onError.length, 1) + t.assert.strictEqual(typeof hooks.onError[0], 'function') }) test('hooks should throw on unexisting handler', t => { @@ -56,9 +52,9 @@ test('hooks should throw on unexisting handler', t => { const hooks = new Hooks() try { hooks.add('onUnexistingHook', noop) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) @@ -67,17 +63,17 @@ test('should throw on wrong parameters', t => { t.plan(4) try { hooks.add(null, () => {}) - t.fail() + t.assert.fail() } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_TYPE') - t.equal(e.message, 'The hook name must be a string') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_TYPE') + t.assert.strictEqual(e.message, 'The hook name must be a string') } try { hooks.add('onSend', null) - t.fail() + t.assert.fail() } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') - t.equal(e.message, 'onSend hook should be a function, instead got [object Null]') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.assert.strictEqual(e.message, 'onSend hook should be a function, instead got [object Null]') } }) diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index f54e296bb02..20a1c146a9f 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test, before } = require('tap') +const { test, before } = require('node:test') const Fastify = require('../..') const helper = require('../helper') const http = require('node:http') @@ -23,7 +23,7 @@ before(async function () { test('Fastify.initialConfig is an object', t => { t.plan(1) - t.type(Fastify().initialConfig, 'object') + t.assert.ok(typeof Fastify().initialConfig === 'object') }) test('without options passed to Fastify, initialConfig should expose default values', t => { @@ -51,7 +51,7 @@ test('without options passed to Fastify, initialConfig should expose default val useSemicolonDelimiter: false } - t.same(Fastify().initialConfig, fastifyDefaultOptions) + t.assert.deepStrictEqual(Fastify().initialConfig, fastifyDefaultOptions) }) test('Fastify.initialConfig should expose all options', t => { @@ -114,30 +114,30 @@ test('Fastify.initialConfig should expose all options', t => { } const fastify = Fastify(options) - t.equal(fastify.initialConfig.http2, true) - t.equal(fastify.initialConfig.https, true, 'for security reason the key cert is hidden') - t.equal(fastify.initialConfig.ignoreTrailingSlash, true) - t.equal(fastify.initialConfig.ignoreDuplicateSlashes, true) - t.equal(fastify.initialConfig.maxParamLength, 200) - t.equal(fastify.initialConfig.connectionTimeout, 0) - t.equal(fastify.initialConfig.keepAliveTimeout, 72000) - t.equal(fastify.initialConfig.bodyLimit, 1049600) - t.equal(fastify.initialConfig.onProtoPoisoning, 'remove') - t.equal(fastify.initialConfig.caseSensitive, true) - t.equal(fastify.initialConfig.useSemicolonDelimiter, false) - t.equal(fastify.initialConfig.allowUnsafeRegex, false) - t.equal(fastify.initialConfig.requestIdHeader, 'request-id-alt') - t.equal(fastify.initialConfig.pluginTimeout, 20000) - t.ok(fastify.initialConfig.constraints.version) + t.assert.strictEqual(fastify.initialConfig.http2, true) + t.assert.strictEqual(fastify.initialConfig.https, true, 'for security reason the key cert is hidden') + t.assert.strictEqual(fastify.initialConfig.ignoreTrailingSlash, true) + t.assert.strictEqual(fastify.initialConfig.ignoreDuplicateSlashes, true) + t.assert.strictEqual(fastify.initialConfig.maxParamLength, 200) + t.assert.strictEqual(fastify.initialConfig.connectionTimeout, 0) + t.assert.strictEqual(fastify.initialConfig.keepAliveTimeout, 72000) + t.assert.strictEqual(fastify.initialConfig.bodyLimit, 1049600) + t.assert.strictEqual(fastify.initialConfig.onProtoPoisoning, 'remove') + t.assert.strictEqual(fastify.initialConfig.caseSensitive, true) + t.assert.strictEqual(fastify.initialConfig.useSemicolonDelimiter, false) + t.assert.strictEqual(fastify.initialConfig.allowUnsafeRegex, false) + t.assert.strictEqual(fastify.initialConfig.requestIdHeader, 'request-id-alt') + t.assert.strictEqual(fastify.initialConfig.pluginTimeout, 20000) + t.assert.ok(fastify.initialConfig.constraints.version) // obfuscated options: - t.equal(fastify.initialConfig.serverFactory, undefined) - t.equal(fastify.initialConfig.trustProxy, undefined) - t.equal(fastify.initialConfig.genReqId, undefined) - t.equal(fastify.initialConfig.childLoggerFactory, undefined) - t.equal(fastify.initialConfig.querystringParser, undefined) - t.equal(fastify.initialConfig.logger, undefined) - t.equal(fastify.initialConfig.trustProxy, undefined) + t.assert.strictEqual(fastify.initialConfig.serverFactory, undefined) + t.assert.strictEqual(fastify.initialConfig.trustProxy, undefined) + t.assert.strictEqual(fastify.initialConfig.genReqId, undefined) + t.assert.strictEqual(fastify.initialConfig.childLoggerFactory, undefined) + t.assert.strictEqual(fastify.initialConfig.querystringParser, undefined) + t.assert.strictEqual(fastify.initialConfig.logger, undefined) + t.assert.strictEqual(fastify.initialConfig.trustProxy, undefined) }) test('Should throw if you try to modify Fastify.initialConfig', t => { @@ -146,12 +146,12 @@ test('Should throw if you try to modify Fastify.initialConfig', t => { const fastify = Fastify({ ignoreTrailingSlash: true }) try { fastify.initialConfig.ignoreTrailingSlash = false - t.fail() + t.assert.fail() } catch (error) { - t.type(error, TypeError) - t.equal(error.message, "Cannot assign to read only property 'ignoreTrailingSlash' of object '#'") - t.ok(error.stack) - t.pass() + t.assert.ok(error instanceof TypeError) + t.assert.strictEqual(error.message, "Cannot assign to read only property 'ignoreTrailingSlash' of object '#'") + t.assert.ok(error.stack) + t.assert.ok(true) } }) @@ -168,12 +168,12 @@ test('We must avoid shallow freezing and ensure that the whole object is freezed try { fastify.initialConfig.https.allowHTTP1 = false - t.fail() + t.assert.fail() } catch (error) { - t.type(error, TypeError) - t.equal(error.message, "Cannot assign to read only property 'allowHTTP1' of object '#'") - t.ok(error.stack) - t.same(fastify.initialConfig.https, { + t.assert.ok(error instanceof TypeError) + t.assert.strictEqual(error.message, "Cannot assign to read only property 'allowHTTP1' of object '#'") + t.assert.ok(error.stack) + t.assert.deepStrictEqual(fastify.initialConfig.https, { allowHTTP1: true }, 'key cert removed') } @@ -183,7 +183,7 @@ test('https value check', t => { t.plan(1) const fastify = Fastify({}) - t.notOk(fastify.initialConfig.https) + t.assert.ok(!fastify.initialConfig.https) }) test('Return an error if options do not match the validation schema', t => { @@ -192,14 +192,14 @@ test('Return an error if options do not match the validation schema', t => { try { Fastify({ ignoreTrailingSlash: 'string instead of boolean' }) - t.fail() + t.assert.fail() } catch (error) { - t.type(error, Error) - t.equal(error.name, 'FastifyError') - t.equal(error.message, 'Invalid initialization options: \'["must be boolean"]\'') - t.equal(error.code, 'FST_ERR_INIT_OPTS_INVALID') - t.ok(error.stack) - t.pass() + t.assert.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.message, 'Invalid initialization options: \'["must be boolean"]\'') + t.assert.strictEqual(error.code, 'FST_ERR_INIT_OPTS_INVALID') + t.assert.ok(error.stack) + t.assert.ok(true) } }) @@ -216,10 +216,10 @@ test('Original options must not be frozen', t => { const fastify = Fastify(originalOptions) - t.equal(Object.isFrozen(originalOptions), false) - t.equal(Object.isFrozen(originalOptions.https), false) - t.equal(Object.isFrozen(fastify.initialConfig), true) - t.equal(Object.isFrozen(fastify.initialConfig.https), true) + t.assert.strictEqual(Object.isFrozen(originalOptions), false) + t.assert.strictEqual(Object.isFrozen(originalOptions.https), false) + t.assert.strictEqual(Object.isFrozen(fastify.initialConfig), true) + t.assert.strictEqual(Object.isFrozen(fastify.initialConfig.https), true) }) test('Original options must not be altered (test deep cloning)', t => { @@ -238,14 +238,14 @@ test('Original options must not be altered (test deep cloning)', t => { const fastify = Fastify(originalOptions) // initialConfig has been triggered - t.equal(Object.isFrozen(fastify.initialConfig), true) + t.assert.strictEqual(Object.isFrozen(fastify.initialConfig), true) // originalOptions must not have been altered - t.same(originalOptions.https.key, originalOptionsClone.https.key) - t.same(originalOptions.https.cert, originalOptionsClone.https.cert) + t.assert.deepStrictEqual(originalOptions.https.key, originalOptionsClone.https.key) + t.assert.deepStrictEqual(originalOptions.https.cert, originalOptionsClone.https.cert) }) -test('Should not have issues when passing stream options to Pino.js', t => { +test('Should not have issues when passing stream options to Pino.js', (t, done) => { t.plan(17) const stream = split(JSON.parse) @@ -267,8 +267,8 @@ test('Should not have issues when passing stream options to Pino.js', t => { return logger.child(bindings, opts) }) - t.type(fastify, 'object') - t.same(fastify.initialConfig, { + t.assert.ok(typeof fastify === 'object') + t.assert.deepStrictEqual(fastify.initialConfig, { connectionTimeout: 0, keepAliveTimeout: 72000, maxRequestsPerSocket: 0, @@ -290,42 +290,44 @@ test('Should not have issues when passing stream options to Pino.js', t => { useSemicolonDelimiter: false }) } catch (error) { - t.fail() + t.assert.fail() } fastify.get('/', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send({ hello: 'world' }) }) stream.once('data', listenAtLogLine => { - t.ok(listenAtLogLine, 'listen at log message is ok') + t.assert.ok(listenAtLogLine, 'listen at log message is ok') stream.once('data', line => { const id = line.reqId - t.ok(line.reqId, 'reqId is defined') - t.equal(line.someBinding, 'value', 'child logger binding is set') - t.ok(line.req, 'req is defined') - t.equal(line.msg, 'incoming request', 'message is set') - t.equal(line.req.method, 'GET', 'method is get') + t.assert.ok(line.reqId, 'reqId is defined') + t.assert.strictEqual(line.someBinding, 'value', 'child logger binding is set') + t.assert.ok(line.req, 'req is defined') + t.assert.strictEqual(line.msg, 'incoming request', 'message is set') + t.assert.strictEqual(line.req.method, 'GET', 'method is get') stream.once('data', line => { - t.equal(line.reqId, id) - t.ok(line.reqId, 'reqId is defined') - t.equal(line.someBinding, 'value', 'child logger binding is set') - t.ok(line.res, 'res is defined') - t.equal(line.msg, 'request completed', 'message is set') - t.equal(line.res.statusCode, 200, 'statusCode is 200') - t.ok(line.responseTime, 'responseTime is defined') + t.assert.strictEqual(line.reqId, id) + t.assert.ok(line.reqId, 'reqId is defined') + t.assert.strictEqual(line.someBinding, 'value', 'child logger binding is set') + t.assert.ok(line.res, 'res is defined') + t.assert.strictEqual(line.msg, 'request completed', 'message is set') + t.assert.strictEqual(line.res.statusCode, 200, 'statusCode is 200') + t.assert.ok(line.responseTime, 'responseTime is defined') }) }) }) fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) - http.get(`http://${localhostForURL}:${fastify.server.address().port}`) + http.get(`http://${localhostForURL}:${fastify.server.address().port}`, () => { + done() + }) }) }) @@ -348,24 +350,23 @@ test('deepFreezeObject() should not throw on TypedArray', t => { const frozenObject = deepFreezeObject(object) // Buffers should not be frozen, as they are Uint8Array inherited instances - t.equal(Object.isFrozen(frozenObject.buffer), false) + t.assert.strictEqual(Object.isFrozen(frozenObject.buffer), false) - t.equal(Object.isFrozen(frozenObject), true) - t.equal(Object.isFrozen(frozenObject.object), true) - t.equal(Object.isFrozen(frozenObject.object.nested), true) + t.assert.strictEqual(Object.isFrozen(frozenObject), true) + t.assert.strictEqual(Object.isFrozen(frozenObject.object), true) + t.assert.strictEqual(Object.isFrozen(frozenObject.object.nested), true) - t.pass() + t.assert.ok(true) } catch (error) { - t.fail() + t.assert.fail() } }) test('pluginTimeout should be parsed correctly', t => { const withDisabledTimeout = Fastify({ pluginTimeout: '0' }) - t.equal(withDisabledTimeout.initialConfig.pluginTimeout, 0) + t.assert.strictEqual(withDisabledTimeout.initialConfig.pluginTimeout, 0) const withInvalidTimeout = Fastify({ pluginTimeout: undefined }) - t.equal(withInvalidTimeout.initialConfig.pluginTimeout, 10000) - t.end() + t.assert.strictEqual(withInvalidTimeout.initialConfig.pluginTimeout, 10000) }) test('Should not mutate the options object outside Fastify', async t => { @@ -373,8 +374,8 @@ test('Should not mutate the options object outside Fastify', async t => { try { Fastify(options) - t.pass() + t.assert.ok(true) } catch (error) { - t.fail(error.message) + t.assert.fail(error.message) } }) diff --git a/test/internals/logger.test.js b/test/internals/logger.test.js index dfd9a1037d5..412bd1114e5 100644 --- a/test/internals/logger.test.js +++ b/test/internals/logger.test.js @@ -1,34 +1,33 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const loggerUtils = require('../../lib/logger') test('time resolution', t => { t.plan(2) - t.equal(typeof loggerUtils.now, 'function') - t.equal(typeof loggerUtils.now(), 'number') + t.assert.strictEqual(typeof loggerUtils.now, 'function') + t.assert.strictEqual(typeof loggerUtils.now(), 'number') }) -test('The logger should add a unique id for every request', t => { +test('The logger should add a unique id for every request', (t, done) => { const ids = [] const fastify = Fastify() fastify.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const queue = new Queue() for (let i = 0; i < 10; i++) { queue.add(checkId) } queue.add(() => { fastify.close() - t.end() + done() }) }) @@ -37,24 +36,24 @@ test('The logger should add a unique id for every request', t => { method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.ok(ids.indexOf(payload.id) === -1, 'the id should not be duplicated') + t.assert.ok(ids.indexOf(payload.id) === -1, 'the id should not be duplicated') ids.push(payload.id) done() }) } }) -test('The logger should not reuse request id header for req.id', t => { +test('The logger should not reuse request id header for req.id', (t, done) => { const fastify = Fastify() fastify.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', @@ -63,27 +62,27 @@ test('The logger should not reuse request id header for req.id', t => { 'Request-Id': 'request-id-1' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.ok(payload.id !== 'request-id-1', 'the request id from the header should not be returned with default configuration') - t.ok(payload.id === 'req-1') // first request id when using the default configuration + t.assert.ok(payload.id !== 'request-id-1', 'the request id from the header should not be returned with default configuration') + t.assert.ok(payload.id === 'req-1') // first request id when using the default configuration fastify.close() - t.end() + done() }) }) }) -test('The logger should reuse request id header for req.id if requestIdHeader is set', t => { +test('The logger should reuse request id header for req.id if requestIdHeader is set', (t, done) => { const fastify = Fastify({ requestIdHeader: 'request-id' }) fastify.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', @@ -92,11 +91,11 @@ test('The logger should reuse request id header for req.id if requestIdHeader is 'Request-Id': 'request-id-1' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.ok(payload.id === 'request-id-1', 'the request id from the header should be returned') + t.assert.ok(payload.id === 'request-id-1', 'the request id from the header should be returned') fastify.close() - t.end() + done() }) }) }) @@ -137,8 +136,8 @@ test('The logger should error if both stream and file destination are given', t } }) } catch (err) { - t.equal(err.code, 'FST_ERR_LOG_INVALID_DESTINATION') - t.equal(err.message, 'Cannot specify both logger.stream and logger.file options') + t.assert.strictEqual(err.code, 'FST_ERR_LOG_INVALID_DESTINATION') + t.assert.strictEqual(err.message, 'Cannot specify both logger.stream and logger.file options') } }) @@ -152,7 +151,7 @@ test('The serializer prevent fails if the request socket is undefined', t => { headers: {} }) - t.same(serialized, { + t.assert.deepStrictEqual(serialized, { method: 'GET', url: '/', version: undefined, diff --git a/test/internals/plugin.test.js b/test/internals/plugin.test.js index 56ebfd00ee5..8dc3357121a 100644 --- a/test/internals/plugin.test.js +++ b/test/internals/plugin.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const pluginUtilsPublic = require('../../lib/pluginUtils.js') const symbols = require('../../lib/symbols.js') @@ -12,8 +11,8 @@ test("shouldSkipOverride should check the 'skip-override' symbol", t => { yes[Symbol.for('skip-override')] = true - t.ok(pluginUtils.shouldSkipOverride(yes)) - t.notOk(pluginUtils.shouldSkipOverride(no)) + t.assert.ok(pluginUtils.shouldSkipOverride(yes)) + t.assert.ok(!pluginUtils.shouldSkipOverride(no)) function yes () {} function no () {} @@ -26,7 +25,7 @@ test('getPluginName should return plugin name if the file is cached', t => { require.cache[expectedPluginName] = { exports: fn } const pluginName = pluginUtilsPublic.getPluginName(fn) - t.equal(pluginName, expectedPluginName) + t.assert.strictEqual(pluginName, expectedPluginName) }) test('getPluginName should not throw when require.cache is undefined', t => { @@ -36,12 +35,12 @@ test('getPluginName should not throw when require.cache is undefined', t => { } const cache = require.cache require.cache = undefined - t.teardown(() => { + t.after(() => { require.cache = cache }) const pluginName = pluginUtilsPublic.getPluginName(example) - t.equal(pluginName, 'example') + t.assert.strictEqual(pluginName, 'example') }) test("getMeta should return the object stored with the 'plugin-meta' symbol", t => { @@ -50,7 +49,7 @@ test("getMeta should return the object stored with the 'plugin-meta' symbol", t const meta = { hello: 'world' } fn[Symbol.for('plugin-meta')] = meta - t.same(meta, pluginUtils.getMeta(fn)) + t.assert.deepStrictEqual(meta, pluginUtils.getMeta(fn)) function fn () {} }) @@ -73,9 +72,9 @@ test('checkDecorators should check if the given decorator is present in the inst try { pluginUtils.checkDecorators.call(context, fn) - t.pass('Everything ok') + t.assert.ok('Everything ok') } catch (err) { - t.fail(err) + t.assert.fail(err) } function fn () {} @@ -99,9 +98,9 @@ test('checkDecorators should check if the given decorator is present in the inst try { pluginUtils.checkDecorators.call(context, fn) - t.fail('should throw') + t.assert.fail('should throw') } catch (err) { - t.equal(err.message, "The decorator 'plugin' is not present in Request") + t.assert.strictEqual(err.message, "The decorator 'plugin' is not present in Request") } function fn () {} @@ -121,9 +120,9 @@ test('checkDecorators should accept optional decorators', t => { try { pluginUtils.checkDecorators.call(context, fn) - t.pass('Everything ok') + t.assert.ok('Everything ok') } catch (err) { - t.fail(err) + t.assert.fail(err) } function fn () {} @@ -141,9 +140,9 @@ test('checkDependencies should check if the given dependency is present in the i try { pluginUtils.checkDependencies.call(context, fn) - t.pass('Everything ok') + t.assert.ok('Everything ok') } catch (err) { - t.fail(err) + t.assert.fail(err) } function fn () {} @@ -162,9 +161,9 @@ test('checkDependencies should check if the given dependency is present in the i try { pluginUtils.checkDependencies.call(context, fn) - t.fail('should throw') + t.assert.fail('should throw') } catch (err) { - t.equal(err.message, "The dependency 'plugin' of plugin 'test-plugin' is not registered") + t.assert.strictEqual(err.message, "The dependency 'plugin' of plugin 'test-plugin' is not registered") } function fn () {} diff --git a/test/internals/reply-serialize.test.js b/test/internals/reply-serialize.test.js index 9a67fad5b65..24606d39808 100644 --- a/test/internals/reply-serialize.test.js +++ b/test/internals/reply-serialize.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const { kReplyCacheSerializeFns, kRouteContext } = require('../../lib/symbols') const Fastify = require('../../fastify') @@ -62,10 +62,10 @@ function getResponseSchema () { } } -test('Reply#compileSerializationSchema', t => { +test('Reply#compileSerializationSchema', async t => { t.plan(4) - t.test('Should return a serialization function', async t => { + await t.test('Should return a serialization function', async t => { const fastify = Fastify() t.plan(4) @@ -73,14 +73,14 @@ test('Reply#compileSerializationSchema', t => { fastify.get('/', (req, reply) => { const serialize = reply.compileSerializationSchema(getDefaultSchema()) const input = { hello: 'world' } - t.type(serialize, Function) - t.type(serialize(input), 'string') - t.equal(serialize(input), JSON.stringify(input)) + t.assert.ok(serialize instanceof Function) + t.assert.ok(typeof serialize(input) === 'string') + t.assert.strictEqual(serialize(input), JSON.stringify(input)) try { serialize({ world: 'foo' }) } catch (err) { - t.equal(err.message, '"hello" is required!') + t.assert.strictEqual(err.message, '"hello" is required!') } reply.send({ hello: 'world' }) @@ -92,7 +92,7 @@ test('Reply#compileSerializationSchema', t => { }) }) - t.test('Should reuse the serialize fn across multiple invocations - Route without schema', + await t.test('Should reuse the serialize fn across multiple invocations - Route without schema', async t => { const fastify = Fastify() let serialize = null @@ -107,20 +107,20 @@ test('Reply#compileSerializationSchema', t => { counter++ if (counter > 1) { const newSerialize = reply.compileSerializationSchema(schemaObj) - t.equal(serialize, newSerialize, 'Are the same validate function') + t.assert.strictEqual(serialize, newSerialize, 'Are the same validate function') serialize = newSerialize } else { - t.pass('build the schema compilation function') + t.assert.ok(true, 'build the schema compilation function') serialize = reply.compileSerializationSchema(schemaObj) } - t.type(serialize, Function) - t.equal(serialize(input), JSON.stringify(input)) + t.assert.ok(serialize instanceof Function) + t.assert.strictEqual(serialize(input), JSON.stringify(input)) try { serialize({ world: 'foo' }) } catch (err) { - t.equal(err.message, '"hello" is required!') + t.assert.strictEqual(err.message, '"hello" is required!') } reply.send({ hello: 'world' }) @@ -133,36 +133,36 @@ test('Reply#compileSerializationSchema', t => { fastify.inject('/') ]) - t.equal(counter, 4) + t.assert.strictEqual(counter, 4) } ) - t.test('Should use the custom serializer compiler for the route', + await t.test('Should use the custom serializer compiler for the route', async t => { const fastify = Fastify() let called = 0 const custom = ({ schema, httpStatus, url, method }) => { - t.equal(schema, schemaObj) - t.equal(url, '/') - t.equal(method, 'GET') - t.equal(httpStatus, '201') + t.assert.strictEqual(schema, schemaObj) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpStatus, '201') return input => { called++ - t.same(input, { hello: 'world' }) + t.assert.deepStrictEqual(input, { hello: 'world' }) return JSON.stringify(input) } } const custom2 = ({ schema, httpStatus, url, method, contentType }) => { - t.equal(schema, schemaObj) - t.equal(url, '/user') - t.equal(method, 'GET') - t.equal(httpStatus, '3xx') - t.equal(contentType, 'application/json') + t.assert.strictEqual(schema, schemaObj) + t.assert.strictEqual(url, '/user') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpStatus, '3xx') + t.assert.strictEqual(contentType, 'application/json') return input => { - t.same(input, { fullName: 'Jone', phone: 1090243795 }) + t.assert.deepStrictEqual(input, { fullName: 'Jone', phone: 1090243795 }) return JSON.stringify(input) } } @@ -175,10 +175,10 @@ test('Reply#compileSerializationSchema', t => { const first = reply.compileSerializationSchema(schemaObj, '201') const second = reply.compileSerializationSchema(schemaObj, '201') - t.equal(first, second) - t.ok(first(input), JSON.stringify(input)) - t.ok(second(input), JSON.stringify(input)) - t.equal(called, 2) + t.assert.strictEqual(first, second) + t.assert.ok(first(input), JSON.stringify(input)) + t.assert.ok(second(input), JSON.stringify(input)) + t.assert.strictEqual(called, 2) reply.send({ hello: 'world' }) }) @@ -186,7 +186,7 @@ test('Reply#compileSerializationSchema', t => { fastify.get('/user', { serializerCompiler: custom2 }, (req, reply) => { const input = { fullName: 'Jone', phone: 1090243795 } const first = reply.compileSerializationSchema(schemaObj, '3xx', 'application/json') - t.ok(first(input), JSON.stringify(input)) + t.assert.ok(first(input), JSON.stringify(input)) reply.send(input) }) @@ -202,7 +202,7 @@ test('Reply#compileSerializationSchema', t => { } ) - t.test('Should build a WeakMap for cache when called', async t => { + await t.test('Should build a WeakMap for cache when called', async t => { const fastify = Fastify() t.plan(4) @@ -210,10 +210,10 @@ test('Reply#compileSerializationSchema', t => { fastify.get('/', (req, reply) => { const input = { hello: 'world' } - t.equal(reply[kRouteContext][kReplyCacheSerializeFns], null) - t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) - t.type(reply[kRouteContext][kReplyCacheSerializeFns], WeakMap) - t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) + t.assert.strictEqual(reply[kRouteContext][kReplyCacheSerializeFns], null) + t.assert.strictEqual(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) + t.assert.ok(reply[kRouteContext][kReplyCacheSerializeFns] instanceof WeakMap) + t.assert.strictEqual(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) reply.send({ hello: 'world' }) }) @@ -225,10 +225,10 @@ test('Reply#compileSerializationSchema', t => { }) }) -test('Reply#getSerializationFunction', t => { +test('Reply#getSerializationFunction', async t => { t.plan(3) - t.test('Should retrieve the serialization function from the Schema definition', + await t.test('Should retrieve the serialization function from the Schema definition', async t => { const fastify = Fastify() const okInput201 = { @@ -291,18 +291,18 @@ test('Reply#getSerializationFunction', t => { cached201 = serialize201 cachedJson3xx = serializeJson3xx - t.type(serialize4xx, Function) - t.type(serialize201, Function) - t.type(serializeJson3xx, Function) - t.equal(serialize4xx(okInput4xx), JSON.stringify(okInput4xx)) - t.equal(serialize201(okInput201), JSON.stringify(okInput201)) - t.equal(serializeJson3xx(okInput3xx), JSON.stringify(okInput3xx)) - t.notOk(serializeUndefined) + t.assert.ok(serialize4xx instanceof Function) + t.assert.ok(serialize201 instanceof Function) + t.assert.ok(serializeJson3xx instanceof Function) + t.assert.strictEqual(serialize4xx(okInput4xx), JSON.stringify(okInput4xx)) + t.assert.strictEqual(serialize201(okInput201), JSON.stringify(okInput201)) + t.assert.strictEqual(serializeJson3xx(okInput3xx), JSON.stringify(okInput3xx)) + t.assert.ok(!serializeUndefined) try { serialize4xx(notOkInput4xx) } catch (err) { - t.equal( + t.assert.strictEqual( err.message, 'The value "something" cannot be converted to an integer.' ) @@ -311,13 +311,13 @@ test('Reply#getSerializationFunction', t => { try { serialize201(notOkInput201) } catch (err) { - t.equal(err.message, '"status" is required!') + t.assert.strictEqual(err.message, '"status" is required!') } try { serializeJson3xx(noOkInput3xx) } catch (err) { - t.equal(err.message, 'The value "phone" cannot be converted to a number.') + t.assert.strictEqual(err.message, 'The value "phone" cannot be converted to a number.') } reply.status(201).send(okInput201) @@ -326,9 +326,9 @@ test('Reply#getSerializationFunction', t => { const serialize4xx = reply.getSerializationFunction('4xx') const serializeJson3xx = reply.getSerializationFunction('3xx', 'application/json') - t.equal(serialize4xx, cached4xx) - t.equal(serialize201, cached201) - t.equal(serializeJson3xx, cachedJson3xx) + t.assert.strictEqual(serialize4xx, cached4xx) + t.assert.strictEqual(serialize201, cached201) + t.assert.strictEqual(serializeJson3xx, cachedJson3xx) reply.status(401).send(okInput4xx) } } @@ -341,7 +341,7 @@ test('Reply#getSerializationFunction', t => { } ) - t.test('Should retrieve the serialization function from the cached one', + await t.test('Should retrieve the serialization function from the cached one', async t => { const fastify = Fastify() @@ -376,26 +376,26 @@ test('Reply#getSerializationFunction', t => { if (Number(id) === 1) { const serialize = reply.compileSerializationSchema(schemaObj) - t.type(serialize, Function) - t.equal(serialize(okInput), JSON.stringify(okInput)) + t.assert.ok(serialize instanceof Function) + t.assert.strictEqual(serialize(okInput), JSON.stringify(okInput)) try { serialize(notOkInput) } catch (err) { - t.equal(err.message, '"hello" is required!') + t.assert.strictEqual(err.message, '"hello" is required!') } cached = serialize } else { const serialize = reply.getSerializationFunction(schemaObj) - t.equal(serialize, cached) - t.equal(serialize(okInput), JSON.stringify(okInput)) + t.assert.strictEqual(serialize, cached) + t.assert.strictEqual(serialize(okInput), JSON.stringify(okInput)) try { serialize(notOkInput) } catch (err) { - t.equal(err.message, '"hello" is required!') + t.assert.strictEqual(err.message, '"hello" is required!') } } @@ -410,16 +410,16 @@ test('Reply#getSerializationFunction', t => { } ) - t.test('Should not instantiate a WeakMap if it is not needed', async t => { + await t.test('Should not instantiate a WeakMap if it is not needed', async t => { const fastify = Fastify() t.plan(4) fastify.get('/', (req, reply) => { - t.notOk(reply.getSerializationFunction(getDefaultSchema())) - t.equal(reply[kRouteContext][kReplyCacheSerializeFns], null) - t.notOk(reply.getSerializationFunction('200')) - t.equal(reply[kRouteContext][kReplyCacheSerializeFns], null) + t.assert.ok(!reply.getSerializationFunction(getDefaultSchema())) + t.assert.strictEqual(reply[kRouteContext][kReplyCacheSerializeFns], null) + t.assert.ok(!reply.getSerializationFunction('200')) + t.assert.strictEqual(reply[kRouteContext][kReplyCacheSerializeFns], null) reply.send({ hello: 'world' }) }) @@ -431,10 +431,10 @@ test('Reply#getSerializationFunction', t => { }) }) -test('Reply#serializeInput', t => { +test('Reply#serializeInput', async t => { t.plan(6) - t.test( + await t.test( 'Should throw if missed serialization function from HTTP status', async t => { const fastify = Fastify() @@ -450,8 +450,8 @@ test('Reply#serializeInput', t => { method: 'GET' }) - t.equal(result.statusCode, 500) - t.same(result.json(), { + t.assert.strictEqual(result.statusCode, 500) + t.assert.deepStrictEqual(result.json(), { statusCode: 500, code: 'FST_ERR_MISSING_SERIALIZATION_FN', error: 'Internal Server Error', @@ -460,7 +460,7 @@ test('Reply#serializeInput', t => { } ) - t.test( + await t.test( 'Should throw if missed serialization function from HTTP status with specific content type', async t => { const fastify = Fastify() @@ -494,8 +494,8 @@ test('Reply#serializeInput', t => { method: 'GET' }) - t.equal(result.statusCode, 500) - t.same(result.json(), { + t.assert.strictEqual(result.statusCode, 500) + t.assert.deepStrictEqual(result.json(), { statusCode: 500, code: 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN', error: 'Internal Server Error', @@ -504,7 +504,7 @@ test('Reply#serializeInput', t => { } ) - t.test('Should use a serializer fn from HTTP status', async t => { + await t.test('Should use a serializer fn from HTTP status', async t => { const fastify = Fastify() const okInput201 = { status: 'ok', @@ -549,16 +549,16 @@ test('Reply#serializeInput', t => { } }, (req, reply) => { - t.equal( + t.assert.strictEqual( reply.serializeInput(okInput4xx, '4xx'), JSON.stringify(okInput4xx) ) - t.equal( + t.assert.strictEqual( reply.serializeInput(okInput201, 201), JSON.stringify(okInput201) ) - t.equal( + t.assert.strictEqual( reply.serializeInput(okInput3xx, {}, '3xx', 'application/json'), JSON.stringify(okInput3xx) ) @@ -566,13 +566,13 @@ test('Reply#serializeInput', t => { try { reply.serializeInput(noOkInput3xx, '3xx', 'application/json') } catch (err) { - t.equal(err.message, 'The value "phone" cannot be converted to a number.') + t.assert.strictEqual(err.message, 'The value "phone" cannot be converted to a number.') } try { reply.serializeInput(notOkInput4xx, '4xx') } catch (err) { - t.equal( + t.assert.strictEqual( err.message, 'The value "something" cannot be converted to an integer.' ) @@ -581,7 +581,7 @@ test('Reply#serializeInput', t => { try { reply.serializeInput(notOkInput201, 201) } catch (err) { - t.equal(err.message, '"status" is required!') + t.assert.strictEqual(err.message, '"status" is required!') } reply.status(204).send('') @@ -594,7 +594,7 @@ test('Reply#serializeInput', t => { }) }) - t.test( + await t.test( 'Should compile a serializer out of a schema if serializer fn missed', async t => { let compilerCalled = 0 @@ -603,14 +603,14 @@ test('Reply#serializeInput', t => { const schemaObj = getDefaultSchema() const fastify = Fastify() const serializerCompiler = ({ schema, httpStatus, method, url }) => { - t.equal(schema, schemaObj) - t.notOk(httpStatus) - t.equal(method, 'GET') - t.equal(url, '/') + t.assert.strictEqual(schema, schemaObj) + t.assert.ok(!httpStatus) + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(url, '/') compilerCalled++ return input => { - t.equal(input, testInput) + t.assert.strictEqual(input, testInput) serializerCalled++ return JSON.stringify(input) } @@ -619,12 +619,12 @@ test('Reply#serializeInput', t => { t.plan(10) fastify.get('/', { serializerCompiler }, (req, reply) => { - t.equal( + t.assert.strictEqual( reply.serializeInput(testInput, schemaObj), JSON.stringify(testInput) ) - t.equal( + t.assert.strictEqual( reply.serializeInput(testInput, schemaObj), JSON.stringify(testInput) ) @@ -637,12 +637,12 @@ test('Reply#serializeInput', t => { method: 'GET' }) - t.equal(compilerCalled, 1) - t.equal(serializerCalled, 2) + t.assert.strictEqual(compilerCalled, 1) + t.assert.strictEqual(serializerCalled, 2) } ) - t.test('Should use a cached serializer fn', async t => { + await t.test('Should use a cached serializer fn', async t => { let compilerCalled = 0 let serializerCalled = 0 let cached @@ -650,15 +650,15 @@ test('Reply#serializeInput', t => { const schemaObj = getDefaultSchema() const fastify = Fastify() const serializer = input => { - t.equal(input, testInput) + t.assert.strictEqual(input, testInput) serializerCalled++ return JSON.stringify(input) } const serializerCompiler = ({ schema, httpStatus, method, url }) => { - t.equal(schema, schemaObj) - t.notOk(httpStatus) - t.equal(method, 'GET') - t.equal(url, '/') + t.assert.strictEqual(schema, schemaObj) + t.assert.ok(!httpStatus) + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(url, '/') compilerCalled++ return serializer @@ -667,14 +667,14 @@ test('Reply#serializeInput', t => { t.plan(12) fastify.get('/', { serializerCompiler }, (req, reply) => { - t.equal( + t.assert.strictEqual( reply.serializeInput(testInput, schemaObj), JSON.stringify(testInput) ) cached = reply.getSerializationFunction(schemaObj) - t.equal( + t.assert.strictEqual( reply.serializeInput(testInput, schemaObj), cached(testInput) ) @@ -687,21 +687,21 @@ test('Reply#serializeInput', t => { method: 'GET' }) - t.equal(cached, serializer) - t.equal(compilerCalled, 1) - t.equal(serializerCalled, 3) + t.assert.strictEqual(cached, serializer) + t.assert.strictEqual(compilerCalled, 1) + t.assert.strictEqual(serializerCalled, 3) }) - t.test('Should instantiate a WeakMap after first call', async t => { + await t.test('Should instantiate a WeakMap after first call', async t => { const fastify = Fastify() t.plan(3) fastify.get('/', (req, reply) => { const input = { hello: 'world' } - t.equal(reply[kRouteContext][kReplyCacheSerializeFns], null) - t.equal(reply.serializeInput(input, getDefaultSchema()), JSON.stringify(input)) - t.type(reply[kRouteContext][kReplyCacheSerializeFns], WeakMap) + t.assert.strictEqual(reply[kRouteContext][kReplyCacheSerializeFns], null) + t.assert.strictEqual(reply.serializeInput(input, getDefaultSchema()), JSON.stringify(input)) + t.assert.ok(reply[kRouteContext][kReplyCacheSerializeFns] instanceof WeakMap) reply.send({ hello: 'world' }) }) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 8f78b11ae01..c849869f867 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const http = require('node:http') const NotFound = require('http-errors').NotFound @@ -40,22 +39,22 @@ test('Once called, Reply should return an object with methods', t => { const context = { config: { onSend: [] }, schema: {}, _parserOptions: {}, server: { hasConstraintStrategy: () => false, initialConfig: {} } } const request = new Request(null, null, null, null, null, context) const reply = new Reply(response, request) - t.equal(typeof reply, 'object') - t.equal(typeof reply[kReplyIsError], 'boolean') - t.equal(typeof reply[kReplyErrorHandlerCalled], 'boolean') - t.equal(typeof reply.send, 'function') - t.equal(typeof reply.code, 'function') - t.equal(typeof reply.status, 'function') - t.equal(typeof reply.header, 'function') - t.equal(typeof reply.serialize, 'function') - t.equal(typeof reply[kReplyHeaders], 'object') - t.same(reply.raw, response) - t.equal(reply[kRouteContext], context) - t.equal(reply.routeOptions.config, context.config) - t.equal(reply.routeOptions.schema, context.schema) - t.equal(reply.request, request) + t.assert.strictEqual(typeof reply, 'object') + t.assert.strictEqual(typeof reply[kReplyIsError], 'boolean') + t.assert.strictEqual(typeof reply[kReplyErrorHandlerCalled], 'boolean') + t.assert.strictEqual(typeof reply.send, 'function') + t.assert.strictEqual(typeof reply.code, 'function') + t.assert.strictEqual(typeof reply.status, 'function') + t.assert.strictEqual(typeof reply.header, 'function') + t.assert.strictEqual(typeof reply.serialize, 'function') + t.assert.strictEqual(typeof reply[kReplyHeaders], 'object') + t.assert.deepStrictEqual(reply.raw, response) + t.assert.strictEqual(reply[kRouteContext], context) + t.assert.strictEqual(reply.routeOptions.config, context.config) + t.assert.strictEqual(reply.routeOptions.schema, context.schema) + t.assert.strictEqual(reply.request, request) // Aim to not bad property keys (including Symbols) - t.notOk('undefined' in reply) + t.assert.ok(!('undefined' in reply)) }) test('reply.send will logStream error and destroy the stream', t => { @@ -87,7 +86,7 @@ test('reply.send will logStream error and destroy the stream', t => { reply.send(payload) payload.destroy(new Error('stream error')) - t.equal(destroyCalled, true, 'Error not logged and not streamed') + t.assert.strictEqual(destroyCalled, true, 'Error not logged and not streamed') }) test('reply.send throw with circular JSON', t => { @@ -101,7 +100,7 @@ test('reply.send throw with circular JSON', t => { end: () => { } } const reply = new Reply(response, { [kRouteContext]: { onSend: [] } }) - t.throws(() => { + t.assert.throws(() => { const obj = {} obj.obj = obj reply.send(JSON.stringify(obj)) @@ -119,23 +118,23 @@ test('reply.send returns itself', t => { end: () => { } } const reply = new Reply(response, { [kRouteContext]: { onSend: [] } }) - t.equal(reply.send('hello'), reply) + t.assert.strictEqual(reply.send('hello'), reply) }) test('reply.serializer should set a custom serializer', t => { t.plan(2) const reply = new Reply(null, null, null) - t.equal(reply[kReplySerializer], null) + t.assert.strictEqual(reply[kReplySerializer], null) reply.serializer('serializer') - t.equal(reply[kReplySerializer], 'serializer') + t.assert.strictEqual(reply[kReplySerializer], 'serializer') }) -test('reply.serializer should support running preSerialization hooks', t => { +test('reply.serializer should support running preSerialization hooks', (t, done) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) - fastify.addHook('preSerialization', async (request, reply, payload) => { t.ok('called', 'preSerialization') }) + fastify.addHook('preSerialization', async (request, reply, payload) => { t.assert.ok('called', 'preSerialization') }) fastify.route({ method: 'GET', url: '/', @@ -151,8 +150,9 @@ test('reply.serializer should support running preSerialization hooks', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"foo":"bar"}') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '{"foo":"bar"}') + done() }) }) @@ -161,7 +161,7 @@ test('reply.serialize should serialize payload', t => { const response = { statusCode: 200 } const context = {} const reply = new Reply(response, { [kRouteContext]: context }) - t.equal(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') + t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') }) test('reply.serialize should serialize payload with a custom serializer', t => { @@ -171,8 +171,8 @@ test('reply.serialize should serialize payload with a custom serializer', t => { const context = {} const reply = new Reply(response, { [kRouteContext]: context }) reply.serializer((x) => (customSerializerCalled = true) && JSON.stringify(x)) - t.equal(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') - t.equal(customSerializerCalled, true, 'custom serializer not called') + t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') + t.assert.strictEqual(customSerializerCalled, true, 'custom serializer not called') }) test('reply.serialize should serialize payload with a context default serializer', t => { @@ -181,14 +181,14 @@ test('reply.serialize should serialize payload with a context default serializer const response = { statusCode: 200 } const context = { [kReplySerializerDefault]: (x) => (customSerializerCalled = true) && JSON.stringify(x) } const reply = new Reply(response, { [kRouteContext]: context }) - t.equal(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') - t.equal(customSerializerCalled, true, 'custom serializer not called') + t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') + t.assert.strictEqual(customSerializerCalled, true, 'custom serializer not called') }) -test('reply.serialize should serialize payload with Fastify instance', t => { +test('reply.serialize should serialize payload with Fastify instance', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.route({ method: 'GET', url: '/', @@ -213,15 +213,15 @@ test('reply.serialize should serialize payload with Fastify instance', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"foo":"bar"}') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '{"foo":"bar"}') + done() }) }) -test('within an instance', t => { +test('within an instance', async t => { const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - const test = t.test + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { reply.code(200) @@ -279,166 +279,175 @@ test('within an instance', t => { done() }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + await fastify.listen({ port: 0 }) - test('custom serializer should be used', t => { - t.plan(3) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/custom-serializer' - }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body.toString(), 'hello=world!') - }) + await t.test('custom serializer should be used', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/custom-serializer' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body.toString(), 'hello=world!') + done() }) + }) - test('status code and content-type should be correct', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body.toString(), 'hello world!') - }) + await t.test('status code and content-type should be correct', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'http://127.0.0.1:' + fastify.server.address().port + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) + }) - test('auto status code should be 200', t => { - t.plan(3) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/auto-status-code' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello world!') - }) + await t.test('auto status code should be 200', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/auto-status-code' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) + }) - test('auto type should be text/plain', t => { - t.plan(3) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/auto-type' - }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body.toString(), 'hello world!') - }) + await t.test('auto type should be text/plain', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/auto-type' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) + }) - test('redirect to `/` - 1', t => { - t.plan(1) + await t.test('redirect to `/` - 1', (t, done) => { + t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect', function (response) { - t.equal(response.statusCode, 302) - }) + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect', function (response) { + t.assert.strictEqual(response.statusCode, 302) + done() }) + }) - test('redirect to `/` - 2', t => { - t.plan(1) + await t.test('redirect to `/` - 2', (t, done) => { + t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code', function (response) { - t.equal(response.statusCode, 301) - }) + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code', function (response) { + t.assert.strictEqual(response.statusCode, 301) + done() }) + }) - test('redirect to `/` - 3', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body.toString(), 'hello world!') - }) + await t.test('redirect to `/` - 3', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) + }) - test('redirect to `/` - 4', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body.toString(), 'hello world!') - }) + await t.test('redirect to `/` - 4', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) + }) - test('redirect to `/` - 5', t => { - t.plan(3) - const url = 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-onsend' - http.get(url, (response) => { - t.equal(response.headers['x-onsend'], 'yes') - t.equal(response.headers['content-length'], '0') - t.equal(response.headers.location, '/') - }) + await t.test('redirect to `/` - 5', (t, done) => { + t.plan(3) + const url = 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-onsend' + http.get(url, (response) => { + t.assert.strictEqual(response.headers['x-onsend'], 'yes') + t.assert.strictEqual(response.headers['content-length'], '0') + t.assert.strictEqual(response.headers.location, '/') + done() }) + }) - test('redirect to `/` - 6', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body.toString(), 'hello world!') - }) + await t.test('redirect to `/` - 6', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) + }) - test('redirect to `/` - 7', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call-overwrite' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body.toString(), 'hello world!') - }) + await t.test('redirect to `/` - 7', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call-overwrite' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) + }) - test('redirect to `/` - 8', t => { - t.plan(1) + await t.test('redirect to `/` - 8', (t, done) => { + t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call', function (response) { - t.equal(response.statusCode, 307) - }) + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call', function (response) { + t.assert.strictEqual(response.statusCode, 307) + done() }) + }) - test('redirect to `/` - 9', t => { - t.plan(1) + await t.test('redirect to `/` - 9', (t, done) => { + t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call-overwrite', function (response) { - t.equal(response.statusCode, 302) - }) + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call-overwrite', function (response) { + t.assert.strictEqual(response.statusCode, 302) + done() }) + }) - test('redirect with async function to `/` - 10', t => { - t.plan(1) + await t.test('redirect with async function to `/` - 10', (t, done) => { + t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-async', function (response) { - t.equal(response.statusCode, 302) - }) + http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-async', function (response) { + t.assert.strictEqual(response.statusCode, 302) + done() }) - - t.end() }) }) -test('buffer without content type should send a application/octet-stream and raw buffer', t => { +test('buffer without content type should send a application/octet-stream and raw buffer', (t, done) => { t.plan(4) const fastify = Fastify() @@ -448,20 +457,22 @@ test('buffer without content type should send a application/octet-stream and raw }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'application/octet-stream') - t.same(body, Buffer.alloc(1024)) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/octet-stream') + t.assert.deepStrictEqual(body, Buffer.alloc(1024)) + done() }) }) }) -test('Uint8Array without content type should send a application/octet-stream and raw buffer', t => { + +test('Uint8Array without content type should send a application/octet-stream and raw buffer', (t, done) => { t.plan(4) const fastify = Fastify() @@ -471,20 +482,21 @@ test('Uint8Array without content type should send a application/octet-stream and }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) fastify.inject({ method: 'GET', url: '/' }, (err, response) => { - t.error(err) - t.equal(response.headers['content-type'], 'application/octet-stream') - t.same(new Uint8Array(response.rawPayload), new Uint8Array(1024).fill(0xff)) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/octet-stream') + t.assert.deepStrictEqual(new Uint8Array(response.rawPayload), new Uint8Array(1024).fill(0xff)) + done() }) }) }) -test('Uint16Array without content type should send a application/octet-stream and raw buffer', t => { +test('Uint16Array without content type should send a application/octet-stream and raw buffer', (t, done) => { t.plan(4) const fastify = Fastify() @@ -494,20 +506,21 @@ test('Uint16Array without content type should send a application/octet-stream an }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.same(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(50).fill(0xffffffff)) + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.deepStrictEqual(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(50).fill(0xffffffff)) + done() }) }) }) -test('TypedArray with content type should not send application/octet-stream', t => { +test('TypedArray with content type should not send application/octet-stream', (t, done) => { t.plan(4) const fastify = Fastify() @@ -518,20 +531,21 @@ test('TypedArray with content type should not send application/octet-stream', t }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'text/plain') - t.same(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(1024).fill(0xffffffff)) + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(1024).fill(0xffffffff)) + done() }) }) }) -test('buffer with content type should not send application/octet-stream', t => { +test('buffer with content type should not send application/octet-stream', (t, done) => { t.plan(4) const fastify = Fastify() @@ -542,21 +556,22 @@ test('buffer with content type should not send application/octet-stream', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body, Buffer.alloc(1024)) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body, Buffer.alloc(1024)) + done() }) }) }) -test('stream with content type should not send application/octet-stream', t => { +test('stream with content type should not send application/octet-stream', (t, done) => { t.plan(4) const fastify = Fastify() @@ -570,20 +585,21 @@ test('stream with content type should not send application/octet-stream', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'text/plain') - t.same(body, buf) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(body, buf) + done() }) }) }) -test('stream without content type should not send application/octet-stream', t => { +test('stream without content type should not send application/octet-stream', (t, done) => { t.plan(4) const fastify = Fastify() @@ -596,20 +612,21 @@ test('stream without content type should not send application/octet-stream', t = }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], undefined) - t.same(body, buf) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], undefined) + t.assert.deepStrictEqual(body, buf) + done() }) }) }) -test('stream using reply.raw.writeHead should return customize headers', t => { +test('stream using reply.raw.writeHead should return customize headers', (t, done) => { t.plan(6) const fastify = Fastify() @@ -622,7 +639,7 @@ test('stream using reply.raw.writeHead should return customize headers', t => { fastify.get('/', function (req, reply) { reply.log.warn = function mockWarn (message) { - t.equal(message, 'response will send, but you shouldn\'t use res.writeHead in stream mode') + t.assert.strictEqual(message, 'response will send, but you shouldn\'t use res.writeHead in stream mode') } reply.raw.writeHead(200, { location: '/' @@ -631,21 +648,22 @@ test('stream using reply.raw.writeHead should return customize headers', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers.location, '/') - t.equal(response.headers['Content-Type'], undefined) - t.same(body, buf) + t.assert.ifError(err) + t.assert.strictEqual(response.headers.location, '/') + t.assert.strictEqual(response.headers['Content-Type'], undefined) + t.assert.deepStrictEqual(body, buf) + done() }) }) }) -test('plain string without content type should send a text/plain', t => { +test('plain string without content type should send a text/plain', (t, done) => { t.plan(4) const fastify = Fastify() @@ -655,21 +673,22 @@ test('plain string without content type should send a text/plain', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'text/plain; charset=utf-8') - t.same(body.toString(), 'hello world!') + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) }) }) -test('plain string with content type should be sent unmodified', t => { +test('plain string with content type should be sent unmodified', (t, done) => { t.plan(4) const fastify = Fastify() @@ -679,21 +698,22 @@ test('plain string with content type should be sent unmodified', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'text/css') - t.same(body.toString(), 'hello world!') + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'text/css') + t.assert.deepStrictEqual(body.toString(), 'hello world!') + done() }) }) }) -test('plain string with content type and custom serializer should be serialized', t => { +test('plain string with content type and custom serializer should be serialized', (t, done) => { t.plan(4) const fastify = Fastify() @@ -706,21 +726,22 @@ test('plain string with content type and custom serializer should be serialized' }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'text/css') - t.same(body.toString(), 'serialized') + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'text/css') + t.assert.deepStrictEqual(body.toString(), 'serialized') + done() }) }) }) -test('plain string with content type application/json should NOT be serialized as json', t => { +test('plain string with content type application/json should NOT be serialized as json', (t, done) => { t.plan(4) const fastify = Fastify() @@ -730,25 +751,28 @@ test('plain string with content type application/json should NOT be serialized a }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.same(body.toString(), '{"key": "hello world!"}') + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(body.toString(), '{"key": "hello world!"}') + done() }) }) }) -test('plain string with custom json content type should NOT be serialized as json', t => { - t.plan(19) +test('plain string with custom json content type should NOT be serialized as json', async t => { + t.plan(12) const fastify = Fastify() + t.after(() => fastify.close()) + const customSamples = { collectionjson: { mimeType: 'application/vnd.collection+json', @@ -782,24 +806,26 @@ test('plain string with custom json content type should NOT be serialized as jso }) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + await fastify.listen({ port: 0 }) - Object.keys(customSamples).forEach((path) => { + await Promise.all(Object.keys(customSamples).map(path => { + return new Promise((resolve, reject) => { sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/' + path }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8') - t.same(body.toString(), customSamples[path].sample) + if (err) { + reject(err) + } + t.assert.strictEqual(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8') + t.assert.deepStrictEqual(body.toString(), customSamples[path].sample) + resolve() }) }) - }) + })) }) -test('non-string with content type application/json SHOULD be serialized as json', t => { +test('non-string with content type application/json SHOULD be serialized as json', (t, done) => { t.plan(4) const fastify = Fastify() @@ -809,21 +835,22 @@ test('non-string with content type application/json SHOULD be serialized as json }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.same(body.toString(), JSON.stringify({ key: 'hello world!' })) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ key: 'hello world!' })) + done() }) }) }) -test('non-string with custom json\'s content-type SHOULD be serialized as json', t => { +test('non-string with custom json\'s content-type SHOULD be serialized as json', (t, done) => { t.plan(4) const fastify = Fastify() @@ -833,24 +860,26 @@ test('non-string with custom json\'s content-type SHOULD be serialized as json', }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], 'application/json; version=2; charset=utf-8') - t.same(body.toString(), JSON.stringify({ key: 'hello world!' })) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/json; version=2; charset=utf-8') + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ key: 'hello world!' })) + done() }) }) }) -test('non-string with custom json content type SHOULD be serialized as json', t => { - t.plan(16) +test('non-string with custom json content type SHOULD be serialized as json', async t => { + t.plan(10) const fastify = Fastify() + t.after(() => fastify.close()) const customSamples = { collectionjson: { @@ -881,25 +910,27 @@ test('non-string with custom json content type SHOULD be serialized as json', t }) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + await fastify.listen({ port: 0 }) - Object.keys(customSamples).forEach((path) => { + await Promise.all(Object.keys(customSamples).map(path => { + return new Promise((resolve, reject) => { sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/' + path }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8') - t.same(body.toString(), JSON.stringify(customSamples[path].sample)) + if (err) { + reject(err) + } + t.assert.strictEqual(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8') + t.assert.deepStrictEqual(body.toString(), JSON.stringify(customSamples[path].sample)) + resolve() }) }) - }) + })) }) -test('error object with a content type that is not application/json should work', t => { - t.plan(6) +test('error object with a content type that is not application/json should work', async t => { + t.plan(4) const fastify = Fastify() @@ -913,32 +944,32 @@ test('error object with a content type that is not application/json should work' reply.send(new Error('some application error')) }) - fastify.inject({ - method: 'GET', - url: '/text' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal(JSON.parse(res.payload).message, 'some application error') - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/text' + }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(JSON.parse(res.payload).message, 'some application error') + } - fastify.inject({ - method: 'GET', - url: '/html' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal(JSON.parse(res.payload).message, 'some application error') - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/html' + }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(JSON.parse(res.payload).message, 'some application error') + } }) -test('undefined payload should be sent as-is', t => { +test('undefined payload should be sent as-is', (t, done) => { t.plan(6) const fastify = Fastify() fastify.addHook('onSend', function (request, reply, payload, done) { - t.equal(payload, undefined) + t.assert.strictEqual(payload, undefined) done() }) @@ -947,31 +978,33 @@ test('undefined payload should be sent as-is', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: `http://127.0.0.1:${fastify.server.address().port}` }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], undefined) - t.equal(response.headers['content-length'], undefined) - t.equal(body.length, 0) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], undefined) + t.assert.strictEqual(response.headers['content-length'], undefined) + t.assert.strictEqual(body.length, 0) + done() }) }) }) -test('for HEAD method, no body should be sent but content-length should be', t => { - t.plan(11) +test('for HEAD method, no body should be sent but content-length should be', async t => { + t.plan(8) const fastify = Fastify() + t.after(() => fastify.close()) const contentType = 'application/json; charset=utf-8' const bodySize = JSON.stringify({ foo: 'bar' }).length fastify.head('/', { onSend: function (request, reply, payload, done) { - t.equal(payload, undefined) + t.assert.strictEqual(payload, undefined) done() } }, function (req, reply) { @@ -982,7 +1015,7 @@ test('for HEAD method, no body should be sent but content-length should be', t = fastify.head('/with/null', { onSend: function (request, reply, payload, done) { - t.equal(payload, 'null') + t.assert.strictEqual(payload, 'null') done() } }, function (req, reply) { @@ -991,36 +1024,46 @@ test('for HEAD method, no body should be sent but content-length should be', t = reply.code(200).send(null) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + await fastify.listen({ port: 0 }) + const promise1 = new Promise((resolve, reject) => { sget({ method: 'HEAD', url: `http://127.0.0.1:${fastify.server.address().port}` }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], contentType) - t.equal(response.headers['content-length'], bodySize.toString()) - t.equal(body.length, 0) + if (err) { + reject(err) + } + t.assert.strictEqual(response.headers['content-type'], contentType) + t.assert.strictEqual(response.headers['content-length'], bodySize.toString()) + t.assert.strictEqual(body.length, 0) + resolve() }) + }) + const promise2 = new Promise((resolve, reject) => { sget({ method: 'HEAD', url: `http://127.0.0.1:${fastify.server.address().port}/with/null` }, (err, response, body) => { - t.error(err) - t.equal(response.headers['content-type'], contentType) - t.equal(response.headers['content-length'], bodySize.toString()) - t.equal(body.length, 0) + if (err) { + reject(err) + } + t.assert.strictEqual(response.headers['content-type'], contentType) + t.assert.strictEqual(response.headers['content-length'], bodySize.toString()) + t.assert.strictEqual(body.length, 0) + resolve() }) }) + + await Promise.all([promise1, promise2]) }) -test('reply.send(new NotFound()) should not invoke the 404 handler', t => { - t.plan(9) +test('reply.send(new NotFound()) should not invoke the 404 handler', async t => { + t.plan(6) const fastify = Fastify() + t.after(() => fastify.close()) fastify.setNotFoundHandler((req, reply) => { t.fail('Should not be called') @@ -1038,42 +1081,50 @@ test('reply.send(new NotFound()) should not invoke the 404 handler', t => { done() }, { prefix: '/prefixed' }) - fastify.listen({ port: 0 }, err => { - t.error(err) - - t.teardown(fastify.close.bind(fastify)) + await fastify.listen({ port: 0 }) + const promise1 = new Promise((resolve, reject) => { sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/not-found' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(body.toString()), { + if (err) { + reject(err) + } + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(body.toString()), { statusCode: 404, error: 'Not Found', message: 'Not Found' }) + resolve() }) + }) + const promise2 = new Promise((resolve, reject) => { sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/prefixed/not-found' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(body), { + if (err) { + reject(err) + } + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(body.toString()), { + statusCode: 404, error: 'Not Found', - message: 'Not Found', - statusCode: 404 + message: 'Not Found' }) + resolve() }) }) + + await Promise.all([promise1, promise2]) }) -test('reply can set multiple instances of same header', t => { +test('reply can set multiple instances of same header', (t, done) => { t.plan(4) const fastify = require('../../')() @@ -1086,75 +1137,80 @@ test('reply can set multiple instances of same header', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, (err, response, body) => { - t.error(err) - t.ok(response.headers['set-cookie']) - t.strictSame(response.headers['set-cookie'], ['one', 'two']) + t.assert.ifError(err) + t.assert.ok(response.headers['set-cookie']) + t.assert.deepStrictEqual(response.headers['set-cookie'], ['one', 'two']) + done() }) }) }) -test('reply.hasHeader returns correct values', t => { +test('reply.hasHeader returns correct values', (t, done) => { t.plan(3) const fastify = require('../../')() fastify.get('/headers', function (req, reply) { reply.header('x-foo', 'foo') - t.equal(reply.hasHeader('x-foo'), true) - t.equal(reply.hasHeader('x-bar'), false) + t.assert.strictEqual(reply.hasHeader('x-foo'), true) + t.assert.strictEqual(reply.hasHeader('x-bar'), false) reply.send() }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => { }) + }, () => { + done() + }) }) }) -test('reply.getHeader returns correct values', t => { +test('reply.getHeader returns correct values', (t, done) => { t.plan(5) const fastify = require('../../')() fastify.get('/headers', function (req, reply) { reply.header('x-foo', 'foo') - t.equal(reply.getHeader('x-foo'), 'foo') + t.assert.strictEqual(reply.getHeader('x-foo'), 'foo') reply.header('x-foo', 'bar') - t.strictSame(reply.getHeader('x-foo'), 'bar') + t.assert.deepStrictEqual(reply.getHeader('x-foo'), 'bar') reply.header('x-foo', 42) - t.strictSame(reply.getHeader('x-foo'), 42) + t.assert.deepStrictEqual(reply.getHeader('x-foo'), 42) reply.header('set-cookie', 'one') reply.header('set-cookie', 'two') - t.strictSame(reply.getHeader('set-cookie'), ['one', 'two']) + t.assert.deepStrictEqual(reply.getHeader('set-cookie'), ['one', 'two']) reply.send() }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => { }) + }, () => { + done() + }) }) }) -test('reply.getHeader returns raw header if there is not in the reply headers', t => { +test('reply.getHeader returns raw header if there is not in the reply headers', (t) => { t.plan(1) const response = { setHeader: () => { }, @@ -1164,10 +1220,10 @@ test('reply.getHeader returns raw header if there is not in the reply headers', end: () => { } } const reply = new Reply(response, { onSend: [] }, null) - t.equal(reply.getHeader('foo'), 'bar') + t.assert.strictEqual(reply.getHeader('foo'), 'bar') }) -test('reply.getHeaders returns correct values', t => { +test('reply.getHeaders returns correct values', (t, done) => { t.plan(3) const fastify = require('../../')() @@ -1175,7 +1231,7 @@ test('reply.getHeaders returns correct values', t => { fastify.get('/headers', function (req, reply) { reply.header('x-foo', 'foo') - t.strictSame(reply.getHeaders(), { + t.assert.deepStrictEqual(reply.getHeaders(), { 'x-foo': 'foo' }) @@ -1183,7 +1239,7 @@ test('reply.getHeaders returns correct values', t => { reply.raw.setHeader('x-foo', 'foo2') reply.raw.setHeader('x-baz', 'baz') - t.strictSame(reply.getHeaders(), { + t.assert.deepStrictEqual(reply.getHeaders(), { 'x-foo': 'foo', 'x-bar': 'bar', 'x-baz': 'baz' @@ -1193,97 +1249,98 @@ test('reply.getHeaders returns correct values', t => { }) fastify.inject('/headers', (err) => { - t.error(err) + t.assert.ifError(err) + done() }) }) -test('reply.removeHeader can remove the value', t => { - t.plan(5) +test('reply.removeHeader can remove the value', (t, done) => { + t.plan(4) const fastify = require('../../')() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/headers', function (req, reply) { reply.header('x-foo', 'foo') - t.equal(reply.getHeader('x-foo'), 'foo') + t.assert.strictEqual(reply.getHeader('x-foo'), 'foo') - t.equal(reply.removeHeader('x-foo'), reply) - t.strictSame(reply.getHeader('x-foo'), undefined) + t.assert.strictEqual(reply.removeHeader('x-foo'), reply) + t.assert.deepStrictEqual(reply.getHeader('x-foo'), undefined) reply.send() }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, () => { - t.pass() + done() }) }) }) -test('reply.header can reset the value', t => { - t.plan(3) +test('reply.header can reset the value', (t, done) => { + t.plan(2) const fastify = require('../../')() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/headers', function (req, reply) { reply.header('x-foo', 'foo') reply.header('x-foo', undefined) - t.strictSame(reply.getHeader('x-foo'), '') + t.assert.deepStrictEqual(reply.getHeader('x-foo'), '') reply.send() }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, () => { - t.pass() + done() }) }) }) // https://github.com/fastify/fastify/issues/3030 -test('reply.hasHeader computes raw and fastify headers', t => { - t.plan(4) +test('reply.hasHeader computes raw and fastify headers', (t, done) => { + t.plan(3) const fastify = require('../../')() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/headers', function (req, reply) { reply.header('x-foo', 'foo') reply.raw.setHeader('x-bar', 'bar') - t.ok(reply.hasHeader('x-foo')) - t.ok(reply.hasHeader('x-bar')) + t.assert.ok(reply.hasHeader('x-foo')) + t.assert.ok(reply.hasHeader('x-bar')) reply.send() }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, () => { - t.pass() + done() }) }) }) -test('Reply should handle JSON content type with a charset', t => { - t.plan(16) +test('Reply should handle JSON content type with a charset', async t => { + t.plan(8) const fastify = require('../../')() @@ -1333,48 +1390,47 @@ test('Reply should handle JSON content type with a charset', t => { .send({ hello: 'world' }) }) - fastify.inject('/default', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - }) - - fastify.inject('/utf8', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - }) + { + const res = await fastify.inject('/default') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + } - fastify.inject('/utf16', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json; charset=utf-16') - }) + { + const res = await fastify.inject('/utf8') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + } - fastify.inject('/utf32', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json; charset=utf-32') - }) + { + const res = await fastify.inject('/utf16') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16') + } - fastify.inject('/type-utf8', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - }) + { + const res = await fastify.inject('/utf32') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-32') + } - fastify.inject('/type-utf16', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json; charset=utf-16') - }) + { + const res = await fastify.inject('/type-utf8') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + } - fastify.inject('/type-utf32', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json; charset=utf-32') - }) + { + const res = await fastify.inject('/type-utf16') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16') + } + { + const res = await fastify.inject('/type-utf32') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-32') + } - fastify.inject('/no-space-type-utf32', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json;charset=utf-32') - }) + { + const res = await fastify.inject('/no-space-type-utf32') + t.assert.strictEqual(res.headers['content-type'], 'application/json;charset=utf-32') + } }) -test('Content type and charset set previously', t => { +test('Content type and charset set previously', (t, done) => { t.plan(2) const fastify = require('../../')() @@ -1389,12 +1445,13 @@ test('Content type and charset set previously', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/json; charset=utf-16') + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16') + done() }) }) -test('.status() is an alias for .code()', t => { +test('.status() is an alias for .code()', (t, done) => { t.plan(2) const fastify = Fastify() @@ -1403,32 +1460,35 @@ test('.status() is an alias for .code()', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 418) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 418) + done() }) }) -test('.statusCode is getter and setter', t => { +test('.statusCode is getter and setter', (t, done) => { t.plan(4) const fastify = Fastify() fastify.get('/', function (req, reply) { - t.equal(reply.statusCode, 200, 'default status value') + t.assert.strictEqual(reply.statusCode, 200, 'default status value') reply.statusCode = 418 - t.equal(reply.statusCode, 418) + t.assert.strictEqual(reply.statusCode, 418) reply.send() }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 418) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 418) + done() }) }) -test('reply.header setting multiple cookies as multiple Set-Cookie headers', t => { - t.plan(7) +test('reply.header setting multiple cookies as multiple Set-Cookie headers', async t => { + t.plan(4) const fastify = require('../../')() + t.after(() => fastify.close()) fastify.get('/headers', function (req, reply) { reply @@ -1439,28 +1499,28 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t = .send({}) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + await fastify.listen({ port: 0 }) + await new Promise((resolve, reject) => { sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' }, (err, response, body) => { - t.error(err) - t.ok(response.headers['set-cookie']) - t.strictSame(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six']) + if (err) { + reject(err) + } + t.assert.ok(response.headers['set-cookie']) + t.assert.deepStrictEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six']) + resolve() }) }) - fastify.inject('/headers', (error, response) => { - t.error(error) - t.ok(response.headers['set-cookie']) - t.strictSame(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six']) - }) + const response = await fastify.inject('/headers') + t.assert.ok(response.headers['set-cookie']) + t.assert.deepStrictEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six']) }) -test('should throw when trying to modify the reply.sent property', t => { +test('should throw when trying to modify the reply.sent property', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1468,14 +1528,15 @@ test('should throw when trying to modify the reply.sent property', t => { try { reply.sent = true } catch (err) { - t.ok(err) + t.assert.ok(err) reply.send() } }) fastify.inject('/', (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok(true) + done() }) }) @@ -1483,10 +1544,10 @@ test('reply.elapsedTime should return 0 before the timer is initialised on the r t.plan(1) const response = { statusCode: 200 } const reply = new Reply(response, null) - t.equal(reply.elapsedTime, 0) + t.assert.strictEqual(reply.elapsedTime, 0) }) -test('reply.elapsedTime should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', t => { +test('reply.elapsedTime should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', async t => { t.plan(1) const fastify = Fastify() fastify.route({ @@ -1498,14 +1559,13 @@ test('reply.elapsedTime should return a number greater than 0 after the timer is }) fastify.addHook('onResponse', (req, reply) => { - t.ok(reply.elapsedTime > 0) - t.end() + t.assert.ok(reply.elapsedTime > 0) }) - fastify.inject({ method: 'GET', url: '/' }) + await fastify.inject({ method: 'GET', url: '/' }) }) -test('reply.elapsedTime should return the time since a request started while inflight', t => { +test('reply.elapsedTime should return the time since a request started while inflight', async t => { t.plan(1) const fastify = Fastify() fastify.route({ @@ -1525,14 +1585,13 @@ test('reply.elapsedTime should return the time since a request started while inf }) fastify.addHook('onResponse', (req, reply) => { - t.ok(reply.elapsedTime > preValidationElapsedTime) - t.end() + t.assert.ok(reply.elapsedTime > preValidationElapsedTime) }) - fastify.inject({ method: 'GET', url: '/' }) + await fastify.inject({ method: 'GET', url: '/' }) }) -test('reply.elapsedTime should return the same value after a request is finished', t => { +test('reply.elapsedTime should return the same value after a request is finished', async t => { t.plan(1) const fastify = Fastify() fastify.route({ @@ -1544,19 +1603,18 @@ test('reply.elapsedTime should return the same value after a request is finished }) fastify.addHook('onResponse', (req, reply) => { - t.equal(reply.elapsedTime, reply.elapsedTime) - t.end() + t.assert.strictEqual(reply.elapsedTime, reply.elapsedTime) }) - fastify.inject({ method: 'GET', url: '/' }) + await fastify.inject({ method: 'GET', url: '/' }) }) -test('reply should use the custom serializer', t => { +test('reply should use the custom serializer', (t, done) => { t.plan(4) const fastify = Fastify() fastify.setReplySerializer((payload, statusCode) => { - t.same(payload, { foo: 'bar' }) - t.equal(statusCode, 200) + t.assert.deepStrictEqual(payload, { foo: 'bar' }) + t.assert.strictEqual(statusCode, 200) payload.foo = 'bar bar' return JSON.stringify(payload) }) @@ -1573,17 +1631,18 @@ test('reply should use the custom serializer', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"foo":"bar bar"}') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '{"foo":"bar bar"}') + done() }) }) -test('reply should use the right serializer in encapsulated context', t => { - t.plan(9) +test('reply should use the right serializer in encapsulated context', async t => { + t.plan(6) const fastify = Fastify() fastify.setReplySerializer((payload) => { - t.same(payload, { foo: 'bar' }) + t.assert.deepStrictEqual(payload, { foo: 'bar' }) payload.foo = 'bar bar' return JSON.stringify(payload) }) @@ -1601,7 +1660,7 @@ test('reply should use the right serializer in encapsulated context', t => { handler: (req, reply) => { reply.send({ john: 'doo' }) } }) instance.setReplySerializer((payload) => { - t.same(payload, { john: 'doo' }) + t.assert.deepStrictEqual(payload, { john: 'doo' }) payload.john = 'too too' return JSON.stringify(payload) }) @@ -1615,40 +1674,31 @@ test('reply should use the right serializer in encapsulated context', t => { handler: (req, reply) => { reply.send({ sweet: 'potato' }) } }) instance.setReplySerializer((payload) => { - t.same(payload, { sweet: 'potato' }) + t.assert.deepStrictEqual(payload, { sweet: 'potato' }) payload.sweet = 'potato potato' return JSON.stringify(payload) }) done() }, { prefix: 'sub' }) - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"foo":"bar bar"}') - }) + { + const res = await fastify.inject('/') + t.assert.strictEqual(res.payload, '{"foo":"bar bar"}') + } - fastify.inject({ - method: 'GET', - url: '/sub' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"john":"too too"}') - }) + { + const res = await fastify.inject('/sub') + t.assert.strictEqual(res.payload, '{"john":"too too"}') + } - fastify.inject({ - method: 'GET', - url: '/sub/sub' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"sweet":"potato potato"}') - }) + { + const res = await fastify.inject('/sub/sub') + t.assert.strictEqual(res.payload, '{"sweet":"potato potato"}') + } }) -test('reply should use the right serializer in deep encapsulated context', t => { - t.plan(8) +test('reply should use the right serializer in deep encapsulated context', async t => { + t.plan(5) const fastify = Fastify() @@ -1665,7 +1715,7 @@ test('reply should use the right serializer in deep encapsulated context', t => handler: (req, reply) => { reply.send({ john: 'doo' }) } }) instance.setReplySerializer((payload) => { - t.same(payload, { john: 'doo' }) + t.assert.deepStrictEqual(payload, { john: 'doo' }) payload.john = 'too too' return JSON.stringify(payload) }) @@ -1677,7 +1727,7 @@ test('reply should use the right serializer in deep encapsulated context', t => handler: (req, reply) => { reply.send({ john: 'deep' }) } }) subInstance.setReplySerializer((payload) => { - t.same(payload, { john: 'deep' }) + t.assert.deepStrictEqual(payload, { john: 'deep' }) payload.john = 'deep deep' return JSON.stringify(payload) }) @@ -1686,32 +1736,21 @@ test('reply should use the right serializer in deep encapsulated context', t => done() }) - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"foo":"bar"}') - }) - - fastify.inject({ - method: 'GET', - url: '/sub' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"john":"too too"}') - }) - - fastify.inject({ - method: 'GET', - url: '/deep' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"john":"deep deep"}') - }) + { + const res = await fastify.inject('/') + t.assert.strictEqual(res.payload, '{"foo":"bar"}') + } + { + const res = await fastify.inject('/sub') + t.assert.strictEqual(res.payload, '{"john":"too too"}') + } + { + const res = await fastify.inject('/deep') + t.assert.strictEqual(res.payload, '{"john":"deep deep"}') + } }) -test('reply should use the route serializer', t => { +test('reply should use the route serializer', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1725,7 +1764,7 @@ test('reply should use the route serializer', t => { handler: (req, reply) => { reply .serializer((payload) => { - t.same(payload, { john: 'doo' }) + t.assert.deepStrictEqual(payload, { john: 'doo' }) payload.john = 'too too' return JSON.stringify(payload) }) @@ -1737,86 +1776,79 @@ test('reply should use the route serializer', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"john":"too too"}') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '{"john":"too too"}') + done() }) }) -test('cannot set the replySerializer when the server is running', t => { +test('cannot set the replySerializer when the server is running', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) try { fastify.setReplySerializer(() => { }) - t.fail('this serializer should not be setup') + t.assert.fail('this serializer should not be setup') } catch (e) { - t.equal(e.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') + t.assert.strictEqual(e.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') + } finally { + done() } }) }) -test('reply should not call the custom serializer for errors and not found', t => { - t.plan(9) +test('reply should not call the custom serializer for errors and not found', async t => { + t.plan(6) const fastify = Fastify() fastify.setReplySerializer((payload, statusCode) => { - t.same(payload, { foo: 'bar' }) - t.equal(statusCode, 200) + t.assert.deepStrictEqual(payload, { foo: 'bar' }) + t.assert.strictEqual(statusCode, 200) return JSON.stringify(payload) }) fastify.get('/', (req, reply) => { reply.send({ foo: 'bar' }) }) fastify.get('/err', (req, reply) => { reply.send(new Error('an error')) }) - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, '{"foo":"bar"}') - }) - - fastify.inject({ - method: 'GET', - url: '/err' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - }) - - fastify.inject({ - method: 'GET', - url: '/not-existing' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) + { + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, '{"foo":"bar"}') + } + { + const res = await fastify.inject('/err') + t.assert.strictEqual(res.statusCode, 500) + } + { + const res = await fastify.inject('/not-existing') + t.assert.strictEqual(res.statusCode, 404) + } }) -test('reply.then', t => { +test('reply.then', async t => { t.plan(4) function request () { } - t.test('without an error', t => { + await t.test('without an error', (t, done) => { t.plan(1) const response = new Writable() const reply = new Reply(response, request) reply.then(function () { - t.pass('fulfilled called') + t.assert.ok(true) + done() }) response.destroy() }) - t.test('with an error', t => { + await t.test('with an error', (t, done) => { t.plan(1) const response = new Writable() @@ -1824,15 +1856,16 @@ test('reply.then', t => { const _err = new Error('kaboom') reply.then(function () { - t.fail('fulfilled called') + t.assert.fail('fulfilled called') }, function (err) { - t.equal(err, _err) + t.assert.strictEqual(err, _err) + done() }) response.destroy(_err) }) - t.test('with error but without reject callback', t => { + await t.test('with error but without reject callback', t => { t.plan(1) const response = new Writable() @@ -1840,15 +1873,15 @@ test('reply.then', t => { const _err = new Error('kaboom') reply.then(function () { - t.fail('fulfilled called') + t.assert.fail('fulfilled called') }) - t.pass() + t.assert.ok(true) response.destroy(_err) }) - t.test('with error, without reject callback, with logger', t => { + await t.test('with error, without reject callback, with logger', (t, done) => { t.plan(1) const response = new Writable() @@ -1856,13 +1889,14 @@ test('reply.then', t => { // spy logger reply.log = { warn: (message) => { - t.equal(message, 'unhandled rejection on reply.then') + t.assert.strictEqual(message, 'unhandled rejection on reply.then') + done() } } const _err = new Error('kaboom') reply.then(function () { - t.fail('fulfilled called') + t.assert.fail('fulfilled called') }) response.destroy(_err) @@ -1874,7 +1908,7 @@ test('reply.sent should read from response.writableEnded if it is defined', t => const reply = new Reply({ writableEnded: true }, {}, {}) - t.equal(reply.sent, true) + t.assert.strictEqual(reply.sent, true) }) test('redirect to an invalid URL should not crash the server', async t => { @@ -1884,7 +1918,7 @@ test('redirect to an invalid URL should not crash the server', async t => { url: '/redirect', handler: (req, reply) => { reply.log.warn = function mockWarn (obj, message) { - t.equal(message, 'Invalid character in header content ["location"]') + t.assert.strictEqual(message, 'Invalid character in header content ["location"]') } switch (req.query.useCase) { @@ -1907,8 +1941,8 @@ test('redirect to an invalid URL should not crash the server', async t => { { const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=1`) - t.equal(response.statusCode, 500) - t.same(JSON.parse(body), { + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(body), { statusCode: 500, code: 'ERR_INVALID_CHAR', error: 'Internal Server Error', @@ -1917,14 +1951,14 @@ test('redirect to an invalid URL should not crash the server', async t => { } { const { response } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=2`) - t.equal(response.statusCode, 302) - t.equal(response.headers.location, '/?key=a%E2%80%99b') + t.assert.strictEqual(response.statusCode, 302) + t.assert.strictEqual(response.headers.location, '/?key=a%E2%80%99b') } { const { response } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=3`) - t.equal(response.statusCode, 302) - t.equal(response.headers.location, '/?key=ab') + t.assert.strictEqual(response.statusCode, 302) + t.assert.strictEqual(response.headers.location, '/?key=ab') } await fastify.close() @@ -1937,7 +1971,7 @@ test('invalid response headers should not crash the server', async t => { url: '/bad-headers', handler: (req, reply) => { reply.log.warn = function mockWarn (obj, message) { - t.equal(message, 'Invalid character in header content ["smile-encoded"]', 'only the first invalid header is logged') + t.assert.strictEqual(message, 'Invalid character in header content ["smile-encoded"]', 'only the first invalid header is logged') } reply.header('foo', '$') @@ -1952,8 +1986,8 @@ test('invalid response headers should not crash the server', async t => { await fastify.listen({ port: 0 }) const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) - t.equal(response.statusCode, 500) - t.same(JSON.parse(body), { + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(body), { statusCode: 500, code: 'ERR_INVALID_CHAR', error: 'Internal Server Error', @@ -1970,7 +2004,7 @@ test('invalid response headers when sending back an error', async t => { url: '/bad-headers', handler: (req, reply) => { reply.log.warn = function mockWarn (obj, message) { - t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged') + t.assert.strictEqual(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged') } reply.header('smile', '😄') @@ -1981,8 +2015,8 @@ test('invalid response headers when sending back an error', async t => { await fastify.listen({ port: 0 }) const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) - t.equal(response.statusCode, 500) - t.same(JSON.parse(body), { + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(body), { statusCode: 500, code: 'ERR_INVALID_CHAR', error: 'Internal Server Error', @@ -1999,7 +2033,7 @@ test('invalid response headers and custom error handler', async t => { url: '/bad-headers', handler: (req, reply) => { reply.log.warn = function mockWarn (obj, message) { - t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged') + t.assert.strictEqual(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged') } reply.header('smile', '😄') @@ -2008,15 +2042,15 @@ test('invalid response headers and custom error handler', async t => { }) fastify.setErrorHandler(function (error, request, reply) { - t.equal(error.message, 'user land error', 'custom error handler receives the error') + t.assert.strictEqual(error.message, 'user land error', 'custom error handler receives the error') reply.status(500).send({ ops: true }) }) await fastify.listen({ port: 0 }) const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) - t.equal(response.statusCode, 500) - t.same(JSON.parse(body), { + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(body), { statusCode: 500, code: 'ERR_INVALID_CHAR', error: 'Internal Server Error', @@ -2045,7 +2079,7 @@ test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', const log = { warn: (msg) => { - t.equal(msg, 'Reply was already sent, did you forget to "return reply" in the "/hello" (GET) route?') + t.assert.strictEqual(msg, 'Reply was already sent, did you forget to "return reply" in the "/hello" (GET) route?') } } @@ -2054,11 +2088,11 @@ test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', try { reply.send('') } catch (err) { - t.equal(err.code, 'ERR_HTTP_HEADERS_SENT') + t.assert.strictEqual(err.code, 'ERR_HTTP_HEADERS_SENT') } }) -test('Uint8Array view of ArrayBuffer returns correct byteLength', t => { +test('Uint8Array view of ArrayBuffer returns correct byteLength', (t, done) => { t.plan(5) const fastify = Fastify() @@ -2069,17 +2103,18 @@ test('Uint8Array view of ArrayBuffer returns correct byteLength', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) fastify.inject({ method: 'GET', url: '/' }, (err, response) => { - t.error(err) - t.equal(response.headers['content-type'], 'application/octet-stream') - t.equal(response.headers['content-length'], '10') - t.same(response.rawPayload.byteLength, arrView.byteLength) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(response.headers['content-length'], '10') + t.assert.deepStrictEqual(response.rawPayload.byteLength, arrView.byteLength) + done() }) }) }) diff --git a/test/internals/reqIdGenFactory.test.js b/test/internals/reqIdGenFactory.test.js index b11c7263c0c..567a3cc27a0 100644 --- a/test/internals/reqIdGenFactory.test.js +++ b/test/internals/reqIdGenFactory.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const { reqIdGenFactory } = require('../../lib/reqIdGenFactory') test('should create incremental ids deterministically', t => { @@ -9,18 +9,18 @@ test('should create incremental ids deterministically', t => { for (let i = 1; i < 1e4; ++i) { if (reqIdGen() !== 'req-' + i.toString(36)) { - t.fail() + t.assert.fail() break } } - t.pass() + t.assert.ok(true) }) test('should have prefix "req-"', t => { t.plan(1) const reqIdGen = reqIdGenFactory() - t.ok(reqIdGen().startsWith('req-')) + t.assert.ok(reqIdGen().startsWith('req-')) }) test('different id generator functions should have separate internal counters', t => { @@ -28,18 +28,18 @@ test('different id generator functions should have separate internal counters', const reqIdGenA = reqIdGenFactory() const reqIdGenB = reqIdGenFactory() - t.equal(reqIdGenA(), 'req-1') - t.equal(reqIdGenA(), 'req-2') - t.equal(reqIdGenB(), 'req-1') - t.equal(reqIdGenA(), 'req-3') - t.equal(reqIdGenB(), 'req-2') + t.assert.strictEqual(reqIdGenA(), 'req-1') + t.assert.strictEqual(reqIdGenA(), 'req-2') + t.assert.strictEqual(reqIdGenB(), 'req-1') + t.assert.strictEqual(reqIdGenA(), 'req-3') + t.assert.strictEqual(reqIdGenB(), 'req-2') }) test('should start counting with 1', t => { t.plan(1) const reqIdGen = reqIdGenFactory() - t.equal(reqIdGen(), 'req-1') + t.assert.strictEqual(reqIdGen(), 'req-1') }) test('should handle requestIdHeader and return provided id in header', t => { @@ -47,7 +47,7 @@ test('should handle requestIdHeader and return provided id in header', t => { const reqIdGen = reqIdGenFactory('id') - t.equal(reqIdGen({ headers: { id: '1337' } }), '1337') + t.assert.strictEqual(reqIdGen({ headers: { id: '1337' } }), '1337') }) test('should handle requestIdHeader and fallback if id is not provided in header', t => { @@ -55,7 +55,7 @@ test('should handle requestIdHeader and fallback if id is not provided in header const reqIdGen = reqIdGenFactory('id') - t.equal(reqIdGen({ headers: { notId: '1337' } }), 'req-1') + t.assert.strictEqual(reqIdGen({ headers: { notId: '1337' } }), 'req-1') }) test('should handle requestIdHeader and increment internal counter if no header was provided', t => { @@ -63,10 +63,10 @@ test('should handle requestIdHeader and increment internal counter if no header const reqIdGen = reqIdGenFactory('id') - t.equal(reqIdGen({ headers: {} }), 'req-1') - t.equal(reqIdGen({ headers: {} }), 'req-2') - t.equal(reqIdGen({ headers: { id: '1337' } }), '1337') - t.equal(reqIdGen({ headers: {} }), 'req-3') + t.assert.strictEqual(reqIdGen({ headers: {} }), 'req-1') + t.assert.strictEqual(reqIdGen({ headers: {} }), 'req-2') + t.assert.strictEqual(reqIdGen({ headers: { id: '1337' } }), '1337') + t.assert.strictEqual(reqIdGen({ headers: {} }), 'req-3') }) test('should use optGenReqId to generate ids', t => { @@ -80,10 +80,10 @@ test('should use optGenReqId to generate ids', t => { } const reqIdGen = reqIdGenFactory(undefined, optGenReqId) - t.equal(gotCalled, false) - t.equal(reqIdGen(), '1') - t.equal(gotCalled, true) - t.equal(reqIdGen(), '2') + t.assert.strictEqual(gotCalled, false) + t.assert.strictEqual(reqIdGen(), '1') + t.assert.strictEqual(gotCalled, true) + t.assert.strictEqual(reqIdGen(), '2') }) test('should use optGenReqId to generate ids if requestIdHeader is used but not provided', t => { @@ -97,22 +97,22 @@ test('should use optGenReqId to generate ids if requestIdHeader is used but not } const reqIdGen = reqIdGenFactory('reqId', optGenReqId) - t.equal(gotCalled, false) - t.equal(reqIdGen({ headers: {} }), '1') - t.equal(gotCalled, true) - t.equal(reqIdGen({ headers: {} }), '2') + t.assert.strictEqual(gotCalled, false) + t.assert.strictEqual(reqIdGen({ headers: {} }), '1') + t.assert.strictEqual(gotCalled, true) + t.assert.strictEqual(reqIdGen({ headers: {} }), '2') }) test('should not use optGenReqId to generate ids if requestIdHeader is used and provided', t => { t.plan(2) function optGenReqId () { - t.fail() + t.assert.fail() } const reqIdGen = reqIdGenFactory('reqId', optGenReqId) - t.equal(reqIdGen({ headers: { reqId: 'r1' } }), 'r1') - t.equal(reqIdGen({ headers: { reqId: 'r2' } }), 'r2') + t.assert.strictEqual(reqIdGen({ headers: { reqId: 'r1' } }), 'r1') + t.assert.strictEqual(reqIdGen({ headers: { reqId: 'r2' } }), 'r2') }) test('should fallback to use optGenReqId to generate ids if requestIdHeader is sometimes provided', t => { @@ -126,8 +126,8 @@ test('should fallback to use optGenReqId to generate ids if requestIdHeader is s } const reqIdGen = reqIdGenFactory('reqId', optGenReqId) - t.equal(reqIdGen({ headers: { reqId: 'r1' } }), 'r1') - t.equal(gotCalled, false) - t.equal(reqIdGen({ headers: {} }), '1') - t.equal(gotCalled, true) + t.assert.strictEqual(reqIdGen({ headers: { reqId: 'r1' } }), 'r1') + t.assert.strictEqual(gotCalled, false) + t.assert.strictEqual(reqIdGen({ headers: {} }), '1') + t.assert.strictEqual(gotCalled, true) }) diff --git a/test/internals/request-validate.test.js b/test/internals/request-validate.test.js index 200f5216c39..914588136bf 100644 --- a/test/internals/request-validate.test.js +++ b/test/internals/request-validate.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Ajv = require('ajv') const { kRequestCacheValidateFns, kRouteContext } = require('../../lib/symbols') const Fastify = require('../../fastify') @@ -44,10 +44,10 @@ const requestSchema = { } } -test('#compileValidationSchema', subtest => { +test('#compileValidationSchema', async subtest => { subtest.plan(7) - subtest.test('Should return a function - Route without schema', async t => { + await subtest.test('Should return a function - Route without schema', async t => { const fastify = Fastify() t.plan(3) @@ -55,9 +55,9 @@ test('#compileValidationSchema', subtest => { fastify.get('/', (req, reply) => { const validate = req.compileValidationSchema(defaultSchema) - t.type(validate, Function) - t.ok(validate({ hello: 'world' })) - t.notOk(validate({ world: 'foo' })) + t.assert.ok(validate instanceof Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) reply.send({ hello: 'world' }) }) @@ -68,7 +68,7 @@ test('#compileValidationSchema', subtest => { }) }) - subtest.test('Validate function errors property should be null after validation when input is valid', async t => { + await subtest.test('Validate function errors property should be null after validation when input is valid', async t => { const fastify = Fastify() t.plan(3) @@ -76,9 +76,9 @@ test('#compileValidationSchema', subtest => { fastify.get('/', (req, reply) => { const validate = req.compileValidationSchema(defaultSchema) - t.ok(validate({ hello: 'world' })) - t.ok(Object.hasOwn(validate, 'errors')) - t.equal(validate.errors, null) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(Object.hasOwn(validate, 'errors')) + t.assert.strictEqual(validate.errors, null) reply.send({ hello: 'world' }) }) @@ -89,7 +89,7 @@ test('#compileValidationSchema', subtest => { }) }) - subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => { + await subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => { const fastify = Fastify() t.plan(4) @@ -97,10 +97,10 @@ test('#compileValidationSchema', subtest => { fastify.get('/', (req, reply) => { const validate = req.compileValidationSchema(defaultSchema) - t.notOk(validate({ world: 'foo' })) - t.ok(Object.hasOwn(validate, 'errors')) - t.ok(Array.isArray(validate.errors)) - t.ok(validate.errors.length > 0) + t.assert.ok(!validate({ world: 'foo' })) + t.assert.ok(Object.hasOwn(validate, 'errors')) + t.assert.ok(Array.isArray(validate.errors)) + t.assert.ok(validate.errors.length > 0) reply.send({ hello: 'world' }) }) @@ -111,7 +111,7 @@ test('#compileValidationSchema', subtest => { }) }) - subtest.test( + await subtest.test( 'Should reuse the validate fn across multiple invocations - Route without schema', async t => { const fastify = Fastify() @@ -124,15 +124,15 @@ test('#compileValidationSchema', subtest => { counter++ if (counter > 1) { const newValidate = req.compileValidationSchema(defaultSchema) - t.equal(validate, newValidate, 'Are the same validate function') + t.assert.strictEqual(validate, newValidate, 'Are the same validate function') validate = newValidate } else { validate = req.compileValidationSchema(defaultSchema) } - t.type(validate, Function) - t.ok(validate({ hello: 'world' })) - t.notOk(validate({ world: 'foo' })) + t.assert.ok(validate instanceof Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) reply.send({ hello: 'world' }) }) @@ -156,11 +156,11 @@ test('#compileValidationSchema', subtest => { }) ]) - t.equal(counter, 4) + t.assert.strictEqual(counter, 4) } ) - subtest.test('Should return a function - Route with schema', async t => { + await subtest.test('Should return a function - Route with schema', async t => { const fastify = Fastify() t.plan(3) @@ -175,9 +175,9 @@ test('#compileValidationSchema', subtest => { (req, reply) => { const validate = req.compileValidationSchema(defaultSchema) - t.type(validate, Function) - t.ok(validate({ hello: 'world' })) - t.notOk(validate({ world: 'foo' })) + t.assert.ok(validate instanceof Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) reply.send({ hello: 'world' }) } @@ -193,20 +193,20 @@ test('#compileValidationSchema', subtest => { }) }) - subtest.test( + await subtest.test( 'Should use the custom validator compiler for the route', async t => { const fastify = Fastify() let called = 0 const custom = ({ schema, httpPart, url, method }) => { - t.equal(schema, defaultSchema) - t.equal(url, '/') - t.equal(method, 'GET') - t.equal(httpPart, 'querystring') + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpPart, 'querystring') return input => { called++ - t.same(input, { hello: 'world' }) + t.assert.deepStrictEqual(input, { hello: 'world' }) return true } } @@ -217,10 +217,10 @@ test('#compileValidationSchema', subtest => { const first = req.compileValidationSchema(defaultSchema, 'querystring') const second = req.compileValidationSchema(defaultSchema, 'querystring') - t.equal(first, second) - t.ok(first({ hello: 'world' })) - t.ok(second({ hello: 'world' })) - t.equal(called, 2) + t.assert.strictEqual(first, second) + t.assert.ok(first({ hello: 'world' })) + t.assert.ok(second({ hello: 'world' })) + t.assert.strictEqual(called, 2) reply.send({ hello: 'world' }) }) @@ -232,7 +232,7 @@ test('#compileValidationSchema', subtest => { } ) - subtest.test( + await subtest.test( 'Should instantiate a WeakMap when executed for first time', async t => { const fastify = Fastify() @@ -240,11 +240,11 @@ test('#compileValidationSchema', subtest => { t.plan(5) fastify.get('/', (req, reply) => { - t.equal(req[kRouteContext][kRequestCacheValidateFns], null) - t.type(req.compileValidationSchema(defaultSchema), Function) - t.type(req[kRouteContext][kRequestCacheValidateFns], WeakMap) - t.type(req.compileValidationSchema(Object.assign({}, defaultSchema)), Function) - t.type(req[kRouteContext][kRequestCacheValidateFns], WeakMap) + t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null) + t.assert.ok(req.compileValidationSchema(defaultSchema) instanceof Function) + t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap) + t.assert.ok(req.compileValidationSchema(Object.assign({}, defaultSchema)) instanceof Function) + t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap) reply.send({ hello: 'world' }) }) @@ -257,10 +257,10 @@ test('#compileValidationSchema', subtest => { ) }) -test('#getValidationFunction', subtest => { +test('#getValidationFunction', async subtest => { subtest.plan(6) - subtest.test('Should return a validation function', async t => { + await subtest.test('Should return a validation function', async t => { const fastify = Fastify() t.plan(1) @@ -269,7 +269,7 @@ test('#getValidationFunction', subtest => { const original = req.compileValidationSchema(defaultSchema) const referenced = req.getValidationFunction(defaultSchema) - t.equal(original, referenced) + t.assert.strictEqual(original, referenced) reply.send({ hello: 'world' }) }) @@ -280,7 +280,7 @@ test('#getValidationFunction', subtest => { }) }) - subtest.test('Validate function errors property should be null after validation when input is valid', async t => { + await subtest.test('Validate function errors property should be null after validation when input is valid', async t => { const fastify = Fastify() t.plan(3) @@ -289,9 +289,9 @@ test('#getValidationFunction', subtest => { req.compileValidationSchema(defaultSchema) const validate = req.getValidationFunction(defaultSchema) - t.ok(validate({ hello: 'world' })) - t.ok(Object.hasOwn(validate, 'errors')) - t.equal(validate.errors, null) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(Object.hasOwn(validate, 'errors')) + t.assert.strictEqual(validate.errors, null) reply.send({ hello: 'world' }) }) @@ -302,7 +302,7 @@ test('#getValidationFunction', subtest => { }) }) - subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => { + await subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => { const fastify = Fastify() t.plan(4) @@ -311,10 +311,10 @@ test('#getValidationFunction', subtest => { req.compileValidationSchema(defaultSchema) const validate = req.getValidationFunction(defaultSchema) - t.notOk(validate({ world: 'foo' })) - t.ok(Object.hasOwn(validate, 'errors')) - t.ok(Array.isArray(validate.errors)) - t.ok(validate.errors.length > 0) + t.assert.ok(!validate({ world: 'foo' })) + t.assert.ok(Object.hasOwn(validate, 'errors')) + t.assert.ok(Array.isArray(validate.errors)) + t.assert.ok(validate.errors.length > 0) reply.send({ hello: 'world' }) }) @@ -325,17 +325,17 @@ test('#getValidationFunction', subtest => { }) }) - subtest.test('Should return undefined if no schema compiled', async t => { + await subtest.test('Should return undefined if no schema compiled', async t => { const fastify = Fastify() t.plan(2) fastify.get('/', (req, reply) => { const validate = req.getValidationFunction(defaultSchema) - t.notOk(validate) + t.assert.ok(!validate) const validateFn = req.getValidationFunction(42) - t.notOk(validateFn) + t.assert.ok(!validateFn) reply.send({ hello: 'world' }) }) @@ -343,7 +343,7 @@ test('#getValidationFunction', subtest => { await fastify.inject('/') }) - subtest.test( + await subtest.test( 'Should return the validation function from each HTTP part', async t => { const fastify = Fastify() @@ -363,39 +363,38 @@ test('#getValidationFunction', subtest => { switch (params.id) { case 1: customValidation = req.compileValidationSchema(defaultSchema) - t.ok(req.getValidationFunction('body')) - t.ok(req.getValidationFunction('body')({ hello: 'world' })) - t.notOk(req.getValidationFunction('body')({ world: 'hello' })) + t.assert.ok(req.getValidationFunction('body')) + t.assert.ok(req.getValidationFunction('body')({ hello: 'world' })) + t.assert.ok(!req.getValidationFunction('body')({ world: 'hello' })) break case 2: headerValidation = req.getValidationFunction('headers') - t.ok(headerValidation) - t.ok(headerValidation({ 'x-foo': 'world' })) - t.notOk(headerValidation({ 'x-foo': [] })) + t.assert.ok(headerValidation) + t.assert.ok(headerValidation({ 'x-foo': 'world' })) + t.assert.ok(!headerValidation({ 'x-foo': [] })) break case 3: - t.ok(req.getValidationFunction('params')) - t.ok(req.getValidationFunction('params')({ id: 123 })) - t.notOk(req.getValidationFunction('params'({ id: 1.2 }))) + t.assert.ok(req.getValidationFunction('params')) + t.assert.ok(req.getValidationFunction('params')({ id: 123 })) + t.assert.ok(!req.getValidationFunction('params'({ id: 1.2 }))) break case 4: - t.ok(req.getValidationFunction('querystring')) - t.ok(req.getValidationFunction('querystring')({ foo: 'bar' })) - t.notOk( - req.getValidationFunction('querystring')({ foo: 'not-bar' }) + t.assert.ok(req.getValidationFunction('querystring')) + t.assert.ok(req.getValidationFunction('querystring')({ foo: 'bar' })) + t.assert.ok(!req.getValidationFunction('querystring')({ foo: 'not-bar' }) ) break case 5: - t.equal( + t.assert.strictEqual( customValidation, req.getValidationFunction(defaultSchema) ) - t.ok(customValidation({ hello: 'world' })) - t.notOk(customValidation({})) - t.equal(headerValidation, req.getValidationFunction('headers')) + t.assert.ok(customValidation({ hello: 'world' })) + t.assert.ok(!customValidation({})) + t.assert.strictEqual(headerValidation, req.getValidationFunction('headers')) break default: - t.fail('Invalid id') + t.assert.fail('Invalid id') } reply.send({ hello: 'world' }) @@ -424,7 +423,7 @@ test('#getValidationFunction', subtest => { } ) - subtest.test('Should not set a WeakMap if there is no schema', async t => { + await subtest.test('Should not set a WeakMap if there is no schema', async t => { const fastify = Fastify() t.plan(1) @@ -433,7 +432,7 @@ test('#getValidationFunction', subtest => { req.getValidationFunction(defaultSchema) req.getValidationFunction('body') - t.equal(req[kRouteContext][kRequestCacheValidateFns], null) + t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null) reply.send({ hello: 'world' }) }) @@ -444,10 +443,10 @@ test('#getValidationFunction', subtest => { }) }) -test('#validate', subtest => { +test('#validate', async subtest => { subtest.plan(7) - subtest.test( + await subtest.test( 'Should return true/false if input valid - Route without schema', async t => { const fastify = Fastify() @@ -458,8 +457,8 @@ test('#validate', subtest => { const isNotValid = req.validateInput({ world: 'string' }, defaultSchema) const isValid = req.validateInput({ hello: 'string' }, defaultSchema) - t.notOk(isNotValid) - t.ok(isValid) + t.assert.ok(!isNotValid) + t.assert.ok(isValid) reply.send({ hello: 'world' }) }) @@ -471,20 +470,20 @@ test('#validate', subtest => { } ) - subtest.test( + await subtest.test( 'Should use the custom validator compiler for the route', async t => { const fastify = Fastify() let called = 0 const custom = ({ schema, httpPart, url, method }) => { - t.equal(schema, defaultSchema) - t.equal(url, '/') - t.equal(method, 'GET') - t.equal(httpPart, 'querystring') + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpPart, 'querystring') return input => { called++ - t.same(input, { hello: 'world' }) + t.assert.deepStrictEqual(input, { hello: 'world' }) return true } } @@ -499,9 +498,9 @@ test('#validate', subtest => { ) const ok2 = req.validateInput({ hello: 'world' }, defaultSchema) - t.ok(ok) - t.ok(ok2) - t.equal(called, 2) + t.assert.ok(ok) + t.assert.ok(ok2) + t.assert.strictEqual(called, 2) reply.send({ hello: 'world' }) }) @@ -513,7 +512,7 @@ test('#validate', subtest => { } ) - subtest.test( + await subtest.test( 'Should return true/false if input valid - With Schema for Route defined', async t => { const fastify = Fastify() @@ -530,23 +529,23 @@ test('#validate', subtest => { switch (params.id) { case 1: - t.ok(req.validateInput({ hello: 'world' }, 'body')) - t.notOk(req.validateInput({ hello: [], world: 'foo' }, 'body')) + t.assert.ok(req.validateInput({ hello: 'world' }, 'body')) + t.assert.ok(!req.validateInput({ hello: [], world: 'foo' }, 'body')) break case 2: - t.notOk(req.validateInput({ foo: 'something' }, 'querystring')) - t.ok(req.validateInput({ foo: 'bar' }, 'querystring')) + t.assert.ok(!req.validateInput({ foo: 'something' }, 'querystring')) + t.assert.ok(req.validateInput({ foo: 'bar' }, 'querystring')) break case 3: - t.notOk(req.validateInput({ 'x-foo': [] }, 'headers')) - t.ok(req.validateInput({ 'x-foo': 'something' }, 'headers')) + t.assert.ok(!req.validateInput({ 'x-foo': [] }, 'headers')) + t.assert.ok(req.validateInput({ 'x-foo': 'something' }, 'headers')) break case 4: - t.ok(req.validateInput({ id: params.id }, 'params')) - t.notOk(req.validateInput({ id: 0 }, 'params')) + t.assert.ok(req.validateInput({ id: params.id }, 'params')) + t.assert.ok(!req.validateInput({ id: 0 }, 'params')) break default: - t.fail('Invalid id') + t.assert.fail('Invalid id') } reply.send({ hello: 'world' }) @@ -575,7 +574,7 @@ test('#validate', subtest => { } ) - subtest.test( + await subtest.test( 'Should throw if missing validation fn for HTTP part and not schema provided', async t => { const fastify = Fastify() @@ -602,7 +601,7 @@ test('#validate', subtest => { req.validateInput({ id: 0 }, 'params') break default: - t.fail('Invalid id') + t.assert.fail('Invalid id') } }) @@ -614,8 +613,8 @@ test('#validate', subtest => { const response = await fastify.inject(`/${j}`) const result = response.json() - t.equal(result.statusCode, 500) - t.equal(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + t.assert.strictEqual(result.statusCode, 500) + t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') })(i) ) } @@ -624,7 +623,7 @@ test('#validate', subtest => { } ) - subtest.test( + await subtest.test( 'Should throw if missing validation fn for HTTP part and not valid schema provided', async t => { const fastify = Fastify() @@ -651,7 +650,7 @@ test('#validate', subtest => { req.validateInput({ id: 0 }, () => {}, 'params') break default: - t.fail('Invalid id') + t.assert.fail('Invalid id') } }) @@ -666,8 +665,8 @@ test('#validate', subtest => { }) const result = response.json() - t.equal(result.statusCode, 500) - t.equal(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + t.assert.strictEqual(result.statusCode, 500) + t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') })(i) ) } @@ -676,7 +675,7 @@ test('#validate', subtest => { } ) - subtest.test('Should throw if invalid schema passed', async t => { + await subtest.test('Should throw if invalid schema passed', async t => { const fastify = Fastify() t.plan(10) @@ -701,7 +700,7 @@ test('#validate', subtest => { req.validateInput({ id: 0 }, () => {}) break default: - t.fail('Invalid id') + t.assert.fail('Invalid id') } }) @@ -716,8 +715,8 @@ test('#validate', subtest => { }) const result = response.json() - t.equal(result.statusCode, 500) - t.equal(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + t.assert.strictEqual(result.statusCode, 500) + t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') })(i) ) } @@ -725,7 +724,7 @@ test('#validate', subtest => { await Promise.all(promises) }) - subtest.test( + await subtest.test( 'Should set a WeakMap if compiling the very first schema', async t => { const fastify = Fastify() @@ -733,9 +732,9 @@ test('#validate', subtest => { t.plan(3) fastify.get('/', (req, reply) => { - t.equal(req[kRouteContext][kRequestCacheValidateFns], null) - t.equal(req.validateInput({ hello: 'world' }, defaultSchema), true) - t.type(req[kRouteContext][kRequestCacheValidateFns], WeakMap) + t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null) + t.assert.strictEqual(req.validateInput({ hello: 'world' }, defaultSchema), true) + t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap) reply.send({ hello: 'world' }) }) @@ -748,24 +747,24 @@ test('#validate', subtest => { ) }) -test('Nested Context', subtest => { +test('Nested Context', async subtest => { subtest.plan(1) - subtest.test('Level_1', tst => { + await subtest.test('Level_1', async tst => { tst.plan(3) - tst.test('#compileValidationSchema', ntst => { + await tst.test('#compileValidationSchema', async ntst => { ntst.plan(5) - ntst.test('Should return a function - Route without schema', async t => { + await ntst.test('Should return a function - Route without schema', async t => { const fastify = Fastify() fastify.register((instance, opts, next) => { instance.get('/', (req, reply) => { const validate = req.compileValidationSchema(defaultSchema) - t.type(validate, Function) - t.ok(validate({ hello: 'world' })) - t.notOk(validate({ world: 'foo' })) + t.assert.ok(validate, Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) reply.send({ hello: 'world' }) }) @@ -781,7 +780,7 @@ test('Nested Context', subtest => { }) }) - ntst.test( + await ntst.test( 'Should reuse the validate fn across multiple invocations - Route without schema', async t => { const fastify = Fastify() @@ -795,15 +794,15 @@ test('Nested Context', subtest => { counter++ if (counter > 1) { const newValidate = req.compileValidationSchema(defaultSchema) - t.equal(validate, newValidate, 'Are the same validate function') + t.assert.strictEqual(validate, newValidate, 'Are the same validate function') validate = newValidate } else { validate = req.compileValidationSchema(defaultSchema) } - t.type(validate, Function) - t.ok(validate({ hello: 'world' })) - t.notOk(validate({ world: 'foo' })) + t.assert.ok(validate, Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) reply.send({ hello: 'world' }) }) @@ -818,11 +817,11 @@ test('Nested Context', subtest => { fastify.inject('/') ]) - t.equal(counter, 4) + t.assert.strictEqual(counter, 4) } ) - ntst.test('Should return a function - Route with schema', async t => { + await ntst.test('Should return a function - Route with schema', async t => { const fastify = Fastify() t.plan(3) @@ -838,9 +837,9 @@ test('Nested Context', subtest => { (req, reply) => { const validate = req.compileValidationSchema(defaultSchema) - t.type(validate, Function) - t.ok(validate({ hello: 'world' })) - t.notOk(validate({ world: 'foo' })) + t.assert.ok(validate, Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) reply.send({ hello: 'world' }) } @@ -859,7 +858,7 @@ test('Nested Context', subtest => { }) }) - ntst.test( + await ntst.test( 'Should use the custom validator compiler for the route', async t => { const fastify = Fastify() @@ -869,14 +868,14 @@ test('Nested Context', subtest => { fastify.register((instance, opts, next) => { const custom = ({ schema, httpPart, url, method }) => { - t.equal(schema, defaultSchema) - t.equal(url, '/') - t.equal(method, 'GET') - t.equal(httpPart, 'querystring') + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpPart, 'querystring') return input => { called++ - t.same(input, { hello: 'world' }) + t.assert.deepStrictEqual(input, { hello: 'world' }) return true } } @@ -891,10 +890,10 @@ test('Nested Context', subtest => { 'querystring' ) - t.equal(first, second) - t.ok(first({ hello: 'world' })) - t.ok(second({ hello: 'world' })) - t.equal(called, 2) + t.assert.strictEqual(first, second) + t.assert.ok(first({ hello: 'world' })) + t.assert.ok(second({ hello: 'world' })) + t.assert.strictEqual(called, 2) reply.send({ hello: 'world' }) }) @@ -906,7 +905,7 @@ test('Nested Context', subtest => { } ) - ntst.test('Should compile the custom validation - nested with schema.headers', async t => { + await ntst.test('Should compile the custom validation - nested with schema.headers', async t => { const fastify = Fastify() let called = false @@ -921,9 +920,9 @@ test('Nested Context', subtest => { const custom = ({ schema, httpPart, url, method }) => { if (called) return () => true // only custom validators keep the same headers object - t.equal(schema, schemaWithHeaders.headers) - t.equal(url, '/') - t.equal(httpPart, 'headers') + t.assert.strictEqual(schema, schemaWithHeaders.headers) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(httpPart, 'headers') called = true return () => true } @@ -934,7 +933,7 @@ test('Nested Context', subtest => { fastify.register((instance, opts, next) => { instance.get('/', { schema: schemaWithHeaders }, (req, reply) => { - t.equal(called, true) + t.assert.strictEqual(called, true) reply.send({ hello: 'world' }) }) @@ -946,10 +945,10 @@ test('Nested Context', subtest => { }) }) - tst.test('#getValidationFunction', ntst => { + await tst.test('#getValidationFunction', async ntst => { ntst.plan(6) - ntst.test('Should return a validation function', async t => { + await ntst.test('Should return a validation function', async t => { const fastify = Fastify() t.plan(1) @@ -959,7 +958,7 @@ test('Nested Context', subtest => { const original = req.compileValidationSchema(defaultSchema) const referenced = req.getValidationFunction(defaultSchema) - t.equal(original, referenced) + t.assert.strictEqual(original, referenced) reply.send({ hello: 'world' }) }) @@ -970,7 +969,7 @@ test('Nested Context', subtest => { await fastify.inject('/') }) - ntst.test('Should return undefined if no schema compiled', async t => { + await ntst.test('Should return undefined if no schema compiled', async t => { const fastify = Fastify() t.plan(1) @@ -979,7 +978,7 @@ test('Nested Context', subtest => { instance.get('/', (req, reply) => { const validate = req.getValidationFunction(defaultSchema) - t.notOk(validate) + t.assert.ok(!validate) reply.send({ hello: 'world' }) }) @@ -990,7 +989,7 @@ test('Nested Context', subtest => { await fastify.inject('/') }) - ntst.test( + await ntst.test( 'Should return the validation function from each HTTP part', async t => { const fastify = Fastify() @@ -1013,48 +1012,46 @@ test('Nested Context', subtest => { customValidation = req.compileValidationSchema( defaultSchema ) - t.ok(req.getValidationFunction('body')) - t.ok(req.getValidationFunction('body')({ hello: 'world' })) - t.notOk( - req.getValidationFunction('body')({ world: 'hello' }) + t.assert.ok(req.getValidationFunction('body')) + t.assert.ok(req.getValidationFunction('body')({ hello: 'world' })) + t.assert.ok(!req.getValidationFunction('body')({ world: 'hello' }) ) break case 2: headerValidation = req.getValidationFunction('headers') - t.ok(headerValidation) - t.ok(headerValidation({ 'x-foo': 'world' })) - t.notOk(headerValidation({ 'x-foo': [] })) + t.assert.ok(headerValidation) + t.assert.ok(headerValidation({ 'x-foo': 'world' })) + t.assert.ok(!headerValidation({ 'x-foo': [] })) break case 3: - t.ok(req.getValidationFunction('params')) - t.ok(req.getValidationFunction('params')({ id: 123 })) - t.notOk(req.getValidationFunction('params'({ id: 1.2 }))) + t.assert.ok(req.getValidationFunction('params')) + t.assert.ok(req.getValidationFunction('params')({ id: 123 })) + t.assert.ok(!req.getValidationFunction('params'({ id: 1.2 }))) break case 4: - t.ok(req.getValidationFunction('querystring')) - t.ok( + t.assert.ok(req.getValidationFunction('querystring')) + t.assert.ok( req.getValidationFunction('querystring')({ foo: 'bar' }) ) - t.notOk( - req.getValidationFunction('querystring')({ - foo: 'not-bar' - }) + t.assert.ok(!req.getValidationFunction('querystring')({ + foo: 'not-bar' + }) ) break case 5: - t.equal( + t.assert.strictEqual( customValidation, req.getValidationFunction(defaultSchema) ) - t.ok(customValidation({ hello: 'world' })) - t.notOk(customValidation({})) - t.equal( + t.assert.ok(customValidation({ hello: 'world' })) + t.assert.ok(!customValidation({})) + t.assert.strictEqual( headerValidation, req.getValidationFunction('headers') ) break default: - t.fail('Invalid id') + t.assert.fail('Invalid id') } reply.send({ hello: 'world' }) @@ -1085,14 +1082,14 @@ test('Nested Context', subtest => { } ) - ntst.test('Should return a validation function - nested', async t => { + await ntst.test('Should return a validation function - nested', async t => { const fastify = Fastify() let called = false const custom = ({ schema, httpPart, url, method }) => { - t.equal(schema, defaultSchema) - t.equal(url, '/') - t.equal(method, 'GET') - t.notOk(httpPart) + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.ok(!httpPart) called = true return () => true @@ -1107,8 +1104,8 @@ test('Nested Context', subtest => { const original = req.compileValidationSchema(defaultSchema) const referenced = req.getValidationFunction(defaultSchema) - t.equal(original, referenced) - t.equal(called, true) + t.assert.strictEqual(original, referenced) + t.assert.strictEqual(called, true) reply.send({ hello: 'world' }) }) @@ -1119,7 +1116,7 @@ test('Nested Context', subtest => { await fastify.inject('/') }) - ntst.test( + await ntst.test( 'Should return undefined if no schema compiled - nested', async t => { const fastify = Fastify() @@ -1136,7 +1133,7 @@ test('Nested Context', subtest => { fastify.get('/', (req, reply) => { const validate = req.compileValidationSchema(defaultSchema) - t.equal(typeof validate, 'function') + t.assert.strictEqual(typeof validate, 'function') reply.send({ hello: 'world' }) }) @@ -1146,8 +1143,8 @@ test('Nested Context', subtest => { instance.get('/', (req, reply) => { const validate = req.getValidationFunction(defaultSchema) - t.notOk(validate) - t.equal(called, 1) + t.assert.ok(!validate) + t.assert.strictEqual(called, 1) reply.send({ hello: 'world' }) }) @@ -1162,7 +1159,7 @@ test('Nested Context', subtest => { } ) - ntst.test('Should per-route defined validation compiler', async t => { + await ntst.test('Should per-route defined validation compiler', async t => { const fastify = Fastify() let validateParent let validateChild @@ -1185,7 +1182,7 @@ test('Nested Context', subtest => { fastify.get('/', (req, reply) => { validateParent = req.compileValidationSchema(defaultSchema) - t.equal(typeof validateParent, 'function') + t.assert.strictEqual(typeof validateParent, 'function') reply.send({ hello: 'world' }) }) @@ -1201,10 +1198,10 @@ test('Nested Context', subtest => { const validate1 = req.compileValidationSchema(defaultSchema) validateChild = req.getValidationFunction(defaultSchema) - t.equal(validate1, validateChild) - t.not(validateParent, validateChild) - t.equal(calledParent, 1) - t.equal(calledChild, 1) + t.assert.strictEqual(validate1, validateChild) + t.assert.notStrictEqual(validateParent, validateChild) + t.assert.strictEqual(calledParent, 1) + t.assert.strictEqual(calledChild, 1) reply.send({ hello: 'world' }) } @@ -1220,10 +1217,10 @@ test('Nested Context', subtest => { }) }) - tst.test('#validate', ntst => { + await tst.test('#validate', async ntst => { ntst.plan(3) - ntst.test( + await ntst.test( 'Should return true/false if input valid - Route without schema', async t => { const fastify = Fastify() @@ -1238,8 +1235,8 @@ test('Nested Context', subtest => { ) const isValid = req.validateInput({ hello: 'string' }, defaultSchema) - t.notOk(isNotValid) - t.ok(isValid) + t.assert.ok(!isNotValid) + t.assert.ok(isValid) reply.send({ hello: 'world' }) }) @@ -1251,7 +1248,7 @@ test('Nested Context', subtest => { } ) - ntst.test( + await ntst.test( 'Should use the custom validator compiler for the route', async t => { const fastify = Fastify() @@ -1264,14 +1261,14 @@ test('Nested Context', subtest => { } const customChild = ({ schema, httpPart, url, method }) => { - t.equal(schema, defaultSchema) - t.equal(url, '/') - t.equal(method, 'GET') - t.equal(httpPart, 'querystring') + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpPart, 'querystring') return input => { childCalled++ - t.same(input, { hello: 'world' }) + t.assert.deepStrictEqual(input, { hello: 'world' }) return true } } @@ -1292,10 +1289,10 @@ test('Nested Context', subtest => { ) const ok2 = req.validateInput({ hello: 'world' }, defaultSchema) - t.ok(ok) - t.ok(ok2) - t.equal(childCalled, 2) - t.equal(parentCalled, 0) + t.assert.ok(ok) + t.assert.ok(ok2) + t.assert.strictEqual(childCalled, 2) + t.assert.strictEqual(parentCalled, 0) reply.send({ hello: 'world' }) } @@ -1308,7 +1305,7 @@ test('Nested Context', subtest => { } ) - ntst.test( + await ntst.test( 'Should return true/false if input valid - With Schema for Route defined and scoped validator compiler', async t => { const validator = new Ajv() @@ -1350,23 +1347,23 @@ test('Nested Context', subtest => { switch (parseInt(params.id)) { case 1: - t.ok(req.validateInput({ hello: 'world' }, 'body')) - t.notOk(req.validateInput({ hello: [], world: 'foo' }, 'body')) + t.assert.ok(req.validateInput({ hello: 'world' }, 'body')) + t.assert.ok(!req.validateInput({ hello: [], world: 'foo' }, 'body')) break case 2: - t.notOk(req.validateInput({ foo: 'something' }, 'querystring')) - t.ok(req.validateInput({ foo: 'bar' }, 'querystring')) + t.assert.ok(!req.validateInput({ foo: 'something' }, 'querystring')) + t.assert.ok(req.validateInput({ foo: 'bar' }, 'querystring')) break case 3: - t.notOk(req.validateInput({ 'x-foo': [] }, 'headers')) - t.ok(req.validateInput({ 'x-foo': 'something' }, 'headers')) + t.assert.ok(!req.validateInput({ 'x-foo': [] }, 'headers')) + t.assert.ok(req.validateInput({ 'x-foo': 'something' }, 'headers')) break case 4: - t.ok(req.validateInput({ id: 1 }, 'params')) - t.notOk(req.validateInput({ id: params.id }, 'params')) + t.assert.ok(req.validateInput({ id: 1 }, 'params')) + t.assert.ok(!req.validateInput({ id: params.id }, 'params')) break default: - t.fail('Invalid id') + t.assert.fail('Invalid id') } reply.send({ hello: 'world' }) @@ -1393,11 +1390,11 @@ test('Nested Context', subtest => { await Promise.all(promises) - t.equal(childCounter.query, 6) // 4 calls made + 2 custom validations - t.equal(childCounter.headers, 6) // 4 calls made + 2 custom validations - t.equal(childCounter.body, 6) // 4 calls made + 2 custom validations - t.equal(childCounter.params, 6) // 4 calls made + 2 custom validations - t.equal(parentCalled, 0) + t.assert.strictEqual(childCounter.query, 6) // 4 calls made + 2 custom validations + t.assert.strictEqual(childCounter.headers, 6) // 4 calls made + 2 custom validations + t.assert.strictEqual(childCounter.body, 6) // 4 calls made + 2 custom validations + t.assert.strictEqual(childCounter.params, 6) // 4 calls made + 2 custom validations + t.assert.strictEqual(parentCalled, 0) } ) }) diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 5ce8d3513c0..aa6c55325fc 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Request = require('../../lib/request') const Context = require('../../lib/context') @@ -47,29 +47,27 @@ test('Regular request', t => { }) req.connection = req.socket const request = new Request('id', 'params', req, 'query', 'log', context) - t.type(request, Request) - t.type(request.validateInput, Function) - t.type(request.getValidationFunction, Function) - t.type(request.compileValidationSchema, Function) - t.equal(request.id, 'id') - t.equal(request.params, 'params') - t.equal(request.raw, req) - t.equal(request.query, 'query') - t.equal(request.headers, headers) - t.equal(request.log, 'log') - t.equal(request.ip, 'ip') - t.equal(request.ips, undefined) - t.equal(request.host, 'hostname') - t.equal(request.body, undefined) - t.equal(request.method, 'GET') - t.equal(request.url, '/') - t.equal(request.originalUrl, '/') - t.equal(request.socket, req.socket) - t.equal(request.protocol, 'http') + t.assert.ok(request instanceof Request) + t.assert.ok(request.validateInput instanceof Function) + t.assert.ok(request.getValidationFunction instanceof Function) + t.assert.ok(request.compileValidationSchema instanceof Function) + t.assert.strictEqual(request.id, 'id') + t.assert.strictEqual(request.params, 'params') + t.assert.strictEqual(request.raw, req) + t.assert.strictEqual(request.query, 'query') + t.assert.strictEqual(request.headers, headers) + t.assert.strictEqual(request.log, 'log') + t.assert.strictEqual(request.ip, 'ip') + t.assert.strictEqual(request.ips, undefined) + t.assert.strictEqual(request.host, 'hostname') + t.assert.strictEqual(request.body, undefined) + t.assert.strictEqual(request.method, 'GET') + t.assert.strictEqual(request.url, '/') + t.assert.strictEqual(request.originalUrl, '/') + t.assert.strictEqual(request.socket, req.socket) + t.assert.strictEqual(request.protocol, 'http') // Aim to not bad property keys (including Symbols) - t.notOk('undefined' in request) - - t.end() + t.assert.ok(!('undefined' in request)) }) test('Request with undefined config', t => { @@ -102,30 +100,28 @@ test('Request with undefined config', t => { }) req.connection = req.socket const request = new Request('id', 'params', req, 'query', 'log', context) - t.type(request, Request) - t.type(request.validateInput, Function) - t.type(request.getValidationFunction, Function) - t.type(request.compileValidationSchema, Function) - t.equal(request.id, 'id') - t.equal(request.params, 'params') - t.equal(request.raw, req) - t.equal(request.query, 'query') - t.equal(request.headers, headers) - t.equal(request.log, 'log') - t.equal(request.ip, 'ip') - t.equal(request.ips, undefined) - t.equal(request.hostname, 'hostname') - t.equal(request.body, undefined) - t.equal(request.method, 'GET') - t.equal(request.url, '/') - t.equal(request.originalUrl, '/') - t.equal(request.socket, req.socket) - t.equal(request.protocol, 'http') + t.assert.ok(request, Request) + t.assert.ok(request.validateInput, Function) + t.assert.ok(request.getValidationFunction, Function) + t.assert.ok(request.compileValidationSchema, Function) + t.assert.strictEqual(request.id, 'id') + t.assert.strictEqual(request.params, 'params') + t.assert.strictEqual(request.raw, req) + t.assert.strictEqual(request.query, 'query') + t.assert.strictEqual(request.headers, headers) + t.assert.strictEqual(request.log, 'log') + t.assert.strictEqual(request.ip, 'ip') + t.assert.strictEqual(request.ips, undefined) + t.assert.strictEqual(request.hostname, 'hostname') + t.assert.strictEqual(request.body, undefined) + t.assert.strictEqual(request.method, 'GET') + t.assert.strictEqual(request.url, '/') + t.assert.strictEqual(request.originalUrl, '/') + t.assert.strictEqual(request.socket, req.socket) + t.assert.strictEqual(request.protocol, 'http') // Aim to not bad property keys (including Symbols) - t.notOk('undefined' in request) - - t.end() + t.assert.ok(!('undefined' in request)) }) test('Regular request - hostname from authority', t => { @@ -141,9 +137,9 @@ test('Regular request - hostname from authority', t => { } const request = new Request('id', 'params', req, 'query', 'log') - t.type(request, Request) - t.equal(request.host, 'authority') - t.equal(request.port, null) + t.assert.ok(request instanceof Request) + t.assert.strictEqual(request.host, 'authority') + t.assert.strictEqual(request.port, null) }) test('Regular request - host header has precedence over authority', t => { @@ -159,9 +155,9 @@ test('Regular request - host header has precedence over authority', t => { headers } const request = new Request('id', 'params', req, 'query', 'log') - t.type(request, Request) - t.equal(request.host, 'hostname') - t.equal(request.port, null) + t.assert.ok(request instanceof Request) + t.assert.strictEqual(request.host, 'hostname') + t.assert.strictEqual(request.port, null) }) test('Request with trust proxy', t => { @@ -202,24 +198,24 @@ test('Request with trust proxy', t => { const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log', context) - t.type(request, TpRequest) - t.equal(request.id, 'id') - t.equal(request.params, 'params') - t.same(request.raw, req) - t.equal(request.query, 'query') - t.equal(request.headers, headers) - t.equal(request.log, 'log') - t.equal(request.ip, '2.2.2.2') - t.same(request.ips, ['ip', '1.1.1.1', '2.2.2.2']) - t.equal(request.host, 'example.com') - t.equal(request.body, undefined) - t.equal(request.method, 'GET') - t.equal(request.url, '/') - t.equal(request.socket, req.socket) - t.equal(request.protocol, 'http') - t.type(request.validateInput, Function) - t.type(request.getValidationFunction, Function) - t.type(request.compileValidationSchema, Function) + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.id, 'id') + t.assert.strictEqual(request.params, 'params') + t.assert.deepStrictEqual(request.raw, req) + t.assert.strictEqual(request.query, 'query') + t.assert.strictEqual(request.headers, headers) + t.assert.strictEqual(request.log, 'log') + t.assert.strictEqual(request.ip, '2.2.2.2') + t.assert.deepStrictEqual(request.ips, ['ip', '1.1.1.1', '2.2.2.2']) + t.assert.strictEqual(request.host, 'example.com') + t.assert.strictEqual(request.body, undefined) + t.assert.strictEqual(request.method, 'GET') + t.assert.strictEqual(request.url, '/') + t.assert.strictEqual(request.socket, req.socket) + t.assert.strictEqual(request.protocol, 'http') + t.assert.ok(request.validateInput instanceof Function) + t.assert.ok(request.getValidationFunction instanceof Function) + t.assert.ok(request.compileValidationSchema instanceof Function) }) test('Request with trust proxy, encrypted', t => { @@ -237,8 +233,8 @@ test('Request with trust proxy, encrypted', t => { const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') - t.type(request, TpRequest) - t.equal(request.protocol, 'https') + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.protocol, 'https') }) test('Request with trust proxy - no x-forwarded-host header', t => { @@ -256,8 +252,8 @@ test('Request with trust proxy - no x-forwarded-host header', t => { const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') - t.type(request, TpRequest) - t.equal(request.host, 'hostname') + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.host, 'hostname') }) test('Request with trust proxy - no x-forwarded-host header and fallback to authority', t => { @@ -275,8 +271,8 @@ test('Request with trust proxy - no x-forwarded-host header and fallback to auth const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') - t.type(request, TpRequest) - t.equal(request.host, 'authority') + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.host, 'authority') }) test('Request with trust proxy - x-forwarded-host header has precedence over host', t => { @@ -295,8 +291,8 @@ test('Request with trust proxy - x-forwarded-host header has precedence over hos const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') - t.type(request, TpRequest) - t.equal(request.host, 'example.com') + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.host, 'example.com') }) test('Request with trust proxy - handles multiple entries in x-forwarded-host/proto', t => { @@ -314,9 +310,9 @@ test('Request with trust proxy - handles multiple entries in x-forwarded-host/pr const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') - t.type(request, TpRequest) - t.equal(request.host, 'example.com') - t.equal(request.protocol, 'https') + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.host, 'example.com') + t.assert.strictEqual(request.protocol, 'https') }) test('Request with trust proxy - plain', t => { @@ -334,7 +330,7 @@ test('Request with trust proxy - plain', t => { const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') - t.same(request.protocol, 'http') + t.assert.deepStrictEqual(request.protocol, 'http') }) test('Request with undefined socket', t => { @@ -349,24 +345,24 @@ test('Request with undefined socket', t => { headers } const request = new Request('id', 'params', req, 'query', 'log') - t.type(request, Request) - t.equal(request.id, 'id') - t.equal(request.params, 'params') - t.same(request.raw, req) - t.equal(request.query, 'query') - t.equal(request.headers, headers) - t.equal(request.log, 'log') - t.equal(request.ip, undefined) - t.equal(request.ips, undefined) - t.equal(request.host, 'hostname') - t.same(request.body, null) - t.equal(request.method, 'GET') - t.equal(request.url, '/') - t.equal(request.protocol, undefined) - t.same(request.socket, req.socket) - t.type(request.validateInput, Function) - t.type(request.getValidationFunction, Function) - t.type(request.compileValidationSchema, Function) + t.assert.ok(request instanceof Request) + t.assert.strictEqual(request.id, 'id') + t.assert.strictEqual(request.params, 'params') + t.assert.deepStrictEqual(request.raw, req) + t.assert.strictEqual(request.query, 'query') + t.assert.strictEqual(request.headers, headers) + t.assert.strictEqual(request.log, 'log') + t.assert.strictEqual(request.ip, undefined) + t.assert.strictEqual(request.ips, undefined) + t.assert.strictEqual(request.host, 'hostname') + t.assert.deepStrictEqual(request.body, undefined) + t.assert.strictEqual(request.method, 'GET') + t.assert.strictEqual(request.url, '/') + t.assert.strictEqual(request.protocol, undefined) + t.assert.deepStrictEqual(request.socket, req.socket) + t.assert.ok(request.validateInput instanceof Function) + t.assert.ok(request.getValidationFunction instanceof Function) + t.assert.ok(request.compileValidationSchema instanceof Function) }) test('Request with trust proxy and undefined socket', t => { @@ -384,5 +380,5 @@ test('Request with trust proxy and undefined socket', t => { const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') - t.same(request.protocol, undefined) + t.assert.deepStrictEqual(request.protocol, undefined) }) diff --git a/test/internals/server.test.js b/test/internals/server.test.js index 615604ed98d..b26a172dfcc 100644 --- a/test/internals/server.test.js +++ b/test/internals/server.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const proxyquire = require('proxyquire') const Fastify = require('../../fastify') @@ -15,7 +15,7 @@ test('start listening', async t => { const { server, listen } = createServer({}, handler) await listen.call(Fastify(), { port: 0, host: 'localhost' }) server.close() - t.pass('server started') + t.assert.ok(true, 'server started') }) test('DNS errors does not stop the main server on localhost - promise interface', async t => { @@ -29,10 +29,10 @@ test('DNS errors does not stop the main server on localhost - promise interface' const { server, listen } = createServer({}, handler) await listen.call(Fastify(), { port: 0, host: 'localhost' }) server.close() - t.pass('server started') + t.assert.ok(true, 'server started') }) -test('DNS errors does not stop the main server on localhost - callback interface', t => { +test('DNS errors does not stop the main server on localhost - callback interface', (t, done) => { t.plan(2) const { createServer } = proxyquire('../../lib/server', { 'node:dns': { @@ -43,13 +43,14 @@ test('DNS errors does not stop the main server on localhost - callback interface }) const { server, listen } = createServer({}, handler) listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => { - t.error(err) + t.assert.ifError(err) server.close() - t.pass('server started') + t.assert.ok(true, 'server started') + done() }) }) -test('DNS returns empty binding', t => { +test('DNS returns empty binding', (t, done) => { t.plan(2) const { createServer } = proxyquire('../../lib/server', { 'node:dns': { @@ -60,13 +61,14 @@ test('DNS returns empty binding', t => { }) const { server, listen } = createServer({}, handler) listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => { - t.error(err) + t.assert.ifError(err) server.close() - t.pass('server started') + t.assert.ok(true, 'server started') + done() }) }) -test('DNS returns more than two binding', t => { +test('DNS returns more than two binding', (t, done) => { t.plan(2) const { createServer } = proxyquire('../../lib/server', { 'node:dns': { @@ -81,8 +83,9 @@ test('DNS returns more than two binding', t => { }) const { server, listen } = createServer({}, handler) listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => { - t.error(err) + t.assert.ifError(err) server.close() - t.pass('server started') + t.assert.ok(true, 'server started') + done() }) }) diff --git a/test/internals/validation.test.js b/test/internals/validation.test.js index 9c0da49543b..7fe4738c56a 100644 --- a/test/internals/validation.test.js +++ b/test/internals/validation.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Ajv = require('ajv') const ajv = new Ajv({ coerceTypes: true }) @@ -13,11 +12,11 @@ const { kSchemaVisited } = require('../../lib/symbols') test('Symbols', t => { t.plan(5) - t.equal(typeof symbols.responseSchema, 'symbol') - t.equal(typeof symbols.bodySchema, 'symbol') - t.equal(typeof symbols.querystringSchema, 'symbol') - t.equal(typeof symbols.paramsSchema, 'symbol') - t.equal(typeof symbols.headersSchema, 'symbol') + t.assert.strictEqual(typeof symbols.responseSchema, 'symbol') + t.assert.strictEqual(typeof symbols.bodySchema, 'symbol') + t.assert.strictEqual(typeof symbols.querystringSchema, 'symbol') + t.assert.strictEqual(typeof symbols.paramsSchema, 'symbol') + t.assert.strictEqual(typeof symbols.headersSchema, 'symbol') }) ;['compileSchemasForValidation', @@ -26,15 +25,15 @@ test('Symbols', t => { t.plan(2) const context = {} validation[func](context) - t.equal(typeof context[symbols.bodySchema], 'undefined') - t.equal(typeof context[symbols.responseSchema], 'undefined') + t.assert.strictEqual(typeof context[symbols.bodySchema], 'undefined') + t.assert.strictEqual(typeof context[symbols.responseSchema], 'undefined') }) test(`${func} schema - missing output schema`, t => { t.plan(1) const context = { schema: {} } validation[func](context, null) - t.equal(typeof context[symbols.responseSchema], 'undefined') + t.assert.strictEqual(typeof context[symbols.responseSchema], 'undefined') }) }) @@ -59,8 +58,8 @@ test('build schema - output schema', t => { } } validation.compileSchemasForSerialization(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.equal(typeof opts[symbols.responseSchema]['2xx'], 'function') - t.equal(typeof opts[symbols.responseSchema]['201'], 'function') + t.assert.strictEqual(typeof opts[symbols.responseSchema]['2xx'], 'function') + t.assert.strictEqual(typeof opts[symbols.responseSchema]['201'], 'function') }) test('build schema - body schema', t => { @@ -76,7 +75,7 @@ test('build schema - body schema', t => { } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.equal(typeof opts[symbols.bodySchema], 'function') + t.assert.strictEqual(typeof opts[symbols.bodySchema], 'function') }) test('build schema - body with multiple content type schemas', t => { @@ -101,8 +100,8 @@ test('build schema - body with multiple content type schemas', t => { } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.type(opts[symbols.bodySchema]['application/json'], 'function') - t.type(opts[symbols.bodySchema]['text/plain'], 'function') + t.assert.ok(opts[symbols.bodySchema]['application/json'], 'function') + t.assert.ok(opts[symbols.bodySchema]['text/plain'], 'function') }) test('build schema - avoid repeated normalize schema', t => { @@ -119,9 +118,9 @@ test('build schema - avoid repeated normalize schema', t => { } } opts.schema = normalizeSchema(opts.schema, serverConfig) - t.not(kSchemaVisited, undefined) - t.equal(opts.schema[kSchemaVisited], true) - t.equal(opts.schema, normalizeSchema(opts.schema, serverConfig)) + t.assert.notStrictEqual(kSchemaVisited, undefined) + t.assert.strictEqual(opts.schema[kSchemaVisited], true) + t.assert.strictEqual(opts.schema, normalizeSchema(opts.schema, serverConfig)) }) test('build schema - query schema', t => { @@ -139,8 +138,8 @@ test('build schema - query schema', t => { } opts.schema = normalizeSchema(opts.schema, serverConfig) validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.type(opts[symbols.querystringSchema].schema.type, 'string') - t.equal(typeof opts[symbols.querystringSchema], 'function') + t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string') + t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function') }) test('build schema - query schema abbreviated', t => { @@ -158,8 +157,8 @@ test('build schema - query schema abbreviated', t => { } opts.schema = normalizeSchema(opts.schema, serverConfig) validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.type(opts[symbols.querystringSchema].schema.type, 'string') - t.equal(typeof opts[symbols.querystringSchema], 'function') + t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string') + t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function') }) test('build schema - querystring schema', t => { @@ -175,8 +174,8 @@ test('build schema - querystring schema', t => { } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.type(opts[symbols.querystringSchema].schema.type, 'string') - t.equal(typeof opts[symbols.querystringSchema], 'function') + t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string') + t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function') }) test('build schema - querystring schema abbreviated', t => { @@ -194,8 +193,8 @@ test('build schema - querystring schema abbreviated', t => { } opts.schema = normalizeSchema(opts.schema, serverConfig) validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.type(opts[symbols.querystringSchema].schema.type, 'string') - t.equal(typeof opts[symbols.querystringSchema], 'function') + t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string') + t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function') }) test('build schema - must throw if querystring and query schema exist', t => { @@ -220,8 +219,8 @@ test('build schema - must throw if querystring and query schema exist', t => { } opts.schema = normalizeSchema(opts.schema, serverConfig) } catch (err) { - t.equal(err.code, 'FST_ERR_SCH_DUPLICATE') - t.equal(err.message, 'Schema with \'querystring\' already present!') + t.assert.strictEqual(err.code, 'FST_ERR_SCH_DUPLICATE') + t.assert.strictEqual(err.message, 'Schema with \'querystring\' already present!') } }) @@ -238,7 +237,7 @@ test('build schema - params schema', t => { } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.equal(typeof opts[symbols.paramsSchema], 'function') + t.assert.strictEqual(typeof opts[symbols.paramsSchema], 'function') }) test('build schema - headers schema', t => { @@ -254,7 +253,7 @@ test('build schema - headers schema', t => { } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) - t.equal(typeof opts[symbols.headersSchema], 'function') + t.assert.strictEqual(typeof opts[symbols.headersSchema], 'function') }) test('build schema - headers are lowercase', t => { @@ -270,7 +269,7 @@ test('build schema - headers are lowercase', t => { } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { - t.ok(schema.properties['content-type'], 'lowercase content-type exists') + t.assert.ok(schema.properties['content-type'], 'lowercase content-type exists') return () => { } }) }) @@ -285,7 +284,7 @@ test('build schema - headers are not lowercased in case of custom object', t => } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { - t.type(schema, Headers) + t.assert.ok(schema, Headers) return () => { } }) }) @@ -300,7 +299,7 @@ test('build schema - headers are not lowercased in case of custom validator prov } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { - t.type(schema, Headers) + t.assert.ok(schema, Headers) return () => { } }, true) }) @@ -318,7 +317,7 @@ test('build schema - uppercased headers are not included', t => { } } validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { - t.notOk('Content-Type' in schema.properties, 'uppercase does not exist') + t.assert.ok(!('Content-Type' in schema.properties), 'uppercase does not exist') return () => { } }) }) @@ -333,7 +332,7 @@ test('build schema - mixed schema types are individually skipped or normalized', body: new CustomSchemaClass() }, assertions: (schema) => { - t.type(schema.body, CustomSchemaClass) + t.assert.ok(schema.body, CustomSchemaClass) } }, { schema: { @@ -342,7 +341,7 @@ test('build schema - mixed schema types are individually skipped or normalized', } }, assertions: (schema) => { - t.type(schema.response[200], CustomSchemaClass) + t.assert.ok(schema.response[200], CustomSchemaClass) } }] From 29c121d44281ba553b9d366c65c380dba4ea290a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulius=20Vali=C5=ABnas?= <66480813+paulius-valiunas@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:47:28 +0300 Subject: [PATCH 0776/1295] types: make plugin options required if they have any defined props (#5637) --- test/types/plugin.test-d.ts | 22 +++++++++++++++------- test/types/register.test-d.ts | 24 ++++++++++++++++++++---- types/register.d.ts | 15 ++++++++++++--- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/test/types/plugin.test-d.ts b/test/types/plugin.test-d.ts index 410c7fa3f3b..e53c944f4fd 100644 --- a/test/types/plugin.test-d.ts +++ b/test/types/plugin.test-d.ts @@ -10,8 +10,16 @@ interface TestOptions extends FastifyPluginOptions { option1: string; option2: boolean; } -const testPluginOpts: FastifyPluginCallback = function (instance, opts, done) { } -const testPluginOptsAsync: FastifyPluginAsync = async function (instance, opts) { } +const testOptions: TestOptions = { + option1: 'a', + option2: false, +} +const testPluginOpts: FastifyPluginCallback = function (instance, opts, done) { + expectType(opts) +} +const testPluginOptsAsync: FastifyPluginAsync = async function (instance, opts) { + expectType(opts) +} const testPluginOptsWithType = (instance: FastifyInstance, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { } const testPluginOptsWithTypeAsync = async (instance: FastifyInstance, opts: FastifyPluginOptions) => { } @@ -39,7 +47,7 @@ expectAssignable(fastify().register(function (instance, opts): expectAssignable(fastify().register(async function (instance, opts) { }, () => { })) expectAssignable(fastify().register(async function (instance, opts) { }, { logLevel: 'info', prefix: 'foobar' })) -expectError(fastify().register(function (instance, opts, done) { }, { logLevel: '' })) // must use a valid logLevel +expectError(fastify().register(function (instance, opts, done) { }, { ...testOptions, logLevel: '' })) // must use a valid logLevel const httpsServer = fastify({ https: {} }) expectError & Promise>>(httpsServer) @@ -48,7 +56,7 @@ expectType { }) .ready((_error) => { }) .close(() => { }) @@ -57,7 +65,7 @@ httpsServer expectAssignable>(httpsServer.after()) expectAssignable>(httpsServer.close()) expectAssignable>(httpsServer.ready()) -expectAssignable>(httpsServer.register(testPluginOpts)) +expectAssignable>(httpsServer.register(testPluginOpts, testOptions)) expectAssignable>(httpsServer.register(testPluginOptsWithType)) expectAssignable>(httpsServer.register(testPluginOptsWithTypeAsync)) expectAssignable>(httpsServer.register(testPluginOptsWithType, { prefix: '/test' })) @@ -66,6 +74,6 @@ expectAssignable>(httpsServer.register(testPluginOptsWith /* eslint-disable @typescript-eslint/no-unused-vars */ async function testAsync (): Promise { await httpsServer - .register(testPluginOpts) - .register(testPluginOpts) + .register(testPluginOpts, testOptions) + .register(testPluginOpts, testOptions) } diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index 2b60cbfa3b1..ab115fae314 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -48,14 +48,22 @@ const testPluginWithHttp2: FastifyPluginCallback = fun const testPluginWithHttp2Async: FastifyPluginAsync = async function (instance, opts) { } const testPluginWithHttp2WithType = (instance: ServerWithHttp2, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { } const testPluginWithHttp2WithTypeAsync = async (instance: ServerWithHttp2, opts: FastifyPluginOptions) => { } +const testOptions: TestOptions = { + option1: 'a', + option2: false, +} expectAssignable(serverWithHttp2.register(testPluginCallback)) expectAssignable(serverWithHttp2.register(testPluginAsync)) expectAssignable(serverWithHttp2.register(testPluginOpts)) expectAssignable(serverWithHttp2.register(testPluginOptsAsync)) expectAssignable(serverWithHttp2.register(testPluginOptsWithType)) expectAssignable(serverWithHttp2.register(testPluginOptsWithTypeAsync)) -expectAssignable(serverWithHttp2.register(testPluginWithHttp2)) -expectAssignable(serverWithHttp2.register(testPluginWithHttp2Async)) +// @ts-expect-error +serverWithHttp2.register(testPluginWithHttp2) +expectAssignable(serverWithHttp2.register(testPluginWithHttp2, testOptions)) +// @ts-expect-error +serverWithHttp2.register(testPluginWithHttp2Async) +expectAssignable(serverWithHttp2.register(testPluginWithHttp2Async, testOptions)) expectAssignable(serverWithHttp2.register(testPluginWithHttp2WithType)) expectAssignable(serverWithHttp2.register(testPluginWithHttp2WithTypeAsync)) expectAssignable(serverWithHttp2.register((instance) => { @@ -85,8 +93,12 @@ expectAssignable(serverWithTypeProvider.register(testPlu expectAssignable(serverWithTypeProvider.register(testPluginOptsAsync)) expectAssignable(serverWithTypeProvider.register(testPluginOptsWithType)) expectAssignable(serverWithTypeProvider.register(testPluginOptsWithTypeAsync)) -expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProvider)) -expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProviderAsync)) +// @ts-expect-error +serverWithTypeProvider.register(testPluginWithTypeProvider) +expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProvider, testOptions)) +// @ts-expect-error +serverWithTypeProvider.register(testPluginWithTypeProviderAsync) +expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProviderAsync, testOptions)) expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProviderWithType)) expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProviderWithTypeAsync)) expectAssignable(serverWithTypeProvider.register((instance) => { @@ -128,8 +140,12 @@ expectAssignable(serverWithTypeProviderAndLogge expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsAsync)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsWithType)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsWithTypeAsync)) +// @ts-expect-error expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger, testOptions)) +// @ts-expect-error expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync, testOptions)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithType)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithTypeAsync)) expectAssignable(serverWithTypeProviderAndLogger.register((instance) => { diff --git a/types/register.d.ts b/types/register.d.ts index de274836d66..b6a7625f75f 100644 --- a/types/register.d.ts +++ b/types/register.d.ts @@ -18,16 +18,25 @@ export type FastifyRegisterOptions = (RegisterOptions & Options) | ((in * Function for adding a plugin to fastify. The options are inferred from the passed in FastifyPlugin parameter. */ export interface FastifyRegister { + ( + plugin: FastifyPluginCallback + ): T; ( plugin: FastifyPluginCallback, - opts?: FastifyRegisterOptions + opts: FastifyRegisterOptions + ): T; + ( + plugin: FastifyPluginAsync ): T; ( plugin: FastifyPluginAsync, - opts?: FastifyRegisterOptions + opts: FastifyRegisterOptions + ): T; + ( + plugin: FastifyPluginCallback | FastifyPluginAsync | Promise<{ default: FastifyPluginCallback }> | Promise<{ default: FastifyPluginAsync }>, ): T; ( plugin: FastifyPluginCallback | FastifyPluginAsync | Promise<{ default: FastifyPluginCallback }> | Promise<{ default: FastifyPluginAsync }>, - opts?: FastifyRegisterOptions + opts: FastifyRegisterOptions ): T; } From 27747ad9fddd62cc0e12ce1b85f2b05aa62cc171 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 19 Oct 2024 09:36:23 +0100 Subject: [PATCH 0777/1295] build(deps-dev): lock typescript minor version (#5748) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e776f6f7830..380631d89a8 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,7 @@ "split2": "^4.2.0", "tap": "^21.0.0", "tsd": "^0.31.0", - "typescript": "^5.4.5", + "typescript": "~5.4.5", "undici": "^6.13.0", "vary": "^1.1.2", "yup": "^1.4.0" From 3ca649315e81e69b8b03fa5a973318cc3ed2257c Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 19 Oct 2024 14:29:27 +0100 Subject: [PATCH 0778/1295] perf(lib/pluginUtils): convert unused capture group to non-capture group (#5749) --- lib/pluginUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index def8f0e7930..88aea6f01ef 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -110,7 +110,7 @@ function checkVersion (fn) { const requiredVersion = meta.fastify - const fastifyRc = /-(rc|pre|alpha).+$/.test(this.version) + const fastifyRc = /-(?:rc|pre|alpha).+$/.test(this.version) if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) { // A Fastify release candidate phase is taking place. In order to reduce // the effort needed to test plugins with the RC, we allow plugins targeting From 9d9431dd3ab5f365b640ff87879917e4d510fd7b Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 19 Oct 2024 19:52:46 +0100 Subject: [PATCH 0779/1295] refactor(lib/validation): replace regexp `.exec()` with `.test()` (#5750) Signed-off-by: Frazer Smith --- lib/validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/validation.js b/lib/validation.js index dd3f74e4852..1584ab5d6c2 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -24,7 +24,7 @@ function compileSchemasForSerialization (context, compile) { .reduce(function (acc, statusCode) { const schema = context.schema.response[statusCode] statusCode = statusCode.toLowerCase() - if (!scChecker.exec(statusCode)) { + if (!scChecker.test(statusCode)) { throw new FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX() } From 5aa86f877fe02ccd7bddba6ceeb4d4597140c290 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 19 Oct 2024 20:34:22 +0100 Subject: [PATCH 0780/1295] refactor(lib/validation): remove redundant regex quantifier (#5751) --- lib/validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/validation.js b/lib/validation.js index 1584ab5d6c2..af9ea4bdb78 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -7,7 +7,7 @@ const { kSchemaBody: bodySchema, kSchemaResponse: responseSchema } = require('./symbols') -const scChecker = /^[1-5]{1}[0-9]{2}$|^[1-5]xx$|^default$/ +const scChecker = /^[1-5](?:\d{2}|xx)$|^default$/ const { FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX From e61c394745259f79bc96afbb80e38dd512bcd437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Mon, 21 Oct 2024 16:04:32 +0200 Subject: [PATCH 0781/1295] chore: getFuncPreview limit split size (#5754) --- lib/pluginUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 88aea6f01ef..bac8664b4a1 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -48,7 +48,7 @@ function getPluginName (func) { function getFuncPreview (func) { // takes the first two lines of the function if nothing else works - return func.toString().split('\n').slice(0, 2).map(s => s.trim()).join(' -- ') + return func.toString().split('\n', 2).map(s => s.trim()).join(' -- ') } function getDisplayName (fn) { From b824dbe7e7f462d43095da0119792cccc130c9a1 Mon Sep 17 00:00:00 2001 From: ChinoUkaegbu <77782533+ChinoUkaegbu@users.noreply.github.com> Date: Tue, 22 Oct 2024 07:31:26 +0100 Subject: [PATCH 0782/1295] refactor: change var instances to const or let (#5759) --- lib/contentTypeParser.js | 6 ++---- lib/context.js | 3 +-- lib/reply.js | 7 +++---- lib/request.js | 6 ++---- lib/route.js | 3 +-- 5 files changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index ab2254808a8..281cde71828 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -113,8 +113,7 @@ ContentTypeParser.prototype.getParser = function (contentType) { if (parser !== undefined) return parser const caseInsensitiveContentType = contentType.toLowerCase() - // eslint-disable-next-line no-var - for (var i = 0; i !== this.parserList.length; ++i) { + for (let i = 0; i !== this.parserList.length; ++i) { const parserListItem = this.parserList[i] if ( caseInsensitiveContentType.slice(0, parserListItem.length) === parserListItem && @@ -130,8 +129,7 @@ ContentTypeParser.prototype.getParser = function (contentType) { } } - // eslint-disable-next-line no-var - for (var j = 0; j !== this.parserRegExpList.length; ++j) { + for (let j = 0; j !== this.parserRegExpList.length; ++j) { const parserRegExp = this.parserRegExpList[j] if (parserRegExp.test(contentType)) { parser = this.customParsers.get(parserRegExp.toString()) diff --git a/lib/context.js b/lib/context.js index 9d86908a480..d6d1f6aa90d 100644 --- a/lib/context.js +++ b/lib/context.js @@ -85,8 +85,7 @@ function defaultSchemaErrorFormatter (errors, dataVar) { let text = '' const separator = ', ' - // eslint-disable-next-line no-var - for (var i = 0; i !== errors.length; ++i) { + for (let i = 0; i !== errors.length; ++i) { const e = errors[i] text += dataVar + (e.instancePath || '') + ' ' + e.message + separator } diff --git a/lib/reply.js b/lib/reply.js index d3d0c1eccad..9478a1f55fc 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -263,8 +263,7 @@ Reply.prototype.header = function (key, value = '') { Reply.prototype.headers = function (headers) { const keys = Object.keys(headers) - /* eslint-disable no-var */ - for (var i = 0; i !== keys.length; ++i) { + for (let i = 0; i !== keys.length; ++i) { const key = keys[i] this.header(key, headers[key]) } @@ -868,9 +867,9 @@ function buildReply (R) { this[kReplyEndTime] = undefined this.log = log - var prop + let prop - for (var i = 0; i < props.length; i++) { + for (let i = 0; i < props.length; i++) { prop = props[i] this[prop.key] = prop.value } diff --git a/lib/request.js b/lib/request.js index b37fe89c22f..75b88c5d958 100644 --- a/lib/request.js +++ b/lib/request.js @@ -73,10 +73,8 @@ function buildRegularRequest (R) { this.log = log this.body = undefined - // eslint-disable-next-line no-var - var prop - // eslint-disable-next-line no-var - for (var i = 0; i < props.length; i++) { + let prop + for (let i = 0; i < props.length; i++) { prop = props[i] this[prop.key] = prop.value } diff --git a/lib/route.js b/lib/route.js index 3d55a6074c3..ecefbd9b39c 100644 --- a/lib/route.js +++ b/lib/route.js @@ -201,8 +201,7 @@ function buildRouting (options) { const path = opts.url || opts.path || '' if (Array.isArray(opts.method)) { - // eslint-disable-next-line no-var - for (var i = 0; i < opts.method.length; ++i) { + for (let i = 0; i < opts.method.length; ++i) { opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i]) validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema) } From 1befac43ac2af71c5e75acb85aa5281695dc2195 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Tue, 22 Oct 2024 16:44:37 -0400 Subject: [PATCH 0783/1295] test: migrate tests from tap to node:test and c8 (#5760) * chore: migrate from tap to node:test and c8 * fix: remove console log --- test/build/error-serializer.test.js | 7 +- test/build/version.test.js | 5 +- test/versioned-routes.test.js | 203 +++++++++++++++------------- test/web-api.test.js | 85 +++++++----- test/wrapThenable.test.js | 19 +-- 5 files changed, 171 insertions(+), 148 deletions(-) diff --git a/test/build/error-serializer.test.js b/test/build/error-serializer.test.js index d14a428c542..a865452b75d 100644 --- a/test/build/error-serializer.test.js +++ b/test/build/error-serializer.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const fs = require('node:fs') const path = require('node:path') const { loadESLint } = require('eslint') @@ -22,7 +21,7 @@ test('check generated code syntax', async (t) => { // if there are any invalid syntax // fatal count will be greater than 0 - t.equal(result[0].fatalErrorCount, 0) + t.assert.strictEqual(result[0].fatalErrorCount, 0) }) const isPrepublish = !!process.env.PREPUBLISH @@ -33,5 +32,5 @@ test('ensure the current error serializer is latest', { skip: !isPrepublish }, a const current = await fs.promises.readFile(path.resolve('lib/error-serializer.js')) // line break should not be a problem depends on system - t.equal(unifyLineBreak(current), unifyLineBreak(code)) + t.assert.strictEqual(unifyLineBreak(current), unifyLineBreak(code)) }) diff --git a/test/build/version.test.js b/test/build/version.test.js index fe6403cb7e1..d9b6479d9ea 100644 --- a/test/build/version.test.js +++ b/test/build/version.test.js @@ -2,8 +2,7 @@ const fs = require('node:fs') const path = require('node:path') -const t = require('tap') -const test = t.test +const { test } = require('node:test') const fastify = require('../../fastify')() test('should be the same as package.json', t => { @@ -11,5 +10,5 @@ test('should be the same as package.json', t => { const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json')).toString('utf8')) - t.equal(fastify.version, json.version) + t.assert.strictEqual(fastify.version, json.version) }) diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js index 0ba950dee8a..c286f192615 100644 --- a/test/versioned-routes.test.js +++ b/test/versioned-routes.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test, before } = require('tap') +const { test, before } = require('node:test') const helper = require('./helper') const Fastify = require('..') const sget = require('simple-get').concat @@ -15,7 +15,7 @@ before(async function () { [localhost] = await helper.getLoopbackHost() }) -test('Should register a versioned route', t => { +test('Should register a versioned route', (t, done) => { t.plan(11) const fastify = Fastify() @@ -35,9 +35,9 @@ test('Should register a versioned route', t => { 'Accept-Version': '1.x' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ @@ -47,9 +47,9 @@ test('Should register a versioned route', t => { 'Accept-Version': '1.2.x' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ @@ -59,9 +59,9 @@ test('Should register a versioned route', t => { 'Accept-Version': '1.2.0' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ @@ -71,12 +71,13 @@ test('Should register a versioned route', t => { 'Accept-Version': '1.2.1' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('Should register a versioned route via route constraints', t => { +test('Should register a versioned route via route constraints', (t, done) => { t.plan(6) const fastify = Fastify() @@ -96,9 +97,9 @@ test('Should register a versioned route via route constraints', t => { 'Accept-Version': '1.x' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ @@ -108,13 +109,14 @@ test('Should register a versioned route via route constraints', t => { 'Accept-Version': '1.2.x' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) -test('Should register the same route with different versions', t => { +test('Should register the same route with different versions', (t, done) => { t.plan(8) const fastify = Fastify() @@ -143,9 +145,9 @@ test('Should register the same route with different versions', t => { 'Accept-Version': '1.x' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, '1.3.0') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, '1.3.0') }) fastify.inject({ @@ -155,9 +157,9 @@ test('Should register the same route with different versions', t => { 'Accept-Version': '1.2.x' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, '1.2.0') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, '1.2.0') }) fastify.inject({ @@ -167,12 +169,13 @@ test('Should register the same route with different versions', t => { 'Accept-Version': '2.x' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('The versioned route should take precedence', t => { +test('The versioned route should take precedence', (t, done) => { t.plan(3) const fastify = Fastify() @@ -200,13 +203,14 @@ test('The versioned route should take precedence', t => { 'Accept-Version': '1.x' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) -test('Versioned route but not version header should return a 404', t => { +test('Versioned route but not version header should return a 404', (t, done) => { t.plan(2) const fastify = Fastify() @@ -223,12 +227,13 @@ test('Versioned route but not version header should return a 404', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('Should register a versioned route', t => { +test('Should register a versioned route', (t, done) => { t.plan(6) const fastify = Fastify() @@ -242,8 +247,8 @@ test('Should register a versioned route', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', @@ -252,9 +257,9 @@ test('Should register a versioned route', t => { 'Accept-Version': '1.x' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) sget({ @@ -264,13 +269,14 @@ test('Should register a versioned route', t => { 'Accept-Version': '2.x' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) }) -test('Shorthand route declaration', t => { +test('Shorthand route declaration', (t, done) => { t.plan(5) const fastify = Fastify() @@ -285,9 +291,9 @@ test('Shorthand route declaration', t => { 'Accept-Version': '1.x' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ @@ -297,27 +303,28 @@ test('Shorthand route declaration', t => { 'Accept-Version': '1.2.1' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('The not found handler should not erase the Accept-Version header', t => { +test('The not found handler should not erase the Accept-Version header', (t, done) => { t.plan(13) const fastify = Fastify() fastify.addHook('onRequest', function (req, reply, done) { - t.same(req.headers['accept-version'], '2.x') + t.assert.deepStrictEqual(req.headers['accept-version'], '2.x') done() }) fastify.addHook('preValidation', function (req, reply, done) { - t.same(req.headers['accept-version'], '2.x') + t.assert.deepStrictEqual(req.headers['accept-version'], '2.x') done() }) fastify.addHook('preHandler', function (req, reply, done) { - t.same(req.headers['accept-version'], '2.x') + t.assert.deepStrictEqual(req.headers['accept-version'], '2.x') done() }) @@ -331,14 +338,14 @@ test('The not found handler should not erase the Accept-Version header', t => { }) fastify.setNotFoundHandler(function (req, reply) { - t.same(req.headers['accept-version'], '2.x') + t.assert.deepStrictEqual(req.headers['accept-version'], '2.x') // we check if the symbol is exposed on key or not for (const key in req.headers) { - t.same(typeof key, 'string') + t.assert.deepStrictEqual(typeof key, 'string') } for (const key of Object.keys(req.headers)) { - t.same(typeof key, 'string') + t.assert.deepStrictEqual(typeof key, 'string') } reply.code(404).send('not found handler') @@ -351,13 +358,14 @@ test('The not found handler should not erase the Accept-Version header', t => { 'Accept-Version': '2.x' } }, (err, res) => { - t.error(err) - t.same(res.payload, 'not found handler') - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'not found handler') + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('Bad accept version (inject)', t => { +test('Bad accept version (inject)', (t, done) => { t.plan(4) const fastify = Fastify() @@ -377,8 +385,8 @@ test('Bad accept version (inject)', t => { 'Accept-Version': 'a.b.c' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) }) fastify.inject({ @@ -388,12 +396,13 @@ test('Bad accept version (inject)', t => { 'Accept-Version': 12 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('Bad accept version (server)', t => { +test('Bad accept version (server)', (t, done) => { t.plan(5) const fastify = Fastify() @@ -407,8 +416,8 @@ test('Bad accept version (server)', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', @@ -417,8 +426,8 @@ test('Bad accept version (server)', t => { 'Accept-Version': 'a.b.c' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) }) sget({ @@ -428,13 +437,14 @@ test('Bad accept version (server)', t => { 'Accept-Version': 12 } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) }) -test('test log stream', t => { +test('test log stream', (t, done) => { t.plan(3) const stream = split(JSON.parse) const fastify = Fastify({ @@ -449,8 +459,8 @@ test('test log stream', t => { }) fastify.listen({ port: 0, host: localhost }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) http.get({ host: fastify.server.address().hostname, @@ -464,16 +474,17 @@ test('test log stream', t => { stream.once('data', listenAtLogLine => { stream.once('data', line => { - t.equal(line.req.version, '1.x') + t.assert.strictEqual(line.req.version, '1.x') stream.once('data', line => { - t.equal(line.req.version, '1.x') + t.assert.strictEqual(line.req.version, '1.x') + done() }) }) }) }) }) -test('Should register a versioned route with custom versioning strategy', t => { +test('Should register a versioned route with custom versioning strategy', (t, done) => { t.plan(8) const customVersioning = { @@ -523,9 +534,9 @@ test('Should register a versioned route with custom versioning strategy', t => { Accept: 'application/vnd.example.api+json;version=2' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from route v2' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from route v2' }) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ @@ -535,9 +546,9 @@ test('Should register a versioned route with custom versioning strategy', t => { Accept: 'application/vnd.example.api+json;version=3' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from route v3' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from route v3' }) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ @@ -547,12 +558,13 @@ test('Should register a versioned route with custom versioning strategy', t => { Accept: 'application/vnd.example.api+json;version=4' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('Vary header check (for documentation example)', t => { +test('Vary header check (for documentation example)', (t, done) => { t.plan(8) const fastify = Fastify() fastify.addHook('onSend', async (req, reply) => { @@ -589,19 +601,20 @@ test('Vary header check (for documentation example)', t => { 'Accept-Version': '1.x' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) - t.equal(res.headers.vary, 'Accept-Version') + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.vary, 'Accept-Version') }) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) - t.equal(res.headers.vary, undefined) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.vary, undefined) + done() }) }) diff --git a/test/web-api.test.js b/test/web-api.test.js index 5441d2729c8..d8828696af8 100644 --- a/test/web-api.test.js +++ b/test/web-api.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../fastify') const fs = require('node:fs') const { Readable } = require('node:stream') @@ -24,8 +23,8 @@ test('should response with a ReadableStream', async (t) => { const expected = await fs.promises.readFile(__filename) - t.equal(statusCode, 200) - t.equal(expected.toString(), body.toString()) + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(expected.toString(), body.toString()) }) test('should response with a Response', async (t) => { @@ -51,9 +50,9 @@ test('should response with a Response', async (t) => { const expected = await fs.promises.readFile(__filename) - t.equal(statusCode, 200) - t.equal(expected.toString(), body.toString()) - t.equal(headers.hello, 'world') + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(expected.toString(), body.toString()) + t.assert.strictEqual(headers.hello, 'world') }) test('should response with a Response 204', async (t) => { @@ -76,9 +75,9 @@ test('should response with a Response 204', async (t) => { body } = await fastify.inject({ method: 'GET', path: '/' }) - t.equal(statusCode, 204) - t.equal(body, '') - t.equal(headers.hello, 'world') + t.assert.strictEqual(statusCode, 204) + t.assert.strictEqual(body, '') + t.assert.strictEqual(headers.hello, 'world') }) test('should response with a Response 304', async (t) => { @@ -101,9 +100,9 @@ test('should response with a Response 304', async (t) => { body } = await fastify.inject({ method: 'GET', path: '/' }) - t.equal(statusCode, 304) - t.equal(body, '') - t.equal(headers.hello, 'world') + t.assert.strictEqual(statusCode, 304) + t.assert.strictEqual(body, '') + t.assert.strictEqual(headers.hello, 'world') }) test('should response with a Response without body', async (t) => { @@ -126,9 +125,9 @@ test('should response with a Response without body', async (t) => { body } = await fastify.inject({ method: 'GET', path: '/' }) - t.equal(statusCode, 200) - t.equal(body, '') - t.equal(headers.hello, 'world') + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(body, '') + t.assert.strictEqual(headers.hello, 'world') }) test('able to use in onSend hook - ReadableStream', async (t) => { @@ -142,7 +141,7 @@ test('able to use in onSend hook - ReadableStream', async (t) => { }) fastify.addHook('onSend', (request, reply, payload, done) => { - t.equal(Object.prototype.toString.call(payload), '[object ReadableStream]') + t.assert.strictEqual(Object.prototype.toString.call(payload), '[object ReadableStream]') done(null, new Response(payload, { status: 200, headers: { @@ -159,9 +158,9 @@ test('able to use in onSend hook - ReadableStream', async (t) => { const expected = await fs.promises.readFile(__filename) - t.equal(statusCode, 200) - t.equal(expected.toString(), body.toString()) - t.equal(headers.hello, 'world') + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(expected.toString(), body.toString()) + t.assert.strictEqual(headers.hello, 'world') }) test('able to use in onSend hook - Response', async (t) => { @@ -180,7 +179,7 @@ test('able to use in onSend hook - Response', async (t) => { }) fastify.addHook('onSend', (request, reply, payload, done) => { - t.equal(Object.prototype.toString.call(payload), '[object Response]') + t.assert.strictEqual(Object.prototype.toString.call(payload), '[object Response]') done(null, new Response(payload.body, { status: 200, headers: payload.headers @@ -195,9 +194,9 @@ test('able to use in onSend hook - Response', async (t) => { const expected = await fs.promises.readFile(__filename) - t.equal(statusCode, 200) - t.equal(expected.toString(), body.toString()) - t.equal(headers.hello, 'world') + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(expected.toString(), body.toString()) + t.assert.strictEqual(headers.hello, 'world') }) test('Error when Response.bodyUsed', async (t) => { @@ -216,31 +215,37 @@ test('Error when Response.bodyUsed', async (t) => { } }) const file = await response.text() - t.equal(expected.toString(), file) - t.equal(response.bodyUsed, true) + t.assert.strictEqual(expected.toString(), file) + t.assert.strictEqual(response.bodyUsed, true) return reply.send(response) }) const response = await fastify.inject({ method: 'GET', path: '/' }) - t.equal(response.statusCode, 500) + t.assert.strictEqual(response.statusCode, 500) const body = response.json() - t.equal(body.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') + t.assert.strictEqual(body.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') }) test('allow to pipe with fetch', async (t) => { t.plan(2) + const abortController = new AbortController() + const { signal } = abortController const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => { + fastify.close() + abortController.abort() + }) fastify.get('/', function (request, reply) { return fetch(`${fastify.listeningOrigin}/fetch`, { - method: 'GET' + method: 'GET', + signal }) }) - fastify.get('/fetch', function (request, reply) { + fastify.get('/fetch', function async (request, reply) { reply.code(200).send({ ok: true }) }) @@ -248,19 +253,25 @@ test('allow to pipe with fetch', async (t) => { const response = await fastify.inject({ method: 'GET', path: '/' }) - t.equal(response.statusCode, 200) - t.same(response.json(), { ok: true }) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { ok: true }) }) test('allow to pipe with undici.fetch', async (t) => { t.plan(2) + const abortController = new AbortController() + const { signal } = abortController const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => { + fastify.close() + abortController.abort() + }) fastify.get('/', function (request, reply) { return undiciFetch(`${fastify.listeningOrigin}/fetch`, { - method: 'GET' + method: 'GET', + signal }) }) @@ -272,6 +283,6 @@ test('allow to pipe with undici.fetch', async (t) => { const response = await fastify.inject({ method: 'GET', path: '/' }) - t.equal(response.statusCode, 200) - t.same(response.json(), { ok: true }) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { ok: true }) }) diff --git a/test/wrapThenable.test.js b/test/wrapThenable.test.js index e953d29c16c..1e543fd9519 100644 --- a/test/wrapThenable.test.js +++ b/test/wrapThenable.test.js @@ -1,17 +1,18 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const { kReplyHijacked } = require('../lib/symbols') const wrapThenable = require('../lib/wrapThenable') const Reply = require('../lib/reply') -test('should resolve immediately when reply[kReplyHijacked] is true', t => { - const reply = {} - reply[kReplyHijacked] = true - const thenable = Promise.resolve() - wrapThenable(thenable, reply) - t.end() +test('should resolve immediately when reply[kReplyHijacked] is true', async t => { + await new Promise(resolve => { + const reply = {} + reply[kReplyHijacked] = true + const thenable = Promise.resolve() + wrapThenable(thenable, reply) + resolve() + }) }) test('should reject immediately when reply[kReplyHijacked] is true', t => { @@ -20,7 +21,7 @@ test('should reject immediately when reply[kReplyHijacked] is true', t => { reply[kReplyHijacked] = true reply.log = { error: ({ err }) => { - t.equal(err.message, 'Reply sent already') + t.assert.strictEqual(err.message, 'Reply sent already') } } From 7e504e9d17ca6cdd107c672bd8b8ef38dac37084 Mon Sep 17 00:00:00 2001 From: Ran Toledo Date: Wed, 23 Oct 2024 13:24:50 +0300 Subject: [PATCH 0784/1295] changed-https-test-file (#5761) --- test/https/https.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/https/https.test.js b/test/https/https.test.js index 2c33ab55081..fe4f28ecbb3 100644 --- a/test/https/https.test.js +++ b/test/https/https.test.js @@ -20,7 +20,7 @@ test('https', async (t) => { }) t.assert.ok('Key/cert successfully loaded') } catch (e) { - t.assert.fail('Key/cert loading failed', e) + t.assert.fail('Key/cert loading failed') } fastify.get('/', function (req, reply) { @@ -87,7 +87,7 @@ test('https - headers', async (t) => { }) t.assert.ok('Key/cert successfully loaded') } catch (e) { - t.assert.fail('Key/cert loading failed', e) + t.assert.fail('Key/cert loading failed') } fastify.get('/', function (req, reply) { From c4935280bf680afa617a2b89679caf496bae775a Mon Sep 17 00:00:00 2001 From: Ran Toledo Date: Wed, 23 Oct 2024 13:59:21 +0300 Subject: [PATCH 0785/1295] test: Migrate http2 test to node:test (#5757) --- test/http2/closing.test.js | 108 ++++++++++----------- test/http2/constraint.test.js | 97 +++++++++---------- test/http2/head.test.js | 37 ++++--- test/http2/missing-http2-module.test.js | 9 +- test/http2/plain.test.js | 62 ++++++------ test/http2/secure-with-fallback.test.js | 122 ++++++++++++------------ test/http2/secure.test.js | 59 ++++++------ test/http2/unknown-http-method.test.js | 27 +++--- 8 files changed, 252 insertions(+), 269 deletions(-) diff --git a/test/http2/closing.test.js b/test/http2/closing.test.js index 6714f69e0ae..9a3193d44d7 100644 --- a/test/http2/closing.test.js +++ b/test/http2/closing.test.js @@ -1,6 +1,6 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const Fastify = require('../..') const http2 = require('node:http2') const { promisify } = require('node:util') @@ -9,102 +9,94 @@ const { once } = require('node:events') const { buildCertificate } = require('../build-certificate') const { getServerUrl } = require('../helper') -t.before(buildCertificate) +test.before(buildCertificate) -t.test('http/2 request while fastify closing', t => { +test('http/2 request while fastify closing', (t, done) => { let fastify try { fastify = Fastify({ http2: true }) - t.pass('http2 successfully loaded') + t.assert.ok('http2 successfully loaded') } catch (e) { - t.fail('http2 loading failed', e) + t.assert.fail('http2 loading failed') } fastify.get('/', () => Promise.resolve({})) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - t.test('return 200', t => { - const url = getServerUrl(fastify) - const session = http2.connect(url, function () { - this.request({ - ':method': 'GET', - ':path': '/' - }).on('response', headers => { - t.equal(headers[':status'], 503) - t.end() - this.destroy() - }).on('error', () => { - // Nothing to do here, - // we are not interested in this error that might - // happen or not - }) - fastify.close() + t.assert.ifError(err) + + const url = getServerUrl(fastify) + const session = http2.connect(url, function () { + this.request({ + ':method': 'GET', + ':path': '/' + }).on('response', headers => { + t.assert.strictEqual(headers[':status'], 503) + done() + this.destroy() + }).on('error', () => { + // Nothing to do here, + // we are not interested in this error that might + // happen or not }) session.on('error', () => { // Nothing to do here, // we are not interested in this error that might // happen or not - t.end() + done() }) + fastify.close() }) - - t.end() }) }) -t.test('http/2 request while fastify closing - return503OnClosing: false', t => { +test('http/2 request while fastify closing - return503OnClosing: false', (t, done) => { let fastify try { fastify = Fastify({ http2: true, return503OnClosing: false }) - t.pass('http2 successfully loaded') + t.assert.ok('http2 successfully loaded') } catch (e) { - t.fail('http2 loading failed', e) + t.assert.fail('http2 loading failed') } + t.after(() => { fastify.close() }) + fastify.get('/', () => Promise.resolve({})) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - t.test('return 200', t => { - const url = getServerUrl(fastify) - const session = http2.connect(url, function () { - this.request({ - ':method': 'GET', - ':path': '/' - }).on('response', headers => { - t.equal(headers[':status'], 200) - t.end() - this.destroy() - }).on('error', () => { - // Nothing to do here, - // we are not interested in this error that might - // happen or not - }) - fastify.close() - }) - session.on('error', () => { + t.assert.ifError(err) + const url = getServerUrl(fastify) + const session = http2.connect(url, function () { + this.request({ + ':method': 'GET', + ':path': '/' + }).on('response', headers => { + t.assert.strictEqual(headers[':status'], 200) + done() + this.destroy() + }).on('error', () => { // Nothing to do here, // we are not interested in this error that might // happen or not - t.end() }) + fastify.close() + }) + session.on('error', () => { + // Nothing to do here, + // we are not interested in this error that might + // happen or not + done() }) - - t.end() }) }) -t.test('http/2 closes successfully with async await', async t => { +test('http/2 closes successfully with async await', async t => { const fastify = Fastify({ http2SessionTimeout: 100, http2: true @@ -119,7 +111,7 @@ t.test('http/2 closes successfully with async await', async t => { await fastify.close() }) -t.test('https/2 closes successfully with async await', async t => { +test('https/2 closes successfully with async await', async t => { const fastify = Fastify({ http2SessionTimeout: 100, http2: true, @@ -138,7 +130,7 @@ t.test('https/2 closes successfully with async await', async t => { await fastify.close() }) -t.test('http/2 server side session emits a timeout event', async t => { +test('http/2 server side session emits a timeout event', async t => { let _resolve const p = new Promise((resolve) => { _resolve = resolve }) @@ -162,7 +154,7 @@ t.test('http/2 server side session emits a timeout event', async t => { }).end() const [headers] = await once(req, 'response') - t.equal(headers[':status'], 200) + t.assert.strictEqual(headers[':status'], 200) req.resume() // An error might or might not happen, as it's OS dependent. diff --git a/test/http2/constraint.test.js b/test/http2/constraint.test.js index aeff918eb04..06f53b7903d 100644 --- a/test/http2/constraint.test.js +++ b/test/http2/constraint.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const h2url = require('h2url') @@ -9,10 +8,10 @@ const alpha = { res: 'alpha' } const beta = { res: 'beta' } const { buildCertificate } = require('../build-certificate') -t.before(buildCertificate) +test.before(buildCertificate) -test('A route supports host constraints under http2 protocol and secure connection', (t) => { - t.plan(6) +test('A route supports host constraints under http2 protocol and secure connection', async (t) => { + t.plan(5) let fastify try { @@ -23,9 +22,9 @@ test('A route supports host constraints under http2 protocol and secure connecti cert: global.context.cert } }) - t.pass('Key/cert successfully loaded') + t.assert.ok(true, 'Key/cert successfully loaded') } catch (e) { - t.fail('Key/cert loading failed', e) + t.assert.fail('Key/cert loading failed') } const constrain = 'fastify.dev' @@ -53,60 +52,58 @@ test('A route supports host constraints under http2 protocol and secure connecti reply.code(200).send({ ...beta, hostname: req.hostname }) } }) + t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + await fastify.listen({ port: 0 }) - t.test('https get request - no constrain', async (t) => { - t.plan(3) + await t.test('https get request - no constrain', async (t) => { + t.plan(3) - const url = `https://localhost:${fastify.server.address().port}` - const res = await h2url.concat({ url }) + const url = `https://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ url }) - t.equal(res.headers[':status'], 200) - t.equal(res.headers['content-length'], '' + JSON.stringify(alpha).length) - t.same(JSON.parse(res.body), alpha) - }) - - t.test('https get request - constrain', async (t) => { - t.plan(3) + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(alpha).length) + t.assert.deepStrictEqual(JSON.parse(res.body), alpha) + }) - const url = `https://localhost:${fastify.server.address().port}/beta` - const res = await h2url.concat({ - url, - headers: { - ':authority': constrain - } - }) + await t.test('https get request - constrain', async (t) => { + t.plan(3) - t.equal(res.headers[':status'], 200) - t.equal(res.headers['content-length'], '' + JSON.stringify(beta).length) - t.same(JSON.parse(res.body), beta) + const url = `https://localhost:${fastify.server.address().port}/beta` + const res = await h2url.concat({ + url, + headers: { + ':authority': constrain + } }) - t.test('https get request - constrain - not found', async (t) => { - t.plan(1) + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(beta).length) + t.assert.deepStrictEqual(JSON.parse(res.body), beta) + }) - const url = `https://localhost:${fastify.server.address().port}/beta` - const res = await h2url.concat({ - url - }) + await t.test('https get request - constrain - not found', async (t) => { + t.plan(1) - t.equal(res.headers[':status'], 404) + const url = `https://localhost:${fastify.server.address().port}/beta` + const res = await h2url.concat({ + url }) - t.test('https get request - constrain - verify hostname and port from request', async (t) => { - t.plan(1) - - const url = `https://localhost:${fastify.server.address().port}/hostname_port` - const res = await h2url.concat({ - url, - headers: { - ':authority': constrain - } - }) - const body = JSON.parse(res.body) - t.equal(body.hostname, constrain) + + t.assert.strictEqual(res.headers[':status'], 404) + }) + await t.test('https get request - constrain - verify hostname and port from request', async (t) => { + t.plan(1) + + const url = `https://localhost:${fastify.server.address().port}/hostname_port` + const res = await h2url.concat({ + url, + headers: { + ':authority': constrain + } }) + const body = JSON.parse(res.body) + t.assert.strictEqual(body.hostname, constrain) }) }) diff --git a/test/http2/head.test.js b/test/http2/head.test.js index 091d5888c45..bf5f254cc58 100644 --- a/test/http2/head.test.js +++ b/test/http2/head.test.js @@ -1,35 +1,34 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const h2url = require('h2url') const msg = { hello: 'world' } -let fastify -try { - fastify = Fastify({ - http2: true - }) - t.pass('http2 successfully loaded') -} catch (e) { - t.fail('http2 loading failed', e) -} +test('http2 HEAD test', async (t) => { + let fastify + try { + fastify = Fastify({ + http2: true + }) + t.assert.ok(true, 'http2 successfully loaded') + } catch (e) { + t.assert.fail('http2 loading failed') + } -fastify.all('/', function (req, reply) { - reply.code(200).send(msg) -}) + fastify.all('/', function (req, reply) { + reply.code(200).send(msg) + }) + t.after(() => { fastify.close() }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + await fastify.listen({ port: 0 }) - test('http HEAD request', async (t) => { + await t.test('http HEAD request', async (t) => { t.plan(1) const url = `http://localhost:${fastify.server.address().port}` const res = await h2url.concat({ url, method: 'HEAD' }) - t.equal(res.headers[':status'], 200) + t.assert.strictEqual(res.headers[':status'], 200) }) }) diff --git a/test/http2/missing-http2-module.test.js b/test/http2/missing-http2-module.test.js index c2e3564418c..27df5285bea 100644 --- a/test/http2/missing-http2-module.test.js +++ b/test/http2/missing-http2-module.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const proxyquire = require('proxyquire') const server = proxyquire('../../lib/server', { 'node:http2': null }) const Fastify = proxyquire('../..', { './lib/server.js': server }) @@ -10,9 +9,9 @@ test('should throw when http2 module cannot be found', t => { t.plan(2) try { Fastify({ http2: true }) - t.fail('fastify did not throw expected error') + t.assert.fail('fastify did not throw expected error') } catch (err) { - t.equal(err.code, 'FST_ERR_HTTP2_INVALID_VERSION') - t.equal(err.message, 'HTTP2 is available only from node >= 8.8.1') + t.assert.strictEqual(err.code, 'FST_ERR_HTTP2_INVALID_VERSION') + t.assert.strictEqual(err.message, 'HTTP2 is available only from node >= 8.8.1') } }) diff --git a/test/http2/plain.test.js b/test/http2/plain.test.js index 98691334485..3e1d5b4c96e 100644 --- a/test/http2/plain.test.js +++ b/test/http2/plain.test.js @@ -1,50 +1,50 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const h2url = require('h2url') const msg = { hello: 'world' } -let fastify -try { - fastify = Fastify({ - http2: true +test('http2 plain test', async t => { + let fastify + try { + fastify = Fastify({ + http2: true + }) + t.assert.ok(true, 'http2 successfully loaded') + } catch (e) { + t.assert.fail('http2 loading failed') + } + + fastify.get('/', function (req, reply) { + reply.code(200).send(msg) }) - t.pass('http2 successfully loaded') -} catch (e) { - t.fail('http2 loading failed', e) -} -fastify.get('/', function (req, reply) { - reply.code(200).send(msg) -}) + fastify.get('/host', function (req, reply) { + reply.code(200).send(req.host) + }) -fastify.get('/host', function (req, reply) { - reply.code(200).send(req.host) -}) + fastify.get('/hostname_port', function (req, reply) { + reply.code(200).send({ hostname: req.hostname, port: req.port }) + }) -fastify.get('/hostname_port', function (req, reply) { - reply.code(200).send({ hostname: req.hostname, port: req.port }) -}) + t.after(() => { fastify.close() }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + await fastify.listen({ port: 0 }) - test('http get request', async (t) => { + await t.test('http get request', async (t) => { t.plan(3) const url = `http://localhost:${fastify.server.address().port}` const res = await h2url.concat({ url }) - t.equal(res.headers[':status'], 200) - t.equal(res.headers['content-length'], '' + JSON.stringify(msg).length) + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(msg).length) - t.same(JSON.parse(res.body), msg) + t.assert.deepStrictEqual(JSON.parse(res.body), msg) }) - test('http host', async (t) => { + await t.test('http host', async (t) => { t.plan(1) const host = `localhost:${fastify.server.address().port}` @@ -52,9 +52,9 @@ fastify.listen({ port: 0 }, err => { const url = `http://${host}/host` const res = await h2url.concat({ url }) - t.equal(res.body, host) + t.assert.strictEqual(res.body, host) }) - test('http hostname and port', async (t) => { + await t.test('http hostname and port', async (t) => { t.plan(2) const host = `localhost:${fastify.server.address().port}` @@ -62,7 +62,7 @@ fastify.listen({ port: 0 }, err => { const url = `http://${host}/hostname_port` const res = await h2url.concat({ url }) - t.equal(JSON.parse(res.body).hostname, host.split(':')[0]) - t.equal(JSON.parse(res.body).port, parseInt(host.split(':')[1])) + t.assert.strictEqual(JSON.parse(res.body).hostname, host.split(':')[0]) + t.assert.strictEqual(JSON.parse(res.body).port, parseInt(host.split(':')[1])) }) }) diff --git a/test/http2/secure-with-fallback.test.js b/test/http2/secure-with-fallback.test.js index 12f57fc6989..d1e8511f05b 100644 --- a/test/http2/secure-with-fallback.test.js +++ b/test/http2/secure-with-fallback.test.js @@ -1,17 +1,16 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const h2url = require('h2url') const sget = require('simple-get').concat const msg = { hello: 'world' } const { buildCertificate } = require('../build-certificate') -t.before(buildCertificate) +test.before(buildCertificate) -test('secure with fallback', (t) => { - t.plan(7) +test('secure with fallback', async (t) => { + t.plan(6) let fastify try { @@ -23,9 +22,9 @@ test('secure with fallback', (t) => { cert: global.context.cert } }) - t.pass('Key/cert successfully loaded') + t.assert.ok(true, 'Key/cert successfully loaded') } catch (e) { - t.fail('Key/cert loading failed', e) + t.assert.fail('Key/cert loading failed') } fastify.get('/', function (req, reply) { @@ -40,71 +39,72 @@ test('secure with fallback', (t) => { throw new Error('kaboom') }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) - t.test('https get error', async (t) => { - t.plan(1) + await fastify.listen({ port: 0 }) - const url = `https://localhost:${fastify.server.address().port}/error` - const res = await h2url.concat({ url }) + await t.test('https get error', async (t) => { + t.plan(1) - t.equal(res.headers[':status'], 500) - }) + const url = `https://localhost:${fastify.server.address().port}/error` + const res = await h2url.concat({ url }) + + t.assert.strictEqual(res.headers[':status'], 500) + }) - t.test('https post', async (t) => { - t.plan(2) - - const url = `https://localhost:${fastify.server.address().port}` - const res = await h2url.concat({ - url, - method: 'POST', - body: JSON.stringify({ hello: 'http2' }), - headers: { - 'content-type': 'application/json' - } - }) - - t.equal(res.headers[':status'], 200) - t.same(JSON.parse(res.body), { hello: 'http2' }) + await t.test('https post', async (t) => { + t.plan(2) + + const url = `https://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ + url, + method: 'POST', + body: JSON.stringify({ hello: 'http2' }), + headers: { + 'content-type': 'application/json' + } }) - t.test('https get request', async (t) => { - t.plan(3) + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.deepStrictEqual(JSON.parse(res.body), { hello: 'http2' }) + }) - const url = `https://localhost:${fastify.server.address().port}` - const res = await h2url.concat({ url }) + await t.test('https get request', async (t) => { + t.plan(3) - t.equal(res.headers[':status'], 200) - t.equal(res.headers['content-length'], '' + JSON.stringify(msg).length) - t.same(JSON.parse(res.body), msg) - }) + const url = `https://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ url }) - t.test('http1 get request', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - }) + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(msg).length) + t.assert.deepStrictEqual(JSON.parse(res.body), msg) + }) + + await t.test('http1 get request', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: 'https://localhost:' + fastify.server.address().port, + rejectUnauthorized: false + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) + }) - t.test('http1 get error', (t) => { - t.plan(2) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port + '/error', - rejectUnauthorized: false - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) - }) + await t.test('http1 get error', (t, done) => { + t.plan(2) + sget({ + method: 'GET', + url: 'https://localhost:' + fastify.server.address().port + '/error', + rejectUnauthorized: false + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + done() }) }) }) diff --git a/test/http2/secure.test.js b/test/http2/secure.test.js index 86f4f1d3418..2a12ce8d0ea 100644 --- a/test/http2/secure.test.js +++ b/test/http2/secure.test.js @@ -1,16 +1,15 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const h2url = require('h2url') const msg = { hello: 'world' } const { buildCertificate } = require('../build-certificate') -t.before(buildCertificate) +test.before(buildCertificate) -test('secure', (t) => { - t.plan(5) +test('secure', async (t) => { + t.plan(4) let fastify try { @@ -21,9 +20,9 @@ test('secure', (t) => { cert: global.context.cert } }) - t.pass('Key/cert successfully loaded') + t.assert.ok(true, 'Key/cert successfully loaded') } catch (e) { - t.fail('Key/cert loading failed', e) + t.assert.fail('Key/cert loading failed') } fastify.get('/', function (req, reply) { @@ -36,35 +35,33 @@ test('secure', (t) => { reply.code(200).send({ hostname: req.hostname, port: req.port }) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) + await fastify.listen({ port: 0 }) - t.test('https get request', async (t) => { - t.plan(3) + await t.test('https get request', async (t) => { + t.plan(3) - const url = `https://localhost:${fastify.server.address().port}` - const res = await h2url.concat({ url }) + const url = `https://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ url }) - t.equal(res.headers[':status'], 200) - t.equal(res.headers['content-length'], '' + JSON.stringify(msg).length) - t.same(JSON.parse(res.body), msg) - }) + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(msg).length) + t.assert.deepStrictEqual(JSON.parse(res.body), msg) + }) - t.test('https get request without trust proxy - protocol', async (t) => { - t.plan(2) + await t.test('https get request without trust proxy - protocol', async (t) => { + t.plan(2) - const url = `https://localhost:${fastify.server.address().port}/proto` - t.same(JSON.parse((await h2url.concat({ url })).body), { proto: 'https' }) - t.same(JSON.parse((await h2url.concat({ url, headers: { 'X-Forwarded-Proto': 'lorem' } })).body), { proto: 'https' }) - }) - t.test('https get request - test hostname and port', async (t) => { - t.plan(2) + const url = `https://localhost:${fastify.server.address().port}/proto` + t.assert.deepStrictEqual(JSON.parse((await h2url.concat({ url })).body), { proto: 'https' }) + t.assert.deepStrictEqual(JSON.parse((await h2url.concat({ url, headers: { 'X-Forwarded-Proto': 'lorem' } })).body), { proto: 'https' }) + }) + await t.test('https get request - test hostname and port', async (t) => { + t.plan(2) - const url = `https://localhost:${fastify.server.address().port}/hostname_port` - const parsedbody = JSON.parse((await h2url.concat({ url })).body) - t.equal(parsedbody.hostname, 'localhost') - t.equal(parsedbody.port, fastify.server.address().port) - }) + const url = `https://localhost:${fastify.server.address().port}/hostname_port` + const parsedbody = JSON.parse((await h2url.concat({ url })).body) + t.assert.strictEqual(parsedbody.hostname, 'localhost') + t.assert.strictEqual(parsedbody.port, fastify.server.address().port) }) }) diff --git a/test/http2/unknown-http-method.test.js b/test/http2/unknown-http-method.test.js index 9d821c00b7d..56941a2d497 100644 --- a/test/http2/unknown-http-method.test.js +++ b/test/http2/unknown-http-method.test.js @@ -1,31 +1,30 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../..') const h2url = require('h2url') const msg = { hello: 'world' } -const fastify = Fastify({ - http2: true -}) +test('http2 unknown http method', async t => { + const fastify = Fastify({ + http2: true + }) -fastify.get('/', function (req, reply) { - reply.code(200).send(msg) -}) + fastify.get('/', function (req, reply) { + reply.code(200).send(msg) + }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) + await fastify.listen({ port: 0 }) - test('http UNKNOWN_METHOD request', async (t) => { + await t.test('http UNKNOWN_METHOD request', async (t) => { t.plan(2) const url = `http://localhost:${fastify.server.address().port}` const res = await h2url.concat({ url, method: 'UNKNOWN_METHOD' }) - t.equal(res.headers[':status'], 404) - t.same(JSON.parse(res.body), { + t.assert.strictEqual(res.headers[':status'], 404) + t.assert.deepStrictEqual(JSON.parse(res.body), { statusCode: 404, code: 'FST_ERR_NOT_FOUND', error: 'Not Found', From 4ae3f6722f1938ae9dbbdb6956e88dfa64d65791 Mon Sep 17 00:00:00 2001 From: Sam Martin Date: Thu, 24 Oct 2024 15:35:16 +0200 Subject: [PATCH 0786/1295] types: add string[] to routeOptions.method (#5762) --- test/types/route.test-d.ts | 8 ++++++++ types/request.d.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 8dcf7ea11a3..ab8ecf08829 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -506,3 +506,11 @@ expectType(fastify().route({ method: ['put', 'patch'], handler: routeHandlerWithReturnValue })) + +expectType(fastify().route({ + url: '/', + method: ['HEAD', 'GET'], + handler: (req) => { + expectType(req.routeOptions.method) + } +})) diff --git a/types/request.d.ts b/types/request.d.ts index 63e3122a440..cab01ee3873 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -21,7 +21,7 @@ export interface ValidationFunction { } export interface RequestRouteOptions { - method: string; + method: string | string[]; // `url` can be `undefined` for instance when `request.is404` is true url: string | undefined; bodyLimit: number; From e17142fc6cf162f448ba28de14c7e5a353e460a8 Mon Sep 17 00:00:00 2001 From: Sam Martin Date: Thu, 24 Oct 2024 16:23:40 +0200 Subject: [PATCH 0787/1295] Fix flaky server tests in versioned-routes.test.js (#5765) --- test/versioned-routes.test.js | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js index c286f192615..e843770f4be 100644 --- a/test/versioned-routes.test.js +++ b/test/versioned-routes.test.js @@ -15,7 +15,7 @@ before(async function () { [localhost] = await helper.getLoopbackHost() }) -test('Should register a versioned route', (t, done) => { +test('Should register a versioned route (inject)', (t, done) => { t.plan(11) const fastify = Fastify() @@ -233,7 +233,7 @@ test('Versioned route but not version header should return a 404', (t, done) => }) }) -test('Should register a versioned route', (t, done) => { +test('Should register a versioned route (server)', (t, done) => { t.plan(6) const fastify = Fastify() @@ -260,18 +260,18 @@ test('Should register a versioned route', (t, done) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 200) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'Accept-Version': '2.x' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port, + headers: { + 'Accept-Version': '2.x' + } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() + }) }) }) }) @@ -428,18 +428,18 @@ test('Bad accept version (server)', (t, done) => { }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) - }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'Accept-Version': 12 - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port, + headers: { + 'Accept-Version': 12 + } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() + }) }) }) }) From d6f0b0be3d69c72782aac7c10c7d66bd9e077ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Thu, 24 Oct 2024 20:09:10 +0200 Subject: [PATCH 0788/1295] fix: correctly handle empty host (#5764) * fix: correctly handle empty host * update test description * fix: proper handle host headers # Conflicts: # lib/request.js * fixup * fixup * more tests * fixup --------- Co-authored-by: KaKa --- docs/Reference/Request.md | 3 +- lib/request.js | 18 ++- test/internals/request.test.js | 136 +++++++++++++++++++-- test/request-header-host.test.js | 197 +++++++++++++++++++++++++++++++ 4 files changed, 341 insertions(+), 13 deletions(-) create mode 100644 test/request-header-host.test.js diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 5e3a8a36e87..2acd11e92fb 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -23,7 +23,8 @@ Request is a core Fastify object containing the following fields: - `host` - the host of the incoming request (derived from `X-Forwarded-Host` header when the [`trustProxy`](./Server.md#factory-trust-proxy) option is enabled). For HTTP/2 compatibility it returns `:authority` if no host header - exists. + exists. When you use `requireHostHeader = false` in the server options, it + will fallback as empty when the host header is missing. - `hostname` - the host of the incoming request without the port - `port` - the port that the server is listening on - `protocol` - the protocol of the incoming request (`https` or `http`) diff --git a/lib/request.js b/lib/request.js index 75b88c5d958..87669a9a025 100644 --- a/lib/request.js +++ b/lib/request.js @@ -117,7 +117,10 @@ function buildRequestWithTrustProxy (R, trustProxy) { if (this.ip !== undefined && this.headers['x-forwarded-host']) { return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host']) } - return this.headers.host || this.headers[':authority'] + let host = this.headers.host ?? this.headers[':authority'] + // support http.requireHostHeader === false + if (this.server.server.requireHostHeader === false) host ??= '' + return host } }, protocol: { @@ -209,23 +212,28 @@ Object.defineProperties(Request.prototype, { }, host: { get () { - return this.raw.headers.host || this.raw.headers[':authority'] + let host = this.raw.headers.host ?? this.raw.headers[':authority'] + // support http.requireHostHeader === false + if (this.server.server.requireHostHeader === false) host ??= '' + return host } }, hostname: { get () { - return (this.host).split(':')[0] + return this.host.split(':')[0] } }, port: { get () { // first try taking port from host - const portFromHost = parseInt((this.host).split(':').slice(-1)[0]) + const portFromHost = parseInt(this.host.split(':').slice(-1)[0]) if (!isNaN(portFromHost)) { return portFromHost } // now fall back to port from host/:authority header - const portFromHeader = parseInt((this.headers.host || this.headers[':authority']).split(':').slice(-1)[0]) + let host = (this.headers.host ?? this.headers[':authority']) + if (this.server.server.requireHostHeader === false) host ??= '' + const portFromHeader = parseInt(host.split(':').slice(-1)[0]) if (!isNaN(portFromHeader)) { return portFromHeader } diff --git a/test/internals/request.test.js b/test/internals/request.test.js index aa6c55325fc..51d9e23f2bd 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -42,7 +42,8 @@ test('Regular request', t => { [kRequest]: Request, [kOptions]: { requestIdLogLabel: 'reqId' - } + }, + server: {} } }) req.connection = req.socket @@ -95,7 +96,8 @@ test('Request with undefined config', t => { [kRequest]: Request, [kOptions]: { requestIdLogLabel: 'reqId' - } + }, + server: {} } }) req.connection = req.socket @@ -135,8 +137,32 @@ test('Regular request - hostname from authority', t => { socket: { remoteAddress: 'ip' }, headers } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) - const request = new Request('id', 'params', req, 'query', 'log') + const request = new Request('id', 'params', req, 'query', 'log', context) t.assert.ok(request instanceof Request) t.assert.strictEqual(request.host, 'authority') t.assert.strictEqual(request.port, null) @@ -154,7 +180,31 @@ test('Regular request - host header has precedence over authority', t => { socket: { remoteAddress: 'ip' }, headers } - const request = new Request('id', 'params', req, 'query', 'log') + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + const request = new Request('id', 'params', req, 'query', 'log', context) t.assert.ok(request instanceof Request) t.assert.strictEqual(request.host, 'hostname') t.assert.strictEqual(request.port, null) @@ -249,9 +299,33 @@ test('Request with trust proxy - no x-forwarded-host header', t => { socket: { remoteAddress: 'ip' }, headers } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) const TpRequest = Request.buildRequest(Request, true) - const request = new TpRequest('id', 'params', req, 'query', 'log') + const request = new TpRequest('id', 'params', req, 'query', 'log', context) t.assert.ok(request instanceof TpRequest) t.assert.strictEqual(request.host, 'hostname') }) @@ -268,9 +342,33 @@ test('Request with trust proxy - no x-forwarded-host header and fallback to auth socket: { remoteAddress: 'ip' }, headers } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) const TpRequest = Request.buildRequest(Request, true) - const request = new TpRequest('id', 'params', req, 'query', 'log') + const request = new TpRequest('id', 'params', req, 'query', 'log', context) t.assert.ok(request instanceof TpRequest) t.assert.strictEqual(request.host, 'authority') }) @@ -344,7 +442,31 @@ test('Request with undefined socket', t => { socket: undefined, headers } - const request = new Request('id', 'params', req, 'query', 'log') + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + const request = new Request('id', 'params', req, 'query', 'log', context) t.assert.ok(request instanceof Request) t.assert.strictEqual(request.id, 'id') t.assert.strictEqual(request.params, 'params') diff --git a/test/request-header-host.test.js b/test/request-header-host.test.js new file mode 100644 index 00000000000..33727b1072b --- /dev/null +++ b/test/request-header-host.test.js @@ -0,0 +1,197 @@ +'use strict' + +const { test } = require('node:test') +const { connect } = require('node:net') +const Fastify = require('..') + +// RFC9112 +// https://www.rfc-editor.org/rfc/rfc9112 +test('Return 400 when Host header is missing', (t, done) => { + t.plan(2) + let data = Buffer.alloc(0) + const fastify = Fastify() + + t.after(() => fastify.close()) + + fastify.get('/', async function () { + t.assert.fail('should not reach handler') + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 400 Bad Request/ + ) + done() + }) + }) +}) + +test('Return 400 when Host header is missing with trust proxy', (t, done) => { + t.plan(2) + let data = Buffer.alloc(0) + const fastify = Fastify({ + trustProxy: true + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function () { + t.assert.fail('should not reach handler') + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 400 Bad Request/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is empty', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.equal(request.host, '') + t.assert.equal(request.hostname, '') + t.assert.equal(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\nHost:\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is empty with trust proxy', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + trustProxy: true, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.equal(request.host, '') + t.assert.equal(request.hostname, '') + t.assert.equal(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\nHost:\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +// Node.js allows exploiting RFC9112 +// https://nodejs.org/docs/latest-v22.x/api/http.html#httpcreateserveroptions-requestlistener +test('Return 200 when Host header is missing and http.requireHostHeader = false', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + http: { + requireHostHeader: false + }, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.equal(request.host, '') + t.assert.equal(request.hostname, '') + t.assert.equal(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is missing and http.requireHostHeader = false with trust proxy', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + http: { + requireHostHeader: false + }, + trustProxy: true, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.equal(request.host, '') + t.assert.equal(request.hostname, '') + t.assert.equal(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) From 0b44565e2269cb7939e5b8bebf2b123077e9ec1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Thu, 24 Oct 2024 23:10:08 +0200 Subject: [PATCH 0789/1295] add split second arg (#5767) --- lib/request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index 87669a9a025..289f736afe5 100644 --- a/lib/request.js +++ b/lib/request.js @@ -220,7 +220,7 @@ Object.defineProperties(Request.prototype, { }, hostname: { get () { - return this.host.split(':')[0] + return this.host.split(':', 1)[0] } }, port: { From 37fc3b050e07d19aefdc338356e6713c56ea7cad Mon Sep 17 00:00:00 2001 From: Ran Toledo Date: Fri, 25 Oct 2024 16:38:25 +0300 Subject: [PATCH 0790/1295] migrated-404-test-file-to-node-test (#5769) --- test/404s.test.js | 1304 +++++++++++++++++++++++---------------------- 1 file changed, 675 insertions(+), 629 deletions(-) diff --git a/test/404s.test.js b/test/404s.test.js index 8ec7789ace7..b9bc182af33 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const fp = require('fastify-plugin') const sget = require('simple-get').concat const errors = require('http-errors') @@ -9,84 +8,83 @@ const split = require('split2') const Fastify = require('..') const { getServerUrl } = require('./helper') -test('default 404', t => { - t.plan(5) +test('default 404', async t => { + t.plan(4) - const test = t.test const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.error(err) - - test('unsupported method', t => { - t.plan(3) - sget({ - method: 'PUT', - url: getServerUrl(fastify), - body: {}, - json: true - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - }) + await fastify.listen({ port: 0 }) + + await t.test('unsupported method', (t, done) => { + t.plan(3) + sget({ + method: 'PUT', + url: getServerUrl(fastify), + body: {}, + json: true + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + done() }) + }) - // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion - test('framework-unsupported method', t => { - t.plan(3) - sget({ - method: 'PROPFIND', - url: getServerUrl(fastify), - body: {}, - json: true - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - }) + // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion + await t.test('framework-unsupported method', (t, done) => { + t.plan(3) + sget({ + method: 'PROPFIND', + url: getServerUrl(fastify), + body: {}, + json: true + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + done() }) + }) - test('unsupported route', t => { - t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported', - body: {}, - json: true - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - }) + await t.test('unsupported route', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/notSupported', + body: {}, + json: true + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + done() }) + }) - test('using post method and multipart/formdata', async t => { - t.plan(3) - const form = new FormData() - form.set('test-field', 'just some field') + await t.test('using post method and multipart/formdata', async t => { + t.plan(3) + const form = new FormData() + form.set('test-field', 'just some field') - const response = await fetch(getServerUrl(fastify) + '/notSupported', { - method: 'POST', - body: form - }) - t.equal(response.status, 404) - t.equal(response.statusText, 'Not Found') - t.equal(response.headers.get('content-type'), 'application/json; charset=utf-8') + const response = await fetch(getServerUrl(fastify) + '/notSupported', { + method: 'POST', + body: form }) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(response.statusText, 'Not Found') + t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8') }) }) -test('customized 404', t => { - t.plan(6) +test('customized 404', async t => { + t.plan(5) - const test = t.test const fastify = Fastify() fastify.get('/', function (req, reply) { @@ -107,120 +105,121 @@ test('customized 404', t => { reply.code(404).send('this was not found') }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.error(err) - - test('unsupported method', t => { - t.plan(3) - sget({ - method: 'PUT', - url: getServerUrl(fastify), - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found') - }) + await fastify.listen({ port: 0 }) + + await t.test('unsupported method', (t, done) => { + t.plan(3) + sget({ + method: 'PUT', + url: getServerUrl(fastify), + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found') + done() }) + }) - test('framework-unsupported method', t => { - t.plan(3) - sget({ - method: 'PROPFIND', - url: getServerUrl(fastify), - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found') - }) + await t.test('framework-unsupported method', (t, done) => { + t.plan(3) + sget({ + method: 'PROPFIND', + url: getServerUrl(fastify), + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found') + done() }) + }) - test('unsupported route', t => { - t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found') - }) + await t.test('unsupported route', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/notSupported' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found') + done() }) + }) - test('with error object', t => { - t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/with-error' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.same(JSON.parse(body), { - error: 'Not Found', - message: 'Not Found', - statusCode: 404 - }) + await t.test('with error object', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/with-error' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Not Found', + message: 'Not Found', + statusCode: 404 }) + done() }) + }) - test('error object with headers property', t => { - t.plan(4) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/with-error-custom-header' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.headers['x-foo'], 'bar') - t.same(JSON.parse(body), { - error: 'Not Found', - message: 'Not Found', - statusCode: 404 - }) + await t.test('error object with headers property', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/with-error-custom-header' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.headers['x-foo'], 'bar') + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Not Found', + message: 'Not Found', + statusCode: 404 }) + done() }) }) }) -test('custom header in notFound handler', t => { - t.plan(2) +test('custom header in notFound handler', async t => { + t.plan(1) - const test = t.test const fastify = Fastify() fastify.setNotFoundHandler(function (req, reply) { reply.code(404).header('x-foo', 'bar').send('this was not found') }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.error(err) - - test('not found with custom header', t => { - t.plan(4) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.headers['x-foo'], 'bar') - t.equal(body.toString(), 'this was not found') - }) + await fastify.listen({ port: 0 }) + + await t.test('not found with custom header', (t, done) => { + t.plan(4) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/notSupported' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.headers['x-foo'], 'bar') + t.assert.strictEqual(body.toString(), 'this was not found') + done() }) }) }) -test('setting a custom 404 handler multiple times is an error', t => { +test('setting a custom 404 handler multiple times is an error', async t => { t.plan(5) - t.test('at the root level', t => { + await t.test('at the root level', t => { t.plan(2) const fastify = Fastify() @@ -229,14 +228,14 @@ test('setting a custom 404 handler multiple times is an error', t => { try { fastify.setNotFoundHandler(() => {}) - t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') + t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { - t.type(err, Error) - t.equal(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'') + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'') } }) - t.test('at the plugin level', t => { + await t.test('at the plugin level', (t, done) => { t.plan(3) const fastify = Fastify() @@ -246,22 +245,23 @@ test('setting a custom 404 handler multiple times is an error', t => { try { instance.setNotFoundHandler(() => {}) - t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') + t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { - t.type(err, Error) - t.equal(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } done() }, { prefix: '/prefix' }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close() + done() }) }) - t.test('at multiple levels', t => { + await t.test('at multiple levels', (t, done) => { t.plan(3) const fastify = Fastify() @@ -269,10 +269,10 @@ test('setting a custom 404 handler multiple times is an error', t => { fastify.register((instance, options, done) => { try { instance.setNotFoundHandler(() => {}) - t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') + t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { - t.type(err, Error) - t.equal(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'') + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'') } done() }) @@ -280,12 +280,13 @@ test('setting a custom 404 handler multiple times is an error', t => { fastify.setNotFoundHandler(() => {}) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close() + done() }) }) - t.test('at multiple levels / 2', t => { + await t.test('at multiple levels / 2', (t, done) => { t.plan(3) const fastify = Fastify() @@ -296,10 +297,10 @@ test('setting a custom 404 handler multiple times is an error', t => { instance.register((instance2, options, done) => { try { instance2.setNotFoundHandler(() => {}) - t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') + t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { - t.type(err, Error) - t.equal(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } done() }) @@ -310,12 +311,13 @@ test('setting a custom 404 handler multiple times is an error', t => { fastify.setNotFoundHandler(() => {}) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close() + done() }) }) - t.test('in separate plugins at the same level', t => { + await t.test('in separate plugins at the same level', (t, done) => { t.plan(3) const fastify = Fastify() @@ -329,10 +331,10 @@ test('setting a custom 404 handler multiple times is an error', t => { instance.register((instance2B, options, done) => { try { instance2B.setNotFoundHandler(() => {}) - t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') + t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { - t.type(err, Error) - t.equal(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } done() }) @@ -343,16 +345,16 @@ test('setting a custom 404 handler multiple times is an error', t => { fastify.setNotFoundHandler(() => {}) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close() + done() }) }) }) -test('encapsulated 404', t => { - t.plan(13) +test('encapsulated 404', async t => { + t.plan(12) - const test = t.test const fastify = Fastify() fastify.get('/', function (req, reply) { @@ -384,199 +386,209 @@ test('encapsulated 404', t => { done() }, { prefix: '/test3/' }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.error(err) - - test('root unsupported method', t => { - t.plan(3) - sget({ - method: 'PUT', - url: getServerUrl(fastify), - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found') - }) + await fastify.listen({ port: 0 }) + + await t.test('root unsupported method', (t, done) => { + t.plan(3) + sget({ + method: 'PUT', + url: getServerUrl(fastify), + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found') + done() }) + }) - test('root framework-unsupported method', t => { - t.plan(3) - sget({ - method: 'PROPFIND', - url: getServerUrl(fastify), - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found') - }) + await t.test('root framework-unsupported method', (t, done) => { + t.plan(3) + sget({ + method: 'PROPFIND', + url: getServerUrl(fastify), + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found') + done() }) + }) - test('root unsupported route', t => { - t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found') - }) + await t.test('root unsupported route', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/notSupported' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found') + done() }) + }) - test('unsupported method', t => { - t.plan(3) - sget({ - method: 'PUT', - url: getServerUrl(fastify) + '/test', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 2') - }) + await t.test('unsupported method', (t, done) => { + t.plan(3) + sget({ + method: 'PUT', + url: getServerUrl(fastify) + '/test', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 2') + done() }) + }) - test('framework-unsupported method', t => { - t.plan(3) - sget({ - method: 'PROPFIND', - url: getServerUrl(fastify) + '/test', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 2') - }) + await t.test('framework-unsupported method', (t, done) => { + t.plan(3) + sget({ + method: 'PROPFIND', + url: getServerUrl(fastify) + '/test', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 2') + done() }) + }) - test('unsupported route', t => { - t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/test/notSupported' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 2') - }) + await t.test('unsupported route', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/test/notSupported' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 2') + done() }) + }) - test('unsupported method 2', t => { - t.plan(3) - sget({ - method: 'PUT', - url: getServerUrl(fastify) + '/test2', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 3') - }) + await t.test('unsupported method 2', (t, done) => { + t.plan(3) + sget({ + method: 'PUT', + url: getServerUrl(fastify) + '/test2', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 3') + done() }) + }) - test('framework-unsupported method 2', t => { - t.plan(3) - sget({ - method: 'PROPFIND', - url: getServerUrl(fastify) + '/test2', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 3') - }) + await t.test('framework-unsupported method 2', (t, done) => { + t.plan(3) + sget({ + method: 'PROPFIND', + url: getServerUrl(fastify) + '/test2', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 3') + done() }) + }) - test('unsupported route 2', t => { - t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/test2/notSupported' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 3') - }) + await t.test('unsupported route 2', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/test2/notSupported' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 3') + done() }) + }) - test('unsupported method 3', t => { - t.plan(3) - sget({ - method: 'PUT', - url: getServerUrl(fastify) + '/test3/', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 4') - }) + await t.test('unsupported method 3', (t, done) => { + t.plan(3) + sget({ + method: 'PUT', + url: getServerUrl(fastify) + '/test3/', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 4') + done() }) + }) - test('framework-unsupported method 3', t => { - t.plan(3) - sget({ - method: 'PROPFIND', - url: getServerUrl(fastify) + '/test3/', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 4') - }) + await t.test('framework-unsupported method 3', (t, done) => { + t.plan(3) + sget({ + method: 'PROPFIND', + url: getServerUrl(fastify) + '/test3/', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 4') + done() }) + }) - test('unsupported route 3', t => { - t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/test3/notSupported' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(body.toString(), 'this was not found 4') - }) + await t.test('unsupported route 3', (t, done) => { + t.plan(3) + sget({ + method: 'GET', + url: getServerUrl(fastify) + '/test3/notSupported' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(body.toString(), 'this was not found 4') + done() }) }) }) -test('custom 404 hook and handler context', t => { - t.plan(21) +test('custom 404 hook and handler context', async t => { + t.plan(19) const fastify = Fastify() fastify.decorate('foo', 42) fastify.addHook('onRequest', function (req, res, done) { - t.equal(this.foo, 42) + t.assert.strictEqual(this.foo, 42) done() }) fastify.addHook('preHandler', function (request, reply, done) { - t.equal(this.foo, 42) + t.assert.strictEqual(this.foo, 42) done() }) fastify.addHook('onSend', function (request, reply, payload, done) { - t.equal(this.foo, 42) + t.assert.strictEqual(this.foo, 42) done() }) fastify.addHook('onResponse', function (request, reply, done) { - t.equal(this.foo, 42) + t.assert.strictEqual(this.foo, 42) done() }) fastify.setNotFoundHandler(function (req, reply) { - t.equal(this.foo, 42) + t.assert.strictEqual(this.foo, 42) reply.code(404).send('this was not found') }) @@ -584,45 +596,45 @@ test('custom 404 hook and handler context', t => { instance.decorate('bar', 84) instance.addHook('onRequest', function (req, res, done) { - t.equal(this.bar, 84) + t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('preHandler', function (request, reply, done) { - t.equal(this.bar, 84) + t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(this.bar, 84) + t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('onResponse', function (request, reply, done) { - t.equal(this.bar, 84) + t.assert.strictEqual(this.bar, 84) done() }) instance.setNotFoundHandler(function (req, reply) { - t.equal(this.foo, 42) - t.equal(this.bar, 84) + t.assert.strictEqual(this.foo, 42) + t.assert.strictEqual(this.bar, 84) reply.code(404).send('encapsulated was not found') }) done() }, { prefix: '/encapsulated' }) - fastify.inject('/not-found', (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.equal(res.payload, 'this was not found') - }) + { + const res = await fastify.inject('/not-found') + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'this was not found') + } - fastify.inject('/encapsulated/not-found', (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.equal(res.payload, 'encapsulated was not found') - }) + { + const res = await fastify.inject('/encapsulated/not-found') + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'encapsulated was not found') + } }) -test('encapsulated custom 404 without - prefix hook and handler context', t => { +test('encapsulated custom 404 without - prefix hook and handler context', (t, done) => { t.plan(13) const fastify = Fastify() @@ -633,29 +645,29 @@ test('encapsulated custom 404 without - prefix hook and handler context', t => { instance.decorate('bar', 84) instance.addHook('onRequest', function (req, res, done) { - t.equal(this.foo, 42) - t.equal(this.bar, 84) + t.assert.strictEqual(this.foo, 42) + t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('preHandler', function (request, reply, done) { - t.equal(this.foo, 42) - t.equal(this.bar, 84) + t.assert.strictEqual(this.foo, 42) + t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(this.foo, 42) - t.equal(this.bar, 84) + t.assert.strictEqual(this.foo, 42) + t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('onResponse', function (request, reply, done) { - t.equal(this.foo, 42) - t.equal(this.bar, 84) + t.assert.strictEqual(this.foo, 42) + t.assert.strictEqual(this.bar, 84) done() }) instance.setNotFoundHandler(function (request, reply) { - t.equal(this.foo, 42) - t.equal(this.bar, 84) + t.assert.strictEqual(this.foo, 42) + t.assert.strictEqual(this.bar, 84) reply.code(404).send('custom not found') }) @@ -663,34 +675,35 @@ test('encapsulated custom 404 without - prefix hook and handler context', t => { }) fastify.inject('/not-found', (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.equal(res.payload, 'custom not found') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'custom not found') + done() }) }) -test('run hooks on default 404', t => { +test('run hooks on default 404', (t, done) => { t.plan(7) const fastify = Fastify() fastify.addHook('onRequest', function (req, res, done) { - t.pass('onRequest called') + t.assert.ok(true, 'onRequest called') done() }) fastify.addHook('preHandler', function (request, reply, done) { - t.pass('preHandler called') + t.assert.ok(true, 'preHandler called') done() }) fastify.addHook('onSend', function (request, reply, payload, done) { - t.pass('onSend called') + t.assert.ok(true, 'onSend called') done() }) fastify.addHook('onResponse', function (request, reply, done) { - t.pass('onResponse called') + t.assert.ok(true, 'onResponse called') done() }) @@ -698,10 +711,10 @@ test('run hooks on default 404', t => { reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'PUT', @@ -709,35 +722,36 @@ test('run hooks on default 404', t => { body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) }) -test('run non-encapsulated plugin hooks on default 404', t => { +test('run non-encapsulated plugin hooks on default 404', (t, done) => { t.plan(6) const fastify = Fastify() fastify.register(fp(function (instance, options, done) { instance.addHook('onRequest', function (req, res, done) { - t.pass('onRequest called') + t.assert.ok(true, 'onRequest called') done() }) instance.addHook('preHandler', function (request, reply, done) { - t.pass('preHandler called') + t.assert.ok(true, 'preHandler called') done() }) instance.addHook('onSend', function (request, reply, payload, done) { - t.pass('onSend called') + t.assert.ok(true, 'onSend called') done() }) instance.addHook('onResponse', function (request, reply, done) { - t.pass('onResponse called') + t.assert.ok(true, 'onResponse called') done() }) @@ -753,34 +767,35 @@ test('run non-encapsulated plugin hooks on default 404', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('run non-encapsulated plugin hooks on custom 404', t => { +test('run non-encapsulated plugin hooks on custom 404', (t, done) => { t.plan(11) const fastify = Fastify() const plugin = fp((instance, opts, done) => { instance.addHook('onRequest', function (req, res, done) { - t.pass('onRequest called') + t.assert.ok(true, 'onRequest called') done() }) instance.addHook('preHandler', function (request, reply, done) { - t.pass('preHandler called') + t.assert.ok(true, 'preHandler called') done() }) instance.addHook('onSend', function (request, reply, payload, done) { - t.pass('onSend called') + t.assert.ok(true, 'onSend called') done() }) instance.addHook('onResponse', function (request, reply, done) { - t.pass('onResponse called') + t.assert.ok(true, 'onResponse called') done() }) @@ -800,34 +815,35 @@ test('run non-encapsulated plugin hooks on custom 404', t => { fastify.register(plugin) // Registering plugin after handler also works fastify.inject({ url: '/not-found' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.equal(res.payload, 'this was not found') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'this was not found') + done() }) }) -test('run hook with encapsulated 404', t => { +test('run hook with encapsulated 404', (t, done) => { t.plan(11) const fastify = Fastify() fastify.addHook('onRequest', function (req, res, done) { - t.pass('onRequest called') + t.assert.ok(true, 'onRequest called') done() }) fastify.addHook('preHandler', function (request, reply, done) { - t.pass('preHandler called') + t.assert.ok(true, 'preHandler called') done() }) fastify.addHook('onSend', function (request, reply, payload, done) { - t.pass('onSend called') + t.assert.ok(true, 'onSend called') done() }) fastify.addHook('onResponse', function (request, reply, done) { - t.pass('onResponse called') + t.assert.ok(true, 'onResponse called') done() }) @@ -837,32 +853,32 @@ test('run hook with encapsulated 404', t => { }) f.addHook('onRequest', function (req, res, done) { - t.pass('onRequest 2 called') + t.assert.ok(true, 'onRequest 2 called') done() }) f.addHook('preHandler', function (request, reply, done) { - t.pass('preHandler 2 called') + t.assert.ok(true, 'preHandler 2 called') done() }) f.addHook('onSend', function (request, reply, payload, done) { - t.pass('onSend 2 called') + t.assert.ok(true, 'onSend 2 called') done() }) f.addHook('onResponse', function (request, reply, done) { - t.pass('onResponse 2 called') + t.assert.ok(true, 'onResponse 2 called') done() }) done() }, { prefix: '/test' }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'PUT', @@ -870,34 +886,35 @@ test('run hook with encapsulated 404', t => { body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) }) -test('run hook with encapsulated 404 and framework-unsupported method', t => { +test('run hook with encapsulated 404 and framework-unsupported method', (t, done) => { t.plan(11) const fastify = Fastify() fastify.addHook('onRequest', function (req, res, done) { - t.pass('onRequest called') + t.assert.ok(true, 'onRequest called') done() }) fastify.addHook('preHandler', function (request, reply, done) { - t.pass('preHandler called') + t.assert.ok(true, 'preHandler called') done() }) fastify.addHook('onSend', function (request, reply, payload, done) { - t.pass('onSend called') + t.assert.ok(true, 'onSend called') done() }) fastify.addHook('onResponse', function (request, reply, done) { - t.pass('onResponse called') + t.assert.ok(true, 'onResponse called') done() }) @@ -907,32 +924,32 @@ test('run hook with encapsulated 404 and framework-unsupported method', t => { }) f.addHook('onRequest', function (req, res, done) { - t.pass('onRequest 2 called') + t.assert.ok(true, 'onRequest 2 called') done() }) f.addHook('preHandler', function (request, reply, done) { - t.pass('preHandler 2 called') + t.assert.ok(true, 'preHandler 2 called') done() }) f.addHook('onSend', function (request, reply, payload, done) { - t.pass('onSend 2 called') + t.assert.ok(true, 'onSend 2 called') done() }) f.addHook('onResponse', function (request, reply, done) { - t.pass('onResponse 2 called') + t.assert.ok(true, 'onResponse 2 called') done() }) done() }, { prefix: '/test' }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'PROPFIND', @@ -940,13 +957,14 @@ test('run hook with encapsulated 404 and framework-unsupported method', t => { body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) }) -test('hooks check 404', t => { +test('hooks check 404', (t, done) => { t.plan(13) const fastify = Fastify() @@ -956,23 +974,23 @@ test('hooks check 404', t => { }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.same(req.query, { foo: 'asd' }) - t.ok('called', 'onSend') + t.assert.deepStrictEqual(req.query, { foo: 'asd' }) + t.assert.ok(true, 'called onSend') done() }) fastify.addHook('onRequest', (req, res, done) => { - t.ok('called', 'onRequest') + t.assert.ok(true, 'called onRequest') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called', 'onResponse') + t.assert.ok(true, 'calledonResponse') done() }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'PUT', @@ -980,16 +998,17 @@ test('hooks check 404', t => { body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) }) sget({ method: 'GET', url: getServerUrl(fastify) + '/notSupported?foo=asd' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) }) @@ -1010,13 +1029,13 @@ test('setNotFoundHandler should not suppress duplicated routes checking', t => { reply.code(404).send('this was not found') }) - t.fail('setNotFoundHandler should not interfere duplicated route error') + t.assert.fail('setNotFoundHandler should not interfere duplicated route error') } catch (error) { - t.ok(error) + t.assert.ok(error) } }) -test('log debug for 404', t => { +test('log debug for 404', async t => { t.plan(1) const Writable = require('node:stream').Writable @@ -1039,28 +1058,29 @@ test('log debug for 404', t => { reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) - t.test('log debug', t => { + await t.test('log debug', (t, done) => { t.plan(7) fastify.inject({ method: 'GET', url: '/not-found' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) const INFO_LEVEL = 30 - t.equal(JSON.parse(logStream.logs[0]).msg, 'incoming request') - t.equal(JSON.parse(logStream.logs[1]).msg, 'Route GET:/not-found not found') - t.equal(JSON.parse(logStream.logs[1]).level, INFO_LEVEL) - t.equal(JSON.parse(logStream.logs[2]).msg, 'request completed') - t.equal(logStream.logs.length, 3) + t.assert.strictEqual(JSON.parse(logStream.logs[0]).msg, 'incoming request') + t.assert.strictEqual(JSON.parse(logStream.logs[1]).msg, 'Route GET:/not-found not found') + t.assert.strictEqual(JSON.parse(logStream.logs[1]).level, INFO_LEVEL) + t.assert.strictEqual(JSON.parse(logStream.logs[2]).msg, 'request completed') + t.assert.strictEqual(logStream.logs.length, 3) + done() }) }) }) -test('Unknown method', t => { +test('Unknown method', (t, done) => { t.plan(5) const fastify = Fastify() @@ -1069,14 +1089,14 @@ test('Unknown method', t => { reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const handler = () => {} // See https://github.com/fastify/light-my-request/pull/20 - t.throws(() => fastify.inject({ + t.assert.throws(() => fastify.inject({ method: 'UNKNOWN_METHOD', url: '/' }, handler), Error) @@ -1085,18 +1105,19 @@ test('Unknown method', t => { method: 'UNKNOWN_METHOD', url: getServerUrl(fastify) }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.strictSame(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', message: 'Client Error', statusCode: 400 }) + done() }) }) }) -test('recognizes errors from the http-errors module', t => { +test('recognizes errors from the http-errors module', (t, done) => { t.plan(5) const fastify = Fastify() @@ -1105,32 +1126,33 @@ test('recognizes errors from the http-errors module', t => { reply.send(new errors.NotFound()) }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) sget(getServerUrl(fastify), (err, response, body) => { - t.error(err) + t.assert.ifError(err) const obj = JSON.parse(body.toString()) - t.strictSame(obj, { + t.assert.deepStrictEqual(obj, { error: 'Not Found', message: 'Not Found', statusCode: 404 }) + done() }) }) }) }) -test('the default 404 handler can be invoked inside a prefixed plugin', t => { +test('the default 404 handler can be invoked inside a prefixed plugin', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1144,17 +1166,18 @@ test('the default 404 handler can be invoked inside a prefixed plugin', t => { }, { prefix: '/v1' }) fastify.inject('/v1/path', (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.strictSame(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) + done() }) }) -test('an inherited custom 404 handler can be invoked inside a prefixed plugin', t => { +test('an inherited custom 404 handler can be invoked inside a prefixed plugin', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1172,18 +1195,19 @@ test('an inherited custom 404 handler can be invoked inside a prefixed plugin', }, { prefix: '/v1' }) fastify.inject('/v1/path', (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) + done() }) }) -test('encapsulated custom 404 handler without a prefix is the handler for the entire 404 level', t => { - t.plan(6) +test('encapsulated custom 404 handler without a prefix is the handler for the entire 404 level', async t => { + t.plan(4) const fastify = Fastify() @@ -1206,38 +1230,39 @@ test('encapsulated custom 404 handler without a prefix is the handler for the en done() }, { prefix: 'prefixed' }) - fastify.inject('/not-found', (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.equal(res.payload, 'custom handler') - }) + { + const res = await fastify.inject('/not-found') + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'custom handler') + } - fastify.inject('/prefixed/not-found', (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.equal(res.payload, 'custom handler 2') - }) + { + const res = await fastify.inject('/prefixed/not-found') + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'custom handler 2') + } }) -test('cannot set notFoundHandler after binding', t => { +test('cannot set notFoundHandler after binding', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) try { fastify.setNotFoundHandler(() => { }) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) + done() } }) }) -test('404 inside onSend', t => { +test('404 inside onSend', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1257,29 +1282,30 @@ test('404 inside onSend', t => { } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: getServerUrl(fastify) }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) }) // https://github.com/fastify/fastify/issues/868 -test('onSend hooks run when an encapsulated route invokes the notFound handler', t => { +test('onSend hooks run when an encapsulated route invokes the notFound handler', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, done) => { instance.addHook('onSend', (request, reply, payload, done) => { - t.pass('onSend hook called') + t.assert.ok(true, 'onSend hook called') done() }) @@ -1291,16 +1317,17 @@ test('onSend hooks run when an encapsulated route invokes the notFound handler', }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) // https://github.com/fastify/fastify/issues/713 -test('preHandler option for setNotFoundHandler', t => { +test('preHandler option for setNotFoundHandler', async t => { t.plan(10) - t.test('preHandler option', t => { + await t.test('preHandler option', (t, done) => { t.plan(2) const fastify = Fastify() @@ -1318,14 +1345,15 @@ test('preHandler option for setNotFoundHandler', t => { url: '/not-found', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { preHandler: true, hello: 'world' }) + t.assert.deepStrictEqual(payload, { preHandler: true, hello: 'world' }) + done() }) }) // https://github.com/fastify/fastify/issues/2229 - t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', { timeout: 40000 }, t => { + await t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', { timeout: 40000 }, (t, done) => { t.plan(3) const fastify = Fastify() @@ -1339,7 +1367,7 @@ test('preHandler option for setNotFoundHandler', t => { }) fastify.post('/', function (req, reply) { - t.equal(reply.callNotFound(), reply) + t.assert.strictEqual(reply.callNotFound(), reply) }) fastify.inject({ @@ -1347,13 +1375,14 @@ test('preHandler option for setNotFoundHandler', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { preHandler: true, hello: 'world' }) + t.assert.deepStrictEqual(payload, { preHandler: true, hello: 'world' }) + done() }) }) - t.test('preHandler hook in setNotFoundHandler should accept an array of functions and be called when callNotFound', t => { + await t.test('preHandler hook in setNotFoundHandler should accept an array of functions and be called when callNotFound', (t, done) => { t.plan(2) const fastify = Fastify() @@ -1381,13 +1410,14 @@ test('preHandler option for setNotFoundHandler', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { preHandler1: true, preHandler2: true, hello: 'world' }) + t.assert.deepStrictEqual(payload, { preHandler1: true, preHandler2: true, hello: 'world' }) + done() }) }) - t.test('preHandler option should be called after preHandler hook', t => { + await t.test('preHandler option should be called after preHandler hook', (t, done) => { t.plan(2) const fastify = Fastify() @@ -1410,14 +1440,15 @@ test('preHandler option for setNotFoundHandler', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { check: 'ab', hello: 'world' }) + t.assert.deepStrictEqual(payload, { check: 'ab', hello: 'world' }) + done() }) }) - t.test('preHandler option should be unique per prefix', t => { - t.plan(4) + await t.test('preHandler option should be unique per prefix', async t => { + t.plan(2) const fastify = Fastify() fastify.setNotFoundHandler({ @@ -1437,28 +1468,30 @@ test('preHandler option for setNotFoundHandler', t => { n() }, { prefix: '/no' }) - fastify.inject({ - method: 'POST', - url: '/not-found', - payload: { hello: 'world' } - }, (err, res) => { - t.error(err) + { + const res = await fastify.inject({ + method: 'POST', + url: '/not-found', + payload: { hello: 'world' } + }) + const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'earth' }) - }) + t.assert.deepStrictEqual(payload, { hello: 'earth' }) + } + + { + const res = await fastify.inject({ + method: 'POST', + url: '/no/not-found', + payload: { hello: 'world' } + }) - fastify.inject({ - method: 'POST', - url: '/no/not-found', - payload: { hello: 'world' } - }, (err, res) => { - t.error(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) - }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + } }) - t.test('preHandler option should handle errors', t => { + await t.test('preHandler option should handle errors', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1475,18 +1508,19 @@ test('preHandler option for setNotFoundHandler', t => { url: '/not-found', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(res.statusCode, 500) - t.same(payload, { + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(payload, { message: 'kaboom', error: 'Internal Server Error', statusCode: 500 }) + done() }) }) - t.test('preHandler option should handle errors with custom status code', t => { + await t.test('preHandler option should handle errors with custom status code', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1504,18 +1538,19 @@ test('preHandler option for setNotFoundHandler', t => { url: '/not-found', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(res.statusCode, 401) - t.same(payload, { + t.assert.strictEqual(res.statusCode, 401) + t.assert.deepStrictEqual(payload, { message: 'go away', error: 'Unauthorized', statusCode: 401 }) + done() }) }) - t.test('preHandler option could accept an array of functions', t => { + await t.test('preHandler option could accept an array of functions', (t, done) => { t.plan(2) const fastify = Fastify() @@ -1539,14 +1574,15 @@ test('preHandler option for setNotFoundHandler', t => { url: '/not-found', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { preHandler: 'ab', hello: 'world' }) + t.assert.deepStrictEqual(payload, { preHandler: 'ab', hello: 'world' }) + done() }) }) - t.test('preHandler option does not interfere with preHandler', t => { - t.plan(4) + await t.test('preHandler option does not interfere with preHandler', async t => { + t.plan(2) const fastify = Fastify() fastify.addHook('preHandler', (req, reply, done) => { @@ -1571,28 +1607,30 @@ test('preHandler option for setNotFoundHandler', t => { n() }, { prefix: '/no' }) - fastify.inject({ - method: 'post', - url: '/not-found', - payload: { hello: 'world' } - }, (err, res) => { - t.error(err) + { + const res = await fastify.inject({ + method: 'post', + url: '/not-found', + payload: { hello: 'world' } + }) + const payload = JSON.parse(res.payload) - t.same(payload, { check: 'ab', hello: 'world' }) - }) + t.assert.deepStrictEqual(payload, { check: 'ab', hello: 'world' }) + } + + { + const res = await fastify.inject({ + method: 'post', + url: '/no/not-found', + payload: { hello: 'world' } + }) - fastify.inject({ - method: 'post', - url: '/no/not-found', - payload: { hello: 'world' } - }, (err, res) => { - t.error(err) const payload = JSON.parse(res.payload) - t.same(payload, { check: 'a', hello: 'world' }) - }) + t.assert.deepStrictEqual(payload, { check: 'a', hello: 'world' }) + } }) - t.test('preHandler option should keep the context', t => { + await t.test('preHandler option should keep the context', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1600,7 +1638,7 @@ test('preHandler option for setNotFoundHandler', t => { fastify.setNotFoundHandler({ preHandler: function (req, reply, done) { - t.equal(this.foo, 42) + t.assert.strictEqual(this.foo, 42) this.foo += 1 req.body.foo = this.foo done() @@ -1614,14 +1652,15 @@ test('preHandler option for setNotFoundHandler', t => { url: '/not-found', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { foo: 43, hello: 'world' }) + t.assert.deepStrictEqual(payload, { foo: 43, hello: 'world' }) + done() }) }) }) -test('reply.notFound invoked the notFound handler', t => { +test('reply.notFound invoked the notFound handler', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1638,30 +1677,31 @@ test('reply.notFound invoked the notFound handler', t => { url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'kaboom', statusCode: 404 }) + done() }) }) -test('The custom error handler should be invoked after the custom not found handler', t => { +test('The custom error handler should be invoked after the custom not found handler', (t, done) => { t.plan(6) const fastify = Fastify() const order = [1, 2] fastify.setErrorHandler((err, req, reply) => { - t.equal(order.shift(), 2) - t.type(err, Error) + t.assert.strictEqual(order.shift(), 2) + t.assert.ok(err instanceof Error) reply.send(err) }) fastify.setNotFoundHandler((req, reply) => { - t.equal(order.shift(), 1) + t.assert.strictEqual(order.shift(), 1) reply.code(404).send(new Error('kaboom')) }) @@ -1673,23 +1713,24 @@ test('The custom error handler should be invoked after the custom not found hand url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'kaboom', statusCode: 404 }) + done() }) }) -test('If the custom not found handler does not use an Error, the custom error handler should not be called', t => { +test('If the custom not found handler does not use an Error, the custom error handler should not be called', (t, done) => { t.plan(3) const fastify = Fastify() fastify.setErrorHandler((_err, req, reply) => { - t.fail('Should not be called') + t.assert.fail('Should not be called') }) fastify.setNotFoundHandler((req, reply) => { @@ -1704,13 +1745,14 @@ test('If the custom not found handler does not use an Error, the custom error ha url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.equal(res.payload, 'kaboom') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'kaboom') + done() }) }) -test('preValidation option', t => { +test('preValidation option', (t, done) => { t.plan(3) const fastify = Fastify() @@ -1718,7 +1760,7 @@ test('preValidation option', t => { fastify.setNotFoundHandler({ preValidation: function (req, reply, done) { - t.ok(this.foo) + t.assert.ok(this.foo) done() } }, function (req, reply) { @@ -1730,24 +1772,25 @@ test('preValidation option', t => { url: '/not-found', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + done() }) }) -t.test('preValidation option could accept an array of functions', t => { +test('preValidation option could accept an array of functions', (t, done) => { t.plan(4) const fastify = Fastify() fastify.setNotFoundHandler({ preValidation: [ (req, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }, (req, reply, done) => { - t.ok('called') + t.assert.ok('called') done() } ] @@ -1760,13 +1803,14 @@ t.test('preValidation option could accept an array of functions', t => { url: '/not-found', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + done() }) }) -test('Should fail to invoke callNotFound inside a 404 handler', t => { +test('Should fail to invoke callNotFound inside a 404 handler', (t, done) => { t.plan(5) let fastify = null @@ -1779,7 +1823,7 @@ test('Should fail to invoke callNotFound inside a 404 handler', t => { } }) } catch (e) { - t.fail() + t.assert.fail() } fastify.setNotFoundHandler((req, reply) => { @@ -1791,95 +1835,100 @@ test('Should fail to invoke callNotFound inside a 404 handler', t => { }) logStream.once('data', line => { - t.equal(line.msg, 'Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.') - t.equal(line.level, 40) + t.assert.strictEqual(line.msg, 'Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.') + t.assert.strictEqual(line.level, 40) }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.equal(res.payload, '404 Not Found') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, '404 Not Found') + done() }) }) -test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', t => { - t.test('Dynamic route', t => { +test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', async t => { + await t.test('Dynamic route', (t, done) => { t.plan(3) const fastify = Fastify() - fastify.get('/hello/:id', () => t.fail('we should not be here')) + fastify.get('/hello/:id', () => t.assert.fail('we should not be here')) fastify.inject({ url: '/hello/%world', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(response.payload), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Bad Request', message: "'/hello/%world' is not a valid url component", statusCode: 400, code: 'FST_ERR_BAD_URL' }) + done() }) }) - t.test('Wildcard', t => { + await t.test('Wildcard', (t, done) => { t.plan(3) const fastify = Fastify() - fastify.get('*', () => t.fail('we should not be here')) + fastify.get('*', () => t.assert.fail('we should not be here')) fastify.inject({ url: '/hello/%world', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(response.payload), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Bad Request', message: "'/hello/%world' is not a valid url component", statusCode: 400, code: 'FST_ERR_BAD_URL' }) + done() }) }) - t.test('No route registered', t => { + await t.test('No route registered', (t, done) => { t.plan(3) const fastify = Fastify() fastify.inject({ url: '/%c0', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 404) - t.same(JSON.parse(response.payload), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Not Found', message: 'Route GET:/%c0 not found', statusCode: 404 }) + done() }) }) - t.test('Only / is registered', t => { + await t.test('Only / is registered', (t, done) => { t.plan(3) const fastify = Fastify() - fastify.get('/', () => t.fail('we should not be here')) + fastify.get('/', () => t.assert.fail('we should not be here')) fastify.inject({ url: '/non-existing', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 404) - t.same(JSON.parse(response.payload), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Not Found', message: 'Route GET:/non-existing not found', statusCode: 404 }) + done() }) }) - t.test('customized 404', t => { + await t.test('customized 404', (t, done) => { t.plan(3) const fastify = Fastify({ logger: true }) fastify.setNotFoundHandler(function (req, reply) { @@ -1889,18 +1938,17 @@ test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', t => { url: '/%c0', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 404) - t.same(response.payload, 'this was not found') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.deepStrictEqual(response.payload, 'this was not found') + done() }) }) - - t.end() }) -test('setNotFoundHandler should be chaining fastify instance', t => { - t.test('Register route after setNotFoundHandler', t => { - t.plan(6) +test('setNotFoundHandler should be chaining fastify instance', async t => { + await t.test('Register route after setNotFoundHandler', async t => { + t.plan(4) const fastify = Fastify() fastify.setNotFoundHandler(function (_req, reply) { reply.code(404).send('this was not found') @@ -1908,52 +1956,50 @@ test('setNotFoundHandler should be chaining fastify instance', t => { reply.send('valid route') }) - fastify.inject({ - url: '/invalid-route', - method: 'GET' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.payload, 'this was not found') - }) + { + const response = await fastify.inject({ + url: '/invalid-route', + method: 'GET' + }) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.payload, 'this was not found') + } - fastify.inject({ - url: '/valid-route', - method: 'GET' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.payload, 'valid route') - }) - }) + { + const response = await fastify.inject({ + url: '/valid-route', + method: 'GET' + }) - t.end() + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.payload, 'valid route') + } + }) }) -test('Send 404 when frameworkError calls reply.callNotFound', t => { - t.test('Dynamic route', t => { +test('Send 404 when frameworkError calls reply.callNotFound', async t => { + await t.test('Dynamic route', (t, done) => { t.plan(4) const fastify = Fastify({ frameworkErrors: (error, req, reply) => { - t.equal(error.message, "'/hello/%world' is not a valid url component") + t.assert.strictEqual(error.message, "'/hello/%world' is not a valid url component") return reply.callNotFound() } }) - fastify.get('/hello/:id', () => t.fail('we should not be here')) + fastify.get('/hello/:id', () => t.assert.fail('we should not be here')) fastify.inject({ url: '/hello/%world', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 404) - t.equal(response.payload, '404 Not Found') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + t.assert.strictEqual(response.payload, '404 Not Found') + done() }) }) - - t.end() }) -test('hooks are applied to not found handlers /1', async ({ equal }) => { +test('hooks are applied to not found handlers /1', async t => { const fastify = Fastify() // adding await here is fundamental for this test @@ -1969,10 +2015,10 @@ test('hooks are applied to not found handlers /1', async ({ equal }) => { }) const { statusCode } = await fastify.inject('/') - equal(statusCode, 401) + t.assert.strictEqual(statusCode, 401) }) -test('hooks are applied to not found handlers /2', async ({ equal }) => { +test('hooks are applied to not found handlers /2', async t => { const fastify = Fastify() async function plugin (fastify) { @@ -1990,15 +2036,15 @@ test('hooks are applied to not found handlers /2', async ({ equal }) => { }) const { statusCode } = await fastify.inject('/') - equal(statusCode, 401) + t.assert.strictEqual(statusCode, 401) }) -test('hooks are applied to not found handlers /3', async ({ equal, fail }) => { +test('hooks are applied to not found handlers /3', async t => { const fastify = Fastify() async function plugin (fastify) { fastify.setNotFoundHandler({ errorHandler }, async () => { - fail('this should never be called') + t.assert.fail('this should never be called') }) function errorHandler (_, request, reply) { @@ -2015,5 +2061,5 @@ test('hooks are applied to not found handlers /3', async ({ equal, fail }) => { }) const { statusCode } = await fastify.inject('/') - equal(statusCode, 401) + t.assert.strictEqual(statusCode, 401) }) From d59ed985a077c41312ece07eb87088e1bc7805d6 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 25 Oct 2024 17:37:54 +0200 Subject: [PATCH 0791/1295] fix: exposeHeadRoute should pass "onSend" hook handlers provided to GET handler if method is not uppercased (#5766) * fix: normalize route methods early * add test for onSend fix * fix lint * Update lib/route.js Co-authored-by: Manuel Spigolon Signed-off-by: Aras Abbasi --------- Signed-off-by: Aras Abbasi Co-authored-by: Manuel Spigolon --- lib/route.js | 40 ++++++------ test/request-error.test.js | 122 +++++++++++++++++++++++++++++++++++++ test/route.8.test.js | 62 +++++++++++++++++++ test/types/route.test-d.ts | 30 +++++++-- types/request.d.ts | 4 +- types/utils.d.ts | 12 +++- 6 files changed, 243 insertions(+), 27 deletions(-) diff --git a/lib/route.js b/lib/route.js index ecefbd9b39c..10c7cacafa0 100644 --- a/lib/route.js +++ b/lib/route.js @@ -181,44 +181,46 @@ function buildRouting (options) { * @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }} */ function route ({ options, isFastify }) { + throwIfAlreadyStarted('Cannot add route!') + // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator const opts = { ...options } - const { exposeHeadRoute } = opts - const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null - const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes + const path = opts.url || opts.path || '' - const isGetRoute = opts.method === 'GET' || - (Array.isArray(opts.method) && opts.method.includes('GET')) - const isHeadRoute = opts.method === 'HEAD' || - (Array.isArray(opts.method) && opts.method.includes('HEAD')) + if (!opts.handler) { + throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path) + } - // we need to clone a set of initial options for HEAD route - const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null + if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') { + throw new FST_ERR_ROUTE_HANDLER_NOT_FN(opts.method, path) + } - throwIfAlreadyStarted('Cannot add route!') + validateBodyLimitOption(opts.bodyLimit) - const path = opts.url || opts.path || '' + const shouldExposeHead = opts.exposeHeadRoute ?? globalExposeHeadRoutes + + let isGetRoute = false + let isHeadRoute = false if (Array.isArray(opts.method)) { for (let i = 0; i < opts.method.length; ++i) { opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i]) validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema) + + isGetRoute = opts.method.includes('GET') + isHeadRoute = opts.method.includes('HEAD') } } else { opts.method = normalizeAndValidateMethod.call(this, opts.method) validateSchemaBodyOption.call(this, opts.method, path, opts.schema) - } - if (!opts.handler) { - throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path) + isGetRoute = opts.method === 'GET' + isHeadRoute = opts.method === 'HEAD' } - if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') { - throw new FST_ERR_ROUTE_HANDLER_NOT_FN(opts.method, path) - } - - validateBodyLimitOption(opts.bodyLimit) + // we need to clone a set of initial options for HEAD route + const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null const prefix = this[kRoutePrefix] diff --git a/test/request-error.test.js b/test/request-error.test.js index 4cb98fa68aa..921dce3e049 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -363,6 +363,128 @@ test('request.routeOptions should be immutable', t => { }) }) +test('request.routeOptions.method is an uppercase string /1', t => { + t.plan(4) + const fastify = Fastify() + const handler = function (req, res) { + t.equal('POST', req.routeOptions.method) + res.send({}) + } + + fastify.post('/', { + bodyLimit: 1000, + handler + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port, + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('request.routeOptions.method is an uppercase string /2', t => { + t.plan(4) + const fastify = Fastify() + const handler = function (req, res) { + t.equal('POST', req.routeOptions.method) + res.send({}) + } + + fastify.route({ + url: '/', + method: 'POST', + bodyLimit: 1000, + handler + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port, + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('request.routeOptions.method is an uppercase string /3', t => { + t.plan(4) + const fastify = Fastify() + const handler = function (req, res) { + t.equal('POST', req.routeOptions.method) + res.send({}) + } + + fastify.route({ + url: '/', + method: 'pOSt', + bodyLimit: 1000, + handler + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port, + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('request.routeOptions.method is an array with uppercase string', t => { + t.plan(4) + const fastify = Fastify() + const handler = function (req, res) { + t.strictSame(['POST'], req.routeOptions.method) + res.send({}) + } + + fastify.route({ + url: '/', + method: ['pOSt'], + bodyLimit: 1000, + handler + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port, + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + test('test request.routeOptions.version', t => { t.plan(7) const fastify = Fastify() diff --git a/test/route.8.test.js b/test/route.8.test.js index 23513e8465b..cf9e4466f1c 100644 --- a/test/route.8.test.js +++ b/test/route.8.test.js @@ -169,3 +169,65 @@ test('Adding manually HEAD route after GET with the same path throws Fastify dup t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') } }) + +test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /1', async (t) => { + t.plan(1) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + await fastify.register((scope, opts, next) => { + scope.route({ + method: 'GET', + path: '/route', + handler: (req, reply) => { + reply.send({ ok: true }) + }, + onSend: (req, reply, payload, done) => { + reply.header('x-content-type', 'application/fastify') + done(null, payload) + } + }) + + next() + }, { prefix: '/prefix' }) + + await fastify.ready() + + const result = await fastify.inject({ + url: '/prefix/route', + method: 'HEAD' + }) + + t.equal(result.headers['x-content-type'], 'application/fastify') +}) + +test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /2', async (t) => { + t.plan(1) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + await fastify.register((scope, opts, next) => { + scope.route({ + method: 'get', + path: '/route', + handler: (req, reply) => { + reply.send({ ok: true }) + }, + onSend: (req, reply, payload, done) => { + reply.header('x-content-type', 'application/fastify') + done(null, payload) + } + }) + + next() + }, { prefix: '/prefix' }) + + await fastify.ready() + + const result = await fastify.inject({ + url: '/prefix/route', + method: 'HEAD' + }) + + t.equal(result.headers['x-content-type'], 'application/fastify') +}) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index ab8ecf08829..244b4935a1d 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -398,9 +398,21 @@ type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' }) }) -expectError(fastify().route({ +expectType(fastify().route({ url: '/', - method: 'CONNECT', // not a valid method + method: 'CONNECT', // not a valid method but could be implemented by the user + handler: routeHandler +})) + +expectType(fastify().route({ + url: '/', + method: 'OPTIONS', + handler: routeHandler +})) + +expectType(fastify().route({ + url: '/', + method: 'OPTION', // OPTION is a typo for OPTIONS handler: routeHandler })) @@ -410,7 +422,7 @@ expectType(fastify().route({ handler: routeHandler })) -expectError(fastify().route({ +expectType(fastify().route({ url: '/', method: ['GET', 'POST', 'OPTION'], // OPTION is a typo for OPTIONS handler: routeHandler @@ -507,10 +519,20 @@ expectType(fastify().route({ handler: routeHandlerWithReturnValue })) +expectType(fastify().route({ + url: '/', + method: 'GET', + handler: (req) => { + expectType(req.routeOptions.method) + expectAssignable>(req.routeOptions.method) + } +})) + expectType(fastify().route({ url: '/', method: ['HEAD', 'GET'], handler: (req) => { - expectType(req.routeOptions.method) + expectType(req.routeOptions.method) + expectAssignable>(req.routeOptions.method) } })) diff --git a/types/request.d.ts b/types/request.d.ts index cab01ee3873..fcfdcd7a0b4 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -5,7 +5,7 @@ import { FastifyBaseLogger } from './logger' import { FastifyRouteConfig, RouteGenericInterface, RouteHandlerMethod } from './route' import { FastifySchema } from './schema' import { FastifyRequestType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyRequestType } from './type-provider' -import { ContextConfigDefault, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './utils' +import { ContextConfigDefault, HTTPMethods, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './utils' type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' export interface RequestGenericInterface { @@ -21,7 +21,7 @@ export interface ValidationFunction { } export interface RequestRouteOptions { - method: string | string[]; + method: HTTPMethods | HTTPMethods[]; // `url` can be `undefined` for instance when `request.is404` is true url: string | undefined; bodyLimit: number; diff --git a/types/utils.d.ts b/types/utils.d.ts index e7897f52c82..d65e1e91ef2 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -2,13 +2,22 @@ import * as http from 'http' import * as http2 from 'http2' import * as https from 'https' +type AutocompletePrimitiveBaseType = + T extends string ? string : + T extends number ? number : + T extends boolean ? boolean : + never + +export type Autocomplete = T | (AutocompletePrimitiveBaseType & Record) + /** * Standard HTTP method strings + * for internal use */ type _HTTPMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS' | 'PROPFIND' | 'PROPPATCH' | 'MKCOL' | 'COPY' | 'MOVE' | 'LOCK' | 'UNLOCK' | 'TRACE' | 'SEARCH' | 'REPORT' | 'MKCALENDAR' -export type HTTPMethods = Uppercase<_HTTPMethods> | Lowercase<_HTTPMethods> +export type HTTPMethods = Autocomplete<_HTTPMethods | Lowercase<_HTTPMethods>> /** * A union type of the Node.js server types from the http, https, and http2 modules. @@ -56,7 +65,6 @@ type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 type HttpCodes = StringAsNumber<`${CodeClasses}${Digit}${Digit}`> type HttpKeys = HttpCodes | `${Digit}xx` export type StatusCodeReply = { - [Key in HttpKeys]?: unknown; } From 2ee97ffa57f80be390414585ca8c07ecdaf250bc Mon Sep 17 00:00:00 2001 From: Karan Raina Date: Sat, 26 Oct 2024 20:31:52 +0530 Subject: [PATCH 0792/1295] test: move allowUnsafeRegex to node test runner (#5770) --- test/allowUnsafeRegex.test.js | 56 ++++++++++++++++--------------- test/async_hooks.test.js | 58 ++++++++++++++++---------------- test/buffer.test.js | 19 +++++------ test/case-insensitive.test.js | 59 +++++++++++++++++---------------- test/chainable.test.js | 9 +++-- test/check.test.js | 17 +++++----- test/childLoggerFactory.test.js | 54 +++++++++++++++++------------- test/client-timeout.test.js | 10 +++--- 8 files changed, 148 insertions(+), 134 deletions(-) diff --git a/test/allowUnsafeRegex.test.js b/test/allowUnsafeRegex.test.js index 6adba0a068a..f700295dc59 100644 --- a/test/allowUnsafeRegex.test.js +++ b/test/allowUnsafeRegex.test.js @@ -1,117 +1,121 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat -test('allow unsafe regex', t => { +test('allow unsafe regex', (t, done) => { t.plan(4) const fastify = Fastify({ allowUnsafeRegex: false }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/:foo(^[0-9]*$)', (req, reply) => { reply.send({ foo: req.params.foo }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/1234' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { foo: '1234' }) + done() }) }) }) -test('allow unsafe regex not match', t => { +test('allow unsafe regex not match', (t, done) => { t.plan(3) const fastify = Fastify({ allowUnsafeRegex: false }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/:foo(^[0-9]*$)', (req, reply) => { reply.send({ foo: req.params.foo }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/a1234' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.equal(response.statusCode, 404) + done() }) }) }) -test('allow unsafe regex not safe', t => { +test('allow unsafe regex not safe', (t, done) => { t.plan(1) const fastify = Fastify({ allowUnsafeRegex: false }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) - t.throws(() => { + t.assert.throws(() => { fastify.get('/:foo(^([0-9]+){4}$)', (req, reply) => { reply.send({ foo: req.params.foo }) }) }) + done() }) -test('allow unsafe regex not safe by default', t => { +test('allow unsafe regex not safe by default', (t, done) => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) - t.throws(() => { + t.assert.throws(() => { fastify.get('/:foo(^([0-9]+){4}$)', (req, reply) => { reply.send({ foo: req.params.foo }) }) }) + done() }) -test('allow unsafe regex allow unsafe', t => { +test('allow unsafe regex allow unsafe', (t, done) => { t.plan(5) const fastify = Fastify({ allowUnsafeRegex: true }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) - t.doesNotThrow(() => { + t.assert.doesNotThrow(() => { fastify.get('/:foo(^([0-9]+){4}$)', (req, reply) => { reply.send({ foo: req.params.foo }) }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/1234' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) + t.assert.deepEqual(JSON.parse(body), { foo: '1234' }) + done() }) }) }) diff --git a/test/async_hooks.test.js b/test/async_hooks.test.js index 992f21f023b..1e2eea4e8af 100644 --- a/test/async_hooks.test.js +++ b/test/async_hooks.test.js @@ -1,7 +1,7 @@ 'use strict' const { createHook } = require('node:async_hooks') -const t = require('tap') +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat @@ -20,27 +20,17 @@ createHook({ const app = Fastify({ logger: false }) -app.get('/', function (request, reply) { - reply.send({ id: 42 }) -}) - -app.post('/', function (request, reply) { - reply.send({ id: 42 }) -}) +test('test async hooks', (t, done) => { + app.get('/', function (request, reply) { + reply.send({ id: 42 }) + }) -app.listen({ port: 0 }, function (err, address) { - t.error(err) + app.post('/', function (request, reply) { + reply.send({ id: 42 }) + }) - sget({ - method: 'POST', - url: 'http://localhost:' + app.server.address().port, - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + app.listen({ port: 0 }, function (err, address) { + t.assert.ifError(err) sget({ method: 'POST', @@ -50,19 +40,31 @@ app.listen({ port: 0 }, function (err, address) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) sget({ - method: 'GET', + method: 'POST', url: 'http://localhost:' + app.server.address().port, + body: { + hello: 'world' + }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - app.close() - t.equal(remainingIds.size, 0) - t.end() + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) + + sget({ + method: 'GET', + url: 'http://localhost:' + app.server.address().port, + json: true + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) + app.close() + t.assert.equal(remainingIds.size, 0) + done() + }) }) }) }) diff --git a/test/buffer.test.js b/test/buffer.test.js index 65ba62b2518..49b26f372e4 100644 --- a/test/buffer.test.js +++ b/test/buffer.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') test('Buffer test', async t => { @@ -12,7 +11,7 @@ test('Buffer test', async t => { return request.body }) - test('should return 200 if the body is not empty', async t => { + await test('should return 200 if the body is not empty', async t => { t.plan(3) const response = await fastify.inject({ @@ -24,12 +23,12 @@ test('Buffer test', async t => { } }) - t.error(response.error) - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), '{"hello":"world"}') + t.assert.ifError(response.error) + t.assert.equal(response.statusCode, 200) + t.assert.deepEqual(response.payload.toString(), '{"hello":"world"}') }) - test('should return 400 if the body is empty', async t => { + await test('should return 400 if the body is empty', async t => { t.plan(3) const response = await fastify.inject({ @@ -41,9 +40,9 @@ test('Buffer test', async t => { } }) - t.error(response.error) - t.equal(response.statusCode, 400) - t.same(JSON.parse(response.payload.toString()), { + t.assert.ifError(response.error) + t.assert.equal(response.statusCode, 400) + t.assert.deepEqual(JSON.parse(response.payload.toString()), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', diff --git a/test/case-insensitive.test.js b/test/case-insensitive.test.js index d9980acd6bb..c54dab0ad1c 100644 --- a/test/case-insensitive.test.js +++ b/test/case-insensitive.test.js @@ -1,120 +1,123 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat -test('case insensitive', t => { +test('case insensitive', (t, done) => { t.plan(4) const fastify = Fastify({ caseSensitive: false }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/foo', (req, reply) => { reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/FOO' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) + t.assert.deepEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) -test('case insensitive inject', t => { +test('case insensitive inject', (t, done) => { t.plan(4) const fastify = Fastify({ caseSensitive: false }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/foo', (req, reply) => { reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/FOO' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), { + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) + t.assert.deepEqual(JSON.parse(response.payload), { hello: 'world' }) + done() }) }) }) -test('case insensitive (parametric)', t => { +test('case insensitive (parametric)', (t, done) => { t.plan(5) const fastify = Fastify({ caseSensitive: false }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/foo/:param', (req, reply) => { - t.equal(req.params.param, 'bAr') + t.assert.equal(req.params.param, 'bAr') reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/FoO/bAr' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) + t.assert.deepEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) -test('case insensitive (wildcard)', t => { +test('case insensitive (wildcard)', (t, done) => { t.plan(5) const fastify = Fastify({ caseSensitive: false }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/foo/*', (req, reply) => { - t.equal(req.params['*'], 'bAr/baZ') + t.assert.equal(req.params['*'], 'bAr/baZ') reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/FoO/bAr/baZ' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.equal(response.statusCode, 200) + t.assert.deepEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) diff --git a/test/chainable.test.js b/test/chainable.test.js index 276083ef3fc..f73709c3ed6 100644 --- a/test/chainable.test.js +++ b/test/chainable.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const fastify = require('..')() const noop = () => {} @@ -22,17 +21,17 @@ const opts = { test('chainable - get', t => { t.plan(1) - t.type(fastify.get('/', opts, noop), fastify) + t.assert.strictEqual(fastify.get('/', opts, noop), fastify) }) test('chainable - post', t => { t.plan(1) - t.type(fastify.post('/', opts, noop), fastify) + t.assert.strictEqual(fastify.post('/', opts, noop), fastify) }) test('chainable - route', t => { t.plan(1) - t.type(fastify.route({ + t.assert.strictEqual(fastify.route({ method: 'GET', url: '/other', schema: opts.schema, diff --git a/test/check.test.js b/test/check.test.js index 6a644e2f634..bf9fd34543c 100644 --- a/test/check.test.js +++ b/test/check.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const { S } = require('fluent-json-schema') const Fastify = require('..') const sget = require('simple-get').concat @@ -107,15 +106,15 @@ const handler = (request, reply) => { }) } -test('serialize the response for a Bad Request error, as defined on the schema', t => { +test('serialize the response for a Bad Request error, as defined on the schema', (t, done) => { const fastify = Fastify({}) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.post('/', options, handler) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const url = `http://localhost:${fastify.server.address().port}/` @@ -124,14 +123,14 @@ test('serialize the response for a Bad Request error, as defined on the schema', url, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(body, { + t.assert.ifError(err) + t.assert.equal(response.statusCode, 400) + t.assert.deepEqual(body, { statusCode: 400, error: 'Bad Request', message: 'body must be object' }) - t.end() + done() }) }) }) diff --git a/test/childLoggerFactory.test.js b/test/childLoggerFactory.test.js index 8471a562501..644f98be7a2 100644 --- a/test/childLoggerFactory.test.js +++ b/test/childLoggerFactory.test.js @@ -1,15 +1,15 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') -test('Should accept a custom childLoggerFactory function', t => { +test('Should accept a custom childLoggerFactory function', (t, done) => { t.plan(4) const fastify = Fastify() fastify.setChildLoggerFactory(function (logger, bindings, opts) { - t.ok(bindings.reqId) - t.ok(opts) + t.assert.ok(bindings.reqId) + t.assert.ok(opts) this.log.debug(bindings, 'created child logger') return logger.child(bindings, opts) }) @@ -19,19 +19,21 @@ test('Should accept a custom childLoggerFactory function', t => { reply.send() }) + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, res) => { - t.error(err) - fastify.close() + t.assert.ifError(err) + done() }) }) }) -test('Should accept a custom childLoggerFactory function as option', t => { +test('Should accept a custom childLoggerFactory function as option', (t, done) => { t.plan(2) const fastify = Fastify({ @@ -48,19 +50,21 @@ test('Should accept a custom childLoggerFactory function as option', t => { reply.send() }) + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, res) => { - t.error(err) - fastify.close() + t.assert.ifError(err) + done() }) }) }) -test('req.log should be the instance returned by the factory', t => { +test('req.log should be the instance returned by the factory', (t, done) => { t.plan(3) const fastify = Fastify() @@ -70,24 +74,26 @@ test('req.log should be the instance returned by the factory', t => { }) fastify.get('/', (req, reply) => { - t.equal(req.log, fastify.log) + t.assert.equal(req.log, fastify.log) req.log.info('log message') reply.send() }) + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, res) => { - t.error(err) - fastify.close() + t.assert.ifError(err) + done() }) }) }) -test('should throw error if invalid logger is returned', t => { +test('should throw error if invalid logger is returned', (t, done) => { t.plan(2) const fastify = Fastify() @@ -100,20 +106,22 @@ test('should throw error if invalid logger is returned', t => { reply.send() }) + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, err => { - t.error(err) - t.throws(() => { + t.assert.ifError(err) + t.assert.throws(() => { try { fastify.inject({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err) => { - t.fail('request should have failed but did not') - t.error(err) - fastify.close() + t.assert.fail('request should have failed but did not') + t.assert.ifError(err) + done() }) } finally { - fastify.close() + done() } }, { code: 'FST_ERR_LOG_INVALID_LOGGER' }) }) diff --git a/test/client-timeout.test.js b/test/client-timeout.test.js index b88aee8a918..7661a1456bd 100644 --- a/test/client-timeout.test.js +++ b/test/client-timeout.test.js @@ -1,13 +1,13 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const fastify = require('..')({ requestTimeout: 5, http: { connectionsCheckingInterval: 1000 } }) const { connect } = require('node:net') -test('requestTimeout should return 408', t => { +test('requestTimeout should return 408', (t, done) => { t.plan(1) - t.teardown(() => { + t.after(() => { fastify.close() }) @@ -28,11 +28,11 @@ test('requestTimeout should return 408', t => { socket.on('data', c => (data = Buffer.concat([data, c]))) socket.on('end', () => { - t.equal( + t.assert.equal( data.toString('utf-8'), 'HTTP/1.1 408 Request Timeout\r\nContent-Length: 71\r\nContent-Type: application/json\r\n\r\n{"error":"Request Timeout","message":"Client Timeout","statusCode":408}' ) - t.end() + done() }) }) }) From dead2738019a41436f36c0a1b14719030f203b30 Mon Sep 17 00:00:00 2001 From: Karan Raina Date: Sun, 27 Oct 2024 16:59:14 +0530 Subject: [PATCH 0793/1295] chore: Conditionally require pino if logger is enabled (#5763) * Conditionally require pino if logger is enabled * add unit tests for pino conditional call * unit test when loggerInstance is passed * Update logger-utils.js Co-authored-by: Manuel Spigolon Signed-off-by: Karan Raina * create logger-factory instead of utils * conditional require for abstract-logging * fix race in a flaky test * Update lib/fourOhFour.js Co-authored-by: Manuel Spigolon Signed-off-by: Karan Raina * Update lib/logger-factory.js Co-authored-by: Manuel Spigolon Signed-off-by: Karan Raina * Update lib/logger-utils.js Co-authored-by: Manuel Spigolon Signed-off-by: Karan Raina * refactor: move logger code out of fastify * Update lib/logger-factory.js Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Karan Raina * move from tap to node:test for unit tests * Update lib/logger-factory.js Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Karan Raina * fix test import from node test * use equal assertion in conditional logger test --------- Signed-off-by: Karan Raina Co-authored-by: Manuel Spigolon Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> --- fastify.js | 5 +- lib/fourOhFour.js | 2 +- lib/{logger.js => logger-factory.js} | 192 ++++++++++----------------- lib/logger-pino.js | 68 ++++++++++ lib/reply.js | 2 +- lib/route.js | 2 +- test/conditional-pino.test.js | 47 +++++++ test/internals/logger.test.js | 5 +- 8 files changed, 194 insertions(+), 129 deletions(-) rename lib/{logger.js => logger-factory.js} (68%) create mode 100644 lib/logger-pino.js create mode 100644 test/conditional-pino.test.js diff --git a/fastify.js b/fastify.js index 6bcf7824301..991147c6c2a 100644 --- a/fastify.js +++ b/fastify.js @@ -42,7 +42,7 @@ const decorator = require('./lib/decorate') const ContentTypeParser = require('./lib/contentTypeParser') const SchemaController = require('./lib/schema-controller') const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks') -const { createLogger, createChildLogger, defaultChildLoggerFactory } = require('./lib/logger') +const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory') const pluginUtils = require('./lib/pluginUtils') const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory') const { buildRouting, validateBodyLimitOption } = require('./lib/route') @@ -135,6 +135,7 @@ function fastify (options) { } // Instance Fastify components + const { logger, hasLogger } = createLogger(options) // Update the options with the fixed values @@ -929,7 +930,7 @@ function validateSchemaErrorFormatter (schemaErrorFormatter) { /** * These export configurations enable JS and TS developers - * to consumer fastify in whatever way best suits their needs. + * to consume fastify in whatever way best suits their needs. * Some examples of supported import syntax includes: * - `const fastify = require('fastify')` * - `const { fastify } = require('fastify')` diff --git a/lib/fourOhFour.js b/lib/fourOhFour.js index de0575475bc..41e77dfeeef 100644 --- a/lib/fourOhFour.js +++ b/lib/fourOhFour.js @@ -18,7 +18,7 @@ const { buildErrorHandler } = require('./error-handler.js') const { FST_ERR_NOT_FOUND } = require('./errors') -const { createChildLogger } = require('./logger') +const { createChildLogger } = require('./logger-factory') const { getGenReqId } = require('./reqIdGenFactory.js') /** diff --git a/lib/logger.js b/lib/logger-factory.js similarity index 68% rename from lib/logger.js rename to lib/logger-factory.js index 75c2288e972..0592275c0c9 100644 --- a/lib/logger.js +++ b/lib/logger-factory.js @@ -1,88 +1,90 @@ 'use strict' -/** - * Code imported from `pino-http` - * Repo: https://github.com/pinojs/pino-http - * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE) - */ - -const nullLogger = require('abstract-logging') -const pino = require('pino') -const { serializersSym } = pino.symbols const { - FST_ERR_LOG_INVALID_DESTINATION, - FST_ERR_LOG_INVALID_LOGGER, - FST_ERR_LOG_INVALID_LOGGER_INSTANCE, + FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED, FST_ERR_LOG_INVALID_LOGGER_CONFIG, - FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED + FST_ERR_LOG_INVALID_LOGGER_INSTANCE, + FST_ERR_LOG_INVALID_LOGGER } = require('./errors') -function createPinoLogger (opts) { - if (opts.stream && opts.file) { - throw new FST_ERR_LOG_INVALID_DESTINATION() - } else if (opts.file) { - // we do not have stream - opts.stream = pino.destination(opts.file) - delete opts.file +/** + * Utility for creating a child logger with the appropriate bindings, logger factory + * and validation. + * @param {object} context + * @param {import('../fastify').FastifyBaseLogger} logger + * @param {import('../fastify').RawRequestDefaultExpression} req + * @param {string} reqId + * @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts + * + * @returns {object} New logger instance, inheriting all parent bindings, + * with child bindings added. + */ +function createChildLogger (context, logger, req, reqId, loggerOpts) { + const loggerBindings = { + [context.requestIdLogLabel]: reqId } + const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req) - const prevLogger = opts.logger - const prevGenReqId = opts.genReqId - let logger = null - - if (prevLogger) { - opts.logger = undefined - opts.genReqId = undefined - // we need to tap into pino internals because in v5 it supports - // adding serializers in child loggers - if (prevLogger[serializersSym]) { - opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym]) - } - logger = prevLogger.child({}, opts) - opts.logger = prevLogger - opts.genReqId = prevGenReqId - } else { - logger = pino(opts, opts.stream) + // Optimization: bypass validation if the factory is our own default factory + if (context.childLoggerFactory !== defaultChildLoggerFactory) { + validateLogger(child, true) // throw if the child is not a valid logger } - return logger + return child } -const serializers = { - req: function asReqValue (req) { - return { - method: req.method, - url: req.url, - version: req.headers && req.headers['accept-version'], - host: req.host, - remoteAddress: req.ip, - remotePort: req.socket ? req.socket.remotePort : undefined - } - }, - err: pino.stdSerializers.err, - res: function asResValue (reply) { - return { - statusCode: reply.statusCode - } - } +/** Default factory to create child logger instance + * + * @param {import('../fastify.js').FastifyBaseLogger} logger + * @param {import('../types/logger.js').Bindings} bindings + * @param {import('../types/logger.js').ChildLoggerOptions} opts + * + * @returns {import('../types/logger.js').FastifyBaseLogger} + */ +function defaultChildLoggerFactory (logger, bindings, opts) { + return logger.child(bindings, opts) } -function now () { - const ts = process.hrtime() - return (ts[0] * 1e3) + (ts[1] / 1e6) +/** + * Determines if a provided logger object meets the requirements + * of a Fastify compatible logger. + * + * @param {object} logger Object to validate. + * @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing) + * + * @returns {boolean} `true` when the logger meets the requirements. + * + * @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is + * missing required methods. + */ +function validateLogger (logger, strict) { + const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child'] + const missingMethods = logger + ? methods.filter(method => !logger[method] || typeof logger[method] !== 'function') + : methods + + if (!missingMethods.length) { + return true + } else if ((missingMethods.length === methods.length) && !strict) { + return false + } else { + throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(',')) + } } function createLogger (options) { - // if no logger is provided, then create a default logger + if (options.logger && options.loggerInstance) { + throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED() + } + if (!options.loggerInstance && !options.logger) { + const nullLogger = require('abstract-logging') const logger = nullLogger logger.child = () => logger return { logger, hasLogger: false } } - if (options.logger && options.loggerInstance) { - throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED() - } + const { createPinoLogger, serializers } = require('./logger-pino.js') // check if the logger instance has all required properties if (validateLogger(options.loggerInstance)) { @@ -120,69 +122,15 @@ function createLogger (options) { return { logger, hasLogger: true } } -/** - * Determines if a provided logger object meets the requirements - * of a Fastify compatible logger. - * - * @param {object} logger Object to validate. - * @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing) - * - * @returns {boolean} `true` when the logger meets the requirements. - * - * @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is - * missing required methods. - */ -function validateLogger (logger, strict) { - const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child'] - const missingMethods = logger - ? methods.filter(method => !logger[method] || typeof logger[method] !== 'function') - : methods - - if (!missingMethods.length) { - return true - } else if ((missingMethods.length === methods.length) && !strict) { - return false - } else { - throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(',')) - } -} - -/** - * Utility for creating a child logger with the appropriate bindings, logger factory - * and validation. - * @param {object} context - * @param {import('../fastify').FastifyBaseLogger} logger - * @param {import('../fastify').RawRequestDefaultExpression} req - * @param {string} reqId - * @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts - */ -function createChildLogger (context, logger, req, reqId, loggerOpts) { - const loggerBindings = { - [context.requestIdLogLabel]: reqId - } - const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req) - - // Optimization: bypass validation if the factory is our own default factory - if (context.childLoggerFactory !== defaultChildLoggerFactory) { - validateLogger(child, true) // throw if the child is not a valid logger - } - - return child -} - -/** - * @param {import('../fastify.js').FastifyBaseLogger} logger - * @param {import('../types/logger.js').Bindings} bindings - * @param {import('../types/logger.js').ChildLoggerOptions} opts - */ -function defaultChildLoggerFactory (logger, bindings, opts) { - return logger.child(bindings, opts) +function now () { + const ts = process.hrtime() + return (ts[0] * 1e3) + (ts[1] / 1e6) } module.exports = { - createLogger, createChildLogger, defaultChildLoggerFactory, - serializers, - now + createLogger, + validateLogger, + now, } diff --git a/lib/logger-pino.js b/lib/logger-pino.js new file mode 100644 index 00000000000..d5e662b2eea --- /dev/null +++ b/lib/logger-pino.js @@ -0,0 +1,68 @@ +'use strict' + +/** + * Code imported from `pino-http` + * Repo: https://github.com/pinojs/pino-http + * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE) + */ + +const pino = require('pino') +const { serializersSym } = pino.symbols +const { + FST_ERR_LOG_INVALID_DESTINATION, +} = require('./errors') + +function createPinoLogger (opts) { + if (opts.stream && opts.file) { + throw new FST_ERR_LOG_INVALID_DESTINATION() + } else if (opts.file) { + // we do not have stream + opts.stream = pino.destination(opts.file) + delete opts.file + } + + const prevLogger = opts.logger + const prevGenReqId = opts.genReqId + let logger = null + + if (prevLogger) { + opts.logger = undefined + opts.genReqId = undefined + // we need to tap into pino internals because in v5 it supports + // adding serializers in child loggers + if (prevLogger[serializersSym]) { + opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym]) + } + logger = prevLogger.child({}, opts) + opts.logger = prevLogger + opts.genReqId = prevGenReqId + } else { + logger = pino(opts, opts.stream) + } + + return logger +} + +const serializers = { + req: function asReqValue (req) { + return { + method: req.method, + url: req.url, + version: req.headers && req.headers['accept-version'], + host: req.host, + remoteAddress: req.ip, + remotePort: req.socket ? req.socket.remotePort : undefined + } + }, + err: pino.stdSerializers.err, + res: function asResValue (reply) { + return { + statusCode: reply.statusCode + } + } +} + +module.exports = { + serializers, + createPinoLogger, +} diff --git a/lib/reply.js b/lib/reply.js index 9478a1f55fc..1148a09c5a2 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -32,7 +32,7 @@ const { } = require('./hooks') const internals = require('./handleRequest')[Symbol.for('internals')] -const loggerUtils = require('./logger') +const loggerUtils = require('./logger-factory') const now = loggerUtils.now const { handleError } = require('./error-handler') const { getSchemaSerializer } = require('./schemas') diff --git a/lib/route.js b/lib/route.js index 10c7cacafa0..d396a02d1b0 100644 --- a/lib/route.js +++ b/lib/route.js @@ -49,7 +49,7 @@ const { kRouteContext } = require('./symbols.js') const { buildErrorHandler } = require('./error-handler') -const { createChildLogger } = require('./logger') +const { createChildLogger } = require('./logger-factory.js') const { getGenReqId } = require('./reqIdGenFactory.js') function buildRouting (options) { diff --git a/test/conditional-pino.test.js b/test/conditional-pino.test.js new file mode 100644 index 00000000000..45dbc48f06b --- /dev/null +++ b/test/conditional-pino.test.js @@ -0,0 +1,47 @@ +'use strict' + +const { test } = require('node:test') + +test("pino is not require'd if logger is not passed", t => { + t.plan(1) + + const fastify = require('..') + + fastify() + + t.assert.equal(require.cache[require.resolve('pino')], undefined) +}) + +test("pino is require'd if logger is passed", t => { + t.plan(1) + + const fastify = require('..') + + fastify({ + logger: true + }) + + t.assert.notEqual(require.cache[require.resolve('pino')], undefined) +}) + +test("pino is require'd if loggerInstance is passed", t => { + t.plan(1) + + const fastify = require('..') + + const loggerInstance = { + fatal: (msg) => { }, + error: (msg) => { }, + warn: (msg) => { }, + info: (msg) => { }, + debug: (msg) => { }, + trace: (msg) => { }, + child: () => loggerInstance + } + + fastify({ + loggerInstance + }) + + t.assert.notEqual(require.cache[require.resolve('pino')], undefined) +}) diff --git a/test/internals/logger.test.js b/test/internals/logger.test.js index 412bd1114e5..3660b828c8f 100644 --- a/test/internals/logger.test.js +++ b/test/internals/logger.test.js @@ -2,7 +2,8 @@ const { test } = require('node:test') const Fastify = require('../..') -const loggerUtils = require('../../lib/logger') +const loggerUtils = require('../../lib/logger-factory') +const { serializers } = require('../../lib/logger-pino') test('time resolution', t => { t.plan(2) @@ -144,7 +145,7 @@ test('The logger should error if both stream and file destination are given', t test('The serializer prevent fails if the request socket is undefined', t => { t.plan(1) - const serialized = loggerUtils.serializers.req({ + const serialized = serializers.req({ method: 'GET', url: '/', socket: undefined, From b9bfd24c4d068375c67169d631d8c0dfb5aa2866 Mon Sep 17 00:00:00 2001 From: Krzysztof Witkowski Date: Sun, 27 Oct 2024 12:41:04 +0100 Subject: [PATCH 0794/1295] feat(test runner node): route 4 (#5774) * feat:(test porting node): ported route 5 test * feat:(test runner node) converted route 4 test * fix(redundant block): deleted * fix(blocks): deleted useless blocks * feat(test runner node): ported route 7 test --- test/route.4.test.js | 46 ++++++++-------- test/route.5.test.js | 109 ++++++++++++++++--------------------- test/route.7.test.js | 126 +++++++++++++++++++++---------------------- 3 files changed, 127 insertions(+), 154 deletions(-) diff --git a/test/route.4.test.js b/test/route.4.test.js index 882cca09981..38d48de8488 100644 --- a/test/route.4.test.js +++ b/test/route.4.test.js @@ -1,21 +1,20 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') -test('route error handler overrides global custom error handler', t => { - t.plan(4) +test('route error handler overrides global custom error handler', async t => { + t.plan(3) const fastify = Fastify() const customGlobalErrorHandler = (error, request, reply) => { - t.error(error) + t.assert.ifError(error) reply.code(429).send({ message: 'Too much coffee' }) } const customRouteErrorHandler = (error, request, reply) => { - t.equal(error.message, 'Wrong Pot Error') + t.assert.strictEqual(error.message, 'Wrong Pot Error') reply.code(418).send({ message: 'Make a brew', statusCode: 418, @@ -34,17 +33,15 @@ test('route error handler overrides global custom error handler', t => { errorHandler: customRouteErrorHandler }) - fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same(JSON.parse(res.payload), { - message: 'Make a brew', - statusCode: 418, - error: 'Wrong Pot Error' - }) + }) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' }) }) @@ -61,7 +58,7 @@ test('throws when route with empty url', async t => { } }) } catch (err) { - t.equal(err.message, 'The path could not be empty') + t.assert.strictEqual(err.message, 'The path could not be empty') } }) @@ -75,7 +72,7 @@ test('throws when route with empty url in shorthand declaration', async t => { async function handler () { return {} } ) } catch (err) { - t.equal(err.message, 'The path could not be empty') + t.assert.strictEqual(err.message, 'The path could not be empty') } }) @@ -94,19 +91,19 @@ test('throws when route-level error handler is not a function', t => { errorHandler: 'teapot' }) } catch (err) { - t.equal(err.message, 'Error Handler for GET:/tea route, if defined, must be a function') + t.assert.strictEqual(err.message, 'Error Handler for GET:/tea route, if defined, must be a function') } }) -test('route child logger factory overrides default child logger factory', t => { - t.plan(3) +test('route child logger factory overrides default child logger factory', async t => { + t.plan(2) const fastify = Fastify() const customRouteChildLogger = (logger, bindings, opts, req) => { const child = logger.child(bindings, opts) child.customLog = function (message) { - t.equal(message, 'custom') + t.assert.strictEqual(message, 'custom') } return child } @@ -121,11 +118,10 @@ test('route child logger factory overrides default child logger factory', t => { childLoggerFactory: customRouteChildLogger }) - fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) }) + + t.assert.strictEqual(res.statusCode, 200) }) diff --git a/test/route.5.test.js b/test/route.5.test.js index a2132081647..edcf7712e6e 100644 --- a/test/route.5.test.js +++ b/test/route.5.test.js @@ -1,18 +1,17 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') -test('route child logger factory does not affect other routes', t => { - t.plan(6) +test('route child logger factory does not affect other routes', async t => { + t.plan(4) const fastify = Fastify() const customRouteChildLogger = (logger, bindings, opts, req) => { const child = logger.child(bindings, opts) child.customLog = function (message) { - t.equal(message, 'custom') + t.assert.strictEqual(message, 'custom') } return child } @@ -31,42 +30,39 @@ test('route child logger factory does not affect other routes', t => { method: 'GET', path: '/tea', handler: (req, res) => { - t.notMatch(req.log.customLog instanceof Function) + t.assert.ok(req.log.customLog instanceof Function) res.send() } }) - fastify.inject({ + let res = await fastify.inject({ method: 'GET', url: '/coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) }) - fastify.inject({ + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ method: 'GET', url: '/tea' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) }) + t.assert.strictEqual(res.statusCode, 200) }) -test('route child logger factory overrides global custom error handler', t => { - t.plan(6) +test('route child logger factory overrides global custom error handler', async t => { + t.plan(4) const fastify = Fastify() const customGlobalChildLogger = (logger, bindings, opts, req) => { const child = logger.child(bindings, opts) child.globalLog = function (message) { - t.equal(message, 'global') + t.assert.strictEqual(message, 'global') } return child } const customRouteChildLogger = (logger, bindings, opts, req) => { const child = logger.child(bindings, opts) child.customLog = function (message) { - t.equal(message, 'custom') + t.assert.strictEqual(message, 'custom') } return child } @@ -91,24 +87,21 @@ test('route child logger factory overrides global custom error handler', t => { } }) - fastify.inject({ + let res = await fastify.inject({ method: 'GET', url: '/coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) }) - fastify.inject({ + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ method: 'GET', url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) }) + t.assert.strictEqual(res.statusCode, 200) }) -test('Creates a HEAD route for each GET one (default)', t => { - t.plan(8) +test('Creates a HEAD route for each GET one (default)', async t => { + t.plan(6) const fastify = Fastify() @@ -128,29 +121,25 @@ test('Creates a HEAD route for each GET one (default)', t => { } }) - fastify.inject({ + let res = await fastify.inject({ method: 'HEAD', url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.body, '') - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/some-light' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') - t.equal(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(res.body, '') }) -test('Do not create a HEAD route for each GET one (exposeHeadRoutes: false)', t => { - t.plan(4) +test('Do not create a HEAD route for each GET one (exposeHeadRoutes: false)', async t => { + t.plan(2) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -170,25 +159,21 @@ test('Do not create a HEAD route for each GET one (exposeHeadRoutes: false)', t } }) - fastify.inject({ + let res = await fastify.inject({ method: 'HEAD', url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) }) + t.assert.strictEqual(res.statusCode, 404) - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/some-light' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) }) + t.assert.strictEqual(res.statusCode, 404) }) -test('Creates a HEAD route for each GET one', t => { - t.plan(8) +test('Creates a HEAD route for each GET one', async t => { + t.plan(6) const fastify = Fastify({ exposeHeadRoutes: true }) @@ -208,23 +193,19 @@ test('Creates a HEAD route for each GET one', t => { } }) - fastify.inject({ + let res = await fastify.inject({ method: 'HEAD', url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.body, '') - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/some-light' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') - t.equal(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(res.body, '') }) diff --git a/test/route.7.test.js b/test/route.7.test.js index c437f160845..333f4d8e4a2 100644 --- a/test/route.7.test.js +++ b/test/route.7.test.js @@ -2,11 +2,11 @@ const stream = require('node:stream') const split = require('split2') -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') +const createError = require('@fastify/error') -test("HEAD route should handle stream.on('error')", t => { +test("HEAD route should handle stream.on('error')", (t, done) => { t.plan(6) const resStream = stream.Readable.from('Hello with error!') @@ -31,23 +31,24 @@ test("HEAD route should handle stream.on('error')", t => { logStream.once('data', line => { const { message, stack } = expectedError - t.same(line.err, { type: 'Error', message, stack }) - t.equal(line.msg, 'Error on Stream found for HEAD route') - t.equal(line.level, 50) + t.assert.deepStrictEqual(line.err, { type: 'Error', message, stack }) + t.assert.strictEqual(line.msg, 'Error on Stream found for HEAD route') + t.assert.strictEqual(line.level, 50) }) fastify.inject({ method: 'HEAD', url: '/more-coffee' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], undefined) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + done() }) }) -test('HEAD route should be exposed by default', t => { - t.plan(7) +test('HEAD route should be exposed by default', async t => { + t.plan(5) const resStream = stream.Readable.from('Hello with error!') const resJson = { hello: 'world' } @@ -70,28 +71,24 @@ test('HEAD route should be exposed by default', t => { } }) - fastify.inject({ + let res = await fastify.inject({ method: 'HEAD', url: '/without-flag' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) }) + t.assert.strictEqual(res.statusCode, 200) - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/with-flag' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJson))}`) - t.equal(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.strictEqual(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJson))}`) + t.assert.strictEqual(res.body, '') }) -test('HEAD route should be exposed if route exposeHeadRoute is set', t => { - t.plan(7) +test('HEAD route should be exposed if route exposeHeadRoute is set', async t => { + t.plan(5) const resBuffer = Buffer.from('I am a coffee!') const resJson = { hello: 'world' } @@ -114,27 +111,23 @@ test('HEAD route should be exposed if route exposeHeadRoute is set', t => { } }) - fastify.inject({ + let res = await fastify.inject({ method: 'HEAD', url: '/one' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/two' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) }) + t.assert.strictEqual(res.statusCode, 404) }) -test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (global)', t => { +test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (global)', (t, done) => { t.plan(6) const resBuffer = Buffer.from('I am a coffee!') @@ -165,16 +158,17 @@ test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes method: 'HEAD', url: '/one' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/pdf') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.headers['x-custom-header'], 'some-custom-header') - t.equal(res.body, '') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/pdf') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.headers['x-custom-header'], 'some-custom-header') + t.assert.strictEqual(res.body, '') + done() }) }) -test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', t => { +test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', (t, done) => { t.plan(6) const fastify = Fastify() @@ -205,16 +199,17 @@ test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes method: 'HEAD', url: '/one' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/pdf') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.headers['x-custom-header'], 'some-custom-header') - t.equal(res.body, '') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/pdf') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.headers['x-custom-header'], 'some-custom-header') + t.assert.strictEqual(res.body, '') + done() }) }) -test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'no-slash\'', t => { +test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'no-slash\'', (t, done) => { t.plan(2) const fastify = Fastify() @@ -234,8 +229,9 @@ test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: }, { prefix: '/prefix' }) fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) @@ -262,9 +258,9 @@ test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: const trailingSlashReply = await fastify.inject({ url: '/prefix/', method: 'HEAD' }) const noneTrailingReply = await fastify.inject({ url: '/prefix', method: 'HEAD' }) - t.equal(doublePrefixReply.statusCode, 404) - t.equal(trailingSlashReply.statusCode, 200) - t.equal(noneTrailingReply.statusCode, 200) + t.assert.strictEqual(doublePrefixReply.statusCode, 404) + t.assert.strictEqual(trailingSlashReply.statusCode, 200) + t.assert.strictEqual(noneTrailingReply.statusCode, 200) }) test('GET route with body schema should throw', t => { @@ -272,7 +268,7 @@ test('GET route with body schema should throw', t => { const fastify = Fastify() - t.throws(() => { + t.assert.throws(() => { fastify.route({ method: 'GET', path: '/get', @@ -283,7 +279,7 @@ test('GET route with body schema should throw', t => { reply.send({ hello: 'world' }) } }) - }, new Error('Body validation schema for GET:/get route is not supported!')) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for GET:/get route is not supported!')()) }) test('HEAD route with body schema should throw', t => { @@ -291,7 +287,7 @@ test('HEAD route with body schema should throw', t => { const fastify = Fastify() - t.throws(() => { + t.assert.throws(() => { fastify.route({ method: 'HEAD', path: '/shouldThrow', @@ -302,7 +298,7 @@ test('HEAD route with body schema should throw', t => { reply.send({ hello: 'world' }) } }) - }, new Error('Body validation schema for HEAD:/shouldThrow route is not supported!')) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrow route is not supported!')()) }) test('[HEAD, GET] route with body schema should throw', t => { @@ -310,7 +306,7 @@ test('[HEAD, GET] route with body schema should throw', t => { const fastify = Fastify() - t.throws(() => { + t.assert.throws(() => { fastify.route({ method: ['HEAD', 'GET'], path: '/shouldThrowHead', @@ -321,7 +317,7 @@ test('[HEAD, GET] route with body schema should throw', t => { reply.send({ hello: 'world' }) } }) - }, new Error('Body validation schema for HEAD:/shouldThrowHead route is not supported!')) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrowHead route is not supported!')()) }) test('GET route with body schema should throw - shorthand', t => { @@ -329,7 +325,7 @@ test('GET route with body schema should throw - shorthand', t => { const fastify = Fastify() - t.throws(() => { + t.assert.throws(() => { fastify.get('/shouldThrow', { schema: { body: {} @@ -339,7 +335,7 @@ test('GET route with body schema should throw - shorthand', t => { reply.send({ hello: 'world' }) } ) - }, new Error('Body validation schema for GET:/shouldThrow route is not supported!')) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for GET:/shouldThrow route is not supported!')()) }) test('HEAD route with body schema should throw - shorthand', t => { @@ -347,7 +343,7 @@ test('HEAD route with body schema should throw - shorthand', t => { const fastify = Fastify() - t.throws(() => { + t.assert.throws(() => { fastify.head('/shouldThrow2', { schema: { body: {} @@ -357,5 +353,5 @@ test('HEAD route with body schema should throw - shorthand', t => { reply.send({ hello: 'world' }) } ) - }, new Error('Body validation schema for HEAD:/shouldThrow2 route is not supported!')) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrow2 route is not supported!')()) }) From 5927ca7957658c108d9fe4fdb06caef2aa0ddb90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giovanni=20Lagan=C3=A0?= Date: Sun, 27 Oct 2024 19:15:56 +0100 Subject: [PATCH 0795/1295] test: migrated custom-http-server from tap to node:test (#5773) * chore: migrated custom-http-server from tap to node:test * fix: removed leftover --- test/custom-http-server.test.js | 36 +++++++++++++++------------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/test/custom-http-server.test.js b/test/custom-http-server.test.js index f981b2080c5..fc638edbc0d 100644 --- a/test/custom-http-server.test.js +++ b/test/custom-http-server.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const http = require('node:http') const dns = require('node:dns').promises const sget = require('simple-get').concat @@ -16,7 +15,7 @@ async function setup () { const fastify = Fastify({ serverFactory: (handler, opts) => { - t.ok(opts.serverFactory, 'it is called once for localhost') + t.assert.ok(opts.serverFactory, 'it is called once for localhost') const server = http.createServer((req, res) => { req.custom = true @@ -27,10 +26,9 @@ async function setup () { } }) - t.teardown(fastify.close.bind(fastify)) - + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { - t.ok(req.raw.custom) + t.assert.ok(req.raw.custom) reply.send({ hello: 'world' }) }) @@ -45,8 +43,8 @@ async function setup () { if (err) { return reject(err) } - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) resolve() }) }) @@ -55,7 +53,7 @@ async function setup () { test('Should not allow forceCloseConnection=idle if the server does not support closeIdleConnections', t => { t.plan(1) - t.throws( + t.assert.throws( () => { Fastify({ forceCloseConnections: 'idle', @@ -75,13 +73,11 @@ async function setup () { test('Should accept user defined serverFactory and ignore secondary server creation', async t => { const server = http.createServer(() => { }) - t.teardown(() => new Promise(resolve => server.close(resolve))) - const app = await Fastify({ + t.after(() => new Promise(resolve => server.close(resolve))) + const app = Fastify({ serverFactory: () => server }) - t.resolves(async () => { - await app.listen({ port: 0 }) - }) + await t.assert.doesNotReject(async () => { await app.listen({ port: 0 }) }) }) test('Should not call close on the server if it has not created it', async t => { @@ -107,12 +103,12 @@ async function setup () { }) const address = server.address() - t.equal(server.listening, true) + t.assert.strictEqual(server.listening, true) await fastify.close() - t.equal(server.listening, true) - t.same(server.address(), address) - t.same(fastify.addresses(), [address]) + t.assert.strictEqual(server.listening, true) + t.assert.deepStrictEqual(server.address(), address) + t.assert.deepStrictEqual(fastify.addresses(), [address]) await new Promise((resolve, reject) => { server.close((err) => { @@ -122,8 +118,8 @@ async function setup () { resolve() }) }) - t.equal(server.listening, false) - t.same(server.address(), null) + t.assert.strictEqual(server.listening, false) + t.assert.deepStrictEqual(server.address(), null) }) } From 5eadeefac887d8624b6c73a2248ba35c06ca0a27 Mon Sep 17 00:00:00 2001 From: Krzysztof Witkowski Date: Sun, 27 Oct 2024 21:37:48 +0100 Subject: [PATCH 0796/1295] test: migrate route.1.test to node test runner (#5784) --- test/route.1.test.js | 151 ++++++++++++++++++++++--------------------- test/route.2.test.js | 33 +++++----- 2 files changed, 96 insertions(+), 88 deletions(-) diff --git a/test/route.1.test.js b/test/route.1.test.js index 5ca84a9cc6a..6c568e3e5c1 100644 --- a/test/route.1.test.js +++ b/test/route.1.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('..') const { @@ -10,15 +9,14 @@ const { } = require('../lib/errors') const { getServerUrl } = require('./helper') -test('route', t => { +test('route', async t => { t.plan(10) - const test = t.test - test('route - get', t => { + await t.test('route - get', (t, done) => { t.plan(4) const fastify = Fastify() - t.doesNotThrow(() => + t.assert.doesNotThrow(() => fastify.route({ method: 'GET', url: '/', @@ -41,24 +39,25 @@ test('route', t => { ) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) - test('missing schema - route', t => { + await t.test('missing schema - route', (t, done) => { t.plan(4) const fastify = Fastify() - t.doesNotThrow(() => + t.assert.doesNotThrow(() => fastify.route({ method: 'GET', url: '/missing', @@ -69,31 +68,32 @@ test('route', t => { ) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/missing' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) - test('invalid handler attribute - route', t => { + await t.test('invalid handler attribute - route', t => { t.plan(1) const fastify = Fastify() - t.throws(() => fastify.get('/', { handler: 'not a function' }, () => { })) + t.assert.throws(() => fastify.get('/', { handler: 'not a function' }, () => { })) }) - test('Add Multiple methods per route all uppercase', t => { + await t.test('Add Multiple methods per route all uppercase', (t, done) => { t.plan(7) const fastify = Fastify() - t.doesNotThrow(() => + t.assert.doesNotThrow(() => fastify.route({ method: ['GET', 'DELETE'], url: '/multiple', @@ -103,33 +103,34 @@ test('route', t => { })) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/multiple' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) sget({ method: 'DELETE', url: getServerUrl(fastify) + '/multiple' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) - test('Add Multiple methods per route all lowercase', t => { + await t.test('Add Multiple methods per route all lowercase', (t, done) => { t.plan(7) const fastify = Fastify() - t.doesNotThrow(() => + t.assert.doesNotThrow(() => fastify.route({ method: ['get', 'delete'], url: '/multiple', @@ -139,33 +140,34 @@ test('route', t => { })) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/multiple' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) sget({ method: 'DELETE', url: getServerUrl(fastify) + '/multiple' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) - test('Add Multiple methods per route mixed uppercase and lowercase', t => { + await t.test('Add Multiple methods per route mixed uppercase and lowercase', (t, done) => { t.plan(7) const fastify = Fastify() - t.doesNotThrow(() => + t.assert.doesNotThrow(() => fastify.route({ method: ['GET', 'delete'], url: '/multiple', @@ -175,33 +177,34 @@ test('route', t => { })) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: getServerUrl(fastify) + '/multiple' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) sget({ method: 'DELETE', url: getServerUrl(fastify) + '/multiple' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) - test('Add invalid Multiple methods per route', t => { + t.test('Add invalid Multiple methods per route', t => { t.plan(1) const fastify = Fastify() - t.throws(() => + t.assert.throws(() => fastify.route({ method: ['GET', 1], url: '/invalid-method', @@ -211,11 +214,11 @@ test('route', t => { }), new FST_ERR_ROUTE_METHOD_INVALID()) }) - test('Add method', t => { + await t.test('Add method', t => { t.plan(1) const fastify = Fastify() - t.throws(() => + t.assert.throws(() => fastify.route({ method: 1, url: '/invalid-method', @@ -225,11 +228,11 @@ test('route', t => { }), new FST_ERR_ROUTE_METHOD_INVALID()) }) - test('Add additional multiple methods to existing route', t => { + await t.test('Add additional multiple methods to existing route', (t, done) => { t.plan(7) const fastify = Fastify() - t.doesNotThrow(() => { + t.assert.doesNotThrow(() => { fastify.get('/add-multiple', function (req, reply) { reply.send({ hello: 'Bob!' }) }) @@ -243,49 +246,52 @@ test('route', t => { }) fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'PUT', url: getServerUrl(fastify) + '/add-multiple' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) sget({ method: 'DELETE', url: getServerUrl(fastify) + '/add-multiple' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) - test('cannot add another route after binding', t => { + await t.test('cannot add another route after binding', (t, done) => { t.plan(1) const fastify = Fastify() fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) - t.throws(() => fastify.route({ + t.assert.throws(() => fastify.route({ method: 'GET', url: '/another-get-route', handler: function (req, reply) { reply.send({ hello: 'world' }) } }), new FST_ERR_INSTANCE_ALREADY_LISTENING('Cannot add route!')) + + done() }) }) }) -test('invalid schema - route', t => { +test('invalid schema - route', (t, done) => { t.plan(3) const fastify = Fastify() @@ -300,10 +306,11 @@ test('invalid schema - route', t => { } }) fastify.after(err => { - t.notOk(err, 'the error is throw on preReady') + t.assert.ok(!err, 'the error is throw on preReady') }) fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.match(err.message, /Failed building the validation schema for GET: \/invalid/) + t.assert.strictEqual(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.assert.match(err.message, /Failed building the validation schema for GET: \/invalid/) + done() }) }) diff --git a/test/route.2.test.js b/test/route.2.test.js index 1175784c2f7..f5d305e0712 100644 --- a/test/route.2.test.js +++ b/test/route.2.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../fastify') test('same route definition object on multiple prefixes', async t => { @@ -17,13 +16,13 @@ test('same route definition object on multiple prefixes', async t => { fastify.register(async function (f) { f.addHook('onRoute', (routeOptions) => { - t.equal(routeOptions.url, '/v1/simple') + t.assert.strictEqual(routeOptions.url, '/v1/simple') }) f.route(routeObject) }, { prefix: '/v1' }) fastify.register(async function (f) { f.addHook('onRoute', (routeOptions) => { - t.equal(routeOptions.url, '/v2/simple') + t.assert.strictEqual(routeOptions.url, '/v2/simple') }) f.route(routeObject) }, { prefix: '/v2' }) @@ -31,7 +30,7 @@ test('same route definition object on multiple prefixes', async t => { await fastify.ready() }) -test('path can be specified in place of uri', t => { +test('path can be specified in place of uri', (t, done) => { t.plan(3) const fastify = Fastify() @@ -49,9 +48,10 @@ test('path can be specified in place of uri', t => { } fastify.inject(reqOpts, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + done() }) }) @@ -65,20 +65,20 @@ test('invalid bodyLimit option - route', t => { method: 'PUT', handler: () => null }) - t.fail('bodyLimit must be an integer') + t.assert.fail('bodyLimit must be an integer') } catch (err) { - t.equal(err.message, "'bodyLimit' option must be an integer > 0. Got 'false'") + t.assert.strictEqual(err.message, "'bodyLimit' option must be an integer > 0. Got 'false'") } try { fastify.post('/url', { bodyLimit: 10000.1 }, () => null) - t.fail('bodyLimit must be an integer') + t.assert.fail('bodyLimit must be an integer') } catch (err) { - t.equal(err.message, "'bodyLimit' option must be an integer > 0. Got '10000.1'") + t.assert.strictEqual(err.message, "'bodyLimit' option must be an integer > 0. Got '10000.1'") } }) -test('handler function in options of shorthand route should works correctly', t => { +test('handler function in options of shorthand route should works correctly', (t, done) => { t.plan(3) const fastify = Fastify() @@ -92,8 +92,9 @@ test('handler function in options of shorthand route should works correctly', t method: 'GET', url: '/foo' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + done() }) }) From 1124b47a4211320bfbff95ee5d77009a5cd8f629 Mon Sep 17 00:00:00 2001 From: Roberto Stagi <34033081+rstagi@users.noreply.github.com> Date: Sun, 27 Oct 2024 21:45:44 +0100 Subject: [PATCH 0797/1295] test: port bodyLimit.test.js (#5781) * test: port bodyLimit.test.js * test: rename bodyLimit to body-limit * test: deepEqual -> deepStrictEqual --------- Co-authored-by: Aras Abbasi --- .../{bodyLimit.test.js => body-limit.test.js} | 119 +++++++++--------- 1 file changed, 61 insertions(+), 58 deletions(-) rename test/{bodyLimit.test.js => body-limit.test.js} (59%) diff --git a/test/bodyLimit.test.js b/test/body-limit.test.js similarity index 59% rename from test/bodyLimit.test.js rename to test/body-limit.test.js index a5eb0cafd6f..108c6bc4411 100644 --- a/test/bodyLimit.test.js +++ b/test/body-limit.test.js @@ -1,26 +1,25 @@ 'use strict' -const Fastify = require('..') +const Fastify = require('../fastify') const sget = require('simple-get').concat const zlib = require('node:zlib') -const t = require('tap') -const test = t.test +const { test } = require('node:test') -test('bodyLimit', t => { +test('bodyLimit', (t, done) => { t.plan(5) try { Fastify({ bodyLimit: 1.3 }) - t.fail('option must be an integer') + t.assert.fail('option must be an integer') } catch (err) { - t.ok(err) + t.assert.ok(err) } try { Fastify({ bodyLimit: [] }) - t.fail('option must be an integer') + t.assert.fail('option must be an integer') } catch (err) { - t.ok(err) + t.assert.ok(err) } const fastify = Fastify({ bodyLimit: 1 }) @@ -30,8 +29,8 @@ test('bodyLimit', t => { }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'POST', @@ -40,14 +39,15 @@ test('bodyLimit', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 413) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 413) + done() }) }) }) -test('bodyLimit is applied to decoded content', t => { - t.plan(9) +test('bodyLimit is applied to decoded content', async (t) => { + t.plan(6) const body = { x: 'x'.repeat(30000) } const json = JSON.stringify(body) @@ -56,7 +56,7 @@ test('bodyLimit is applied to decoded content', t => { const fastify = Fastify() fastify.addHook('preParsing', async (req, reply, payload) => { - t.equal(req.headers['content-length'], `${encoded.length}`) + t.assert.strictEqual(req.headers['content-length'], `${encoded.length}`) const unzip = zlib.createGunzip() Object.defineProperty(unzip, 'receivedEncodedLength', { get () { @@ -79,53 +79,53 @@ test('bodyLimit is applied to decoded content', t => { fastify.post('/body-limit-20k', { bodyLimit: 20000, onError: async (req, res, err) => { - t.equal(err.code, 'FST_ERR_CTP_BODY_TOO_LARGE') - t.equal(err.statusCode, 413) + t.assert.strictEqual(err.code, 'FST_ERR_CTP_BODY_TOO_LARGE') + t.assert.strictEqual(err.statusCode, 413) } }, (request, reply) => { reply.send({ x: 'handler should not be called' }) }) - fastify.inject({ - method: 'POST', - url: '/body-limit-40k', - headers: { - 'content-encoding': 'gzip', - 'content-type': 'application/json' - }, - payload: encoded - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), body) + await t.test('bodyLimit 40k', async (t) => { + const result = await fastify.inject({ + method: 'POST', + url: '/body-limit-40k', + headers: { + 'content-encoding': 'gzip', + 'content-type': 'application/json' + }, + payload: encoded + }) + t.assert.strictEqual(result.statusCode, 200) + t.assert.deepStrictEqual(result.json(), body) }) - fastify.inject({ - method: 'POST', - url: '/body-limit-20k', - headers: { - 'content-encoding': 'gzip', - 'content-type': 'application/json' - }, - payload: encoded - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 413) + await t.test('bodyLimit 20k', async (t) => { + const result = await fastify.inject({ + method: 'POST', + url: '/body-limit-20k', + headers: { + 'content-encoding': 'gzip', + 'content-type': 'application/json' + }, + payload: encoded + }) + t.assert.strictEqual(result.statusCode, 413) }) }) -test('default request.routeOptions.bodyLimit should be 1048576', t => { +test('default request.routeOptions.bodyLimit should be 1048576', (t, done) => { t.plan(4) const fastify = Fastify() fastify.post('/default-bodylimit', { handler (request, reply) { - t.equal(1048576, request.routeOptions.bodyLimit) + t.assert.strictEqual(1048576, request.routeOptions.bodyLimit) reply.send({ }) } }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'POST', @@ -134,25 +134,26 @@ test('default request.routeOptions.bodyLimit should be 1048576', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('request.routeOptions.bodyLimit should be equal to route limit', t => { +test('request.routeOptions.bodyLimit should be equal to route limit', (t, done) => { t.plan(4) const fastify = Fastify({ bodyLimit: 1 }) fastify.post('/route-limit', { bodyLimit: 1000, handler (request, reply) { - t.equal(1000, request.routeOptions.bodyLimit) + t.assert.strictEqual(1000, request.routeOptions.bodyLimit) reply.send({}) } }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'POST', @@ -161,24 +162,25 @@ test('request.routeOptions.bodyLimit should be equal to route limit', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('request.routeOptions.bodyLimit should be equal to server limit', t => { +test('request.routeOptions.bodyLimit should be equal to server limit', (t, done) => { t.plan(4) const fastify = Fastify({ bodyLimit: 100 }) fastify.post('/server-limit', { handler (request, reply) { - t.equal(100, request.routeOptions.bodyLimit) + t.assert.strictEqual(100, request.routeOptions.bodyLimit) reply.send({}) } }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'POST', @@ -187,8 +189,9 @@ test('request.routeOptions.bodyLimit should be equal to server limit', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) From 90e2e8ad32d47b47a554c6719faf31d4bc02f4a9 Mon Sep 17 00:00:00 2001 From: Pietro Marchini Date: Sun, 27 Oct 2024 21:53:06 +0100 Subject: [PATCH 0798/1295] test: port server.test.js to node:test (#5783) --- test/server.test.js | 137 ++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 70 deletions(-) diff --git a/test/server.test.js b/test/server.test.js index 745ce4391a2..b3f3b361060 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -1,115 +1,111 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat const undici = require('undici') -test('listen should accept null port', t => { - t.plan(1) - +test('listen should accept null port', async t => { const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: null }, (err) => { - t.error(err) - }) -}) + t.after(() => fastify.close()) -test('listen should accept undefined port', t => { - t.plan(1) + await t.assert.doesNotReject( + fastify.listen({ port: null }) + ) +}) +test('listen should accept undefined port', async t => { const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: undefined }, (err) => { - t.error(err) - }) -}) + t.after(() => fastify.close()) -test('listen should accept stringified number port', t => { - t.plan(1) + await t.assert.doesNotReject( + fastify.listen({ port: undefined }) + ) +}) +test('listen should accept stringified number port', async t => { const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: '1234' }, (err) => { - t.error(err) - }) -}) + t.after(() => fastify.close()) -test('listen should accept log text resolution function', t => { - t.plan(3) + await t.assert.doesNotReject( + fastify.listen({ port: '1234' }) + ) +}) +test('listen should accept log text resolution function', async t => { const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ - host: '127.0.0.1', - port: '1234', - listenTextResolver: (address) => { - t.equal(address, 'http://127.0.0.1:1234') - t.pass('executed') - return 'hardcoded text' - } - }, (err) => { - t.error(err) - }) + t.after(() => fastify.close()) + + await t.assert.doesNotReject( + fastify.listen({ + host: '127.0.0.1', + port: '1234', + listenTextResolver: (address) => { + t.assert.strictEqual(address, 'http://127.0.0.1:1234') + return 'hardcoded text' + } + }) + ) }) test('listen should reject string port', async (t) => { - t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) try { await fastify.listen({ port: 'hello-world' }) } catch (error) { - t.equal(error.code, 'ERR_SOCKET_BAD_PORT') + t.assert.strictEqual(error.code, 'ERR_SOCKET_BAD_PORT') } try { await fastify.listen({ port: '1234hello' }) } catch (error) { - t.equal(error.code, 'ERR_SOCKET_BAD_PORT') + t.assert.strictEqual(error.code, 'ERR_SOCKET_BAD_PORT') } }) -test('Test for hostname and port', t => { +test('Test for hostname and port', (t, end) => { const app = Fastify() - t.teardown(app.close.bind(app)) + t.after(() => app.close()) app.get('/host', (req, res) => { const host = 'localhost:8000' - t.equal(req.host, host) - t.equal(req.hostname, req.host.split(':')[0]) - t.equal(req.port, Number(req.host.split(':')[1])) + t.assert.strictEqual(req.host, host) + t.assert.strictEqual(req.hostname, req.host.split(':')[0]) + t.assert.strictEqual(req.port, Number(req.host.split(':')[1])) res.send('ok') }) + app.listen({ port: 8000 }, () => { - sget('http://localhost:8000/host', () => { t.end() }) + sget('http://localhost:8000/host', () => { end() }) }) }) -test('abort signal', t => { - t.test('listen should not start server', t => { +test('abort signal', async t => { + await t.test('listen should not start server', (t, end) => { t.plan(2) function onClose (instance, done) { - t.type(fastify, instance) + t.assert.equal(instance, fastify) done() + end() } const controller = new AbortController() const fastify = Fastify() fastify.addHook('onClose', onClose) fastify.listen({ port: 1234, signal: controller.signal }, (err) => { - t.error(err) + t.assert.ifError(err) }) controller.abort() - t.equal(fastify.server.listening, false) + t.assert.strictEqual(fastify.server.listening, false) }) - t.test('listen should not start server if already aborted', t => { + await t.test('listen should not start server if already aborted', (t, end) => { t.plan(2) function onClose (instance, done) { - t.type(fastify, instance) + t.assert.equal(instance, fastify) done() + end() } const controller = new AbortController() @@ -117,34 +113,32 @@ test('abort signal', t => { const fastify = Fastify() fastify.addHook('onClose', onClose) fastify.listen({ port: 1234, signal: controller.signal }, (err) => { - t.error(err) + t.assert.ifError(err) }) - t.equal(fastify.server.listening, false) + t.assert.strictEqual(fastify.server.listening, false) }) - t.test('listen should throw if received invalid signal', t => { + await t.test('listen should throw if received invalid signal', t => { t.plan(2) const fastify = Fastify() try { fastify.listen({ port: 1234, signal: {} }, (err) => { - t.error(err) + t.assert.ifError(err) }) - t.fail() + t.assert.fail('should throw') } catch (e) { - t.equal(e.code, 'FST_ERR_LISTEN_OPTIONS_INVALID') - t.equal(e.message, 'Invalid listen options: \'Invalid options.signal\'') + t.assert.strictEqual(e.code, 'FST_ERR_LISTEN_OPTIONS_INVALID') + t.assert.strictEqual(e.message, 'Invalid listen options: \'Invalid options.signal\'') } }) - - t.end() }) -t.test('#5180 - preClose should be called before closing secondary server', t => { +test('#5180 - preClose should be called before closing secondary server', (t, end) => { t.plan(2) const fastify = Fastify({ forceCloseConnections: true }) let flag = false - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.addHook('preClose', async () => { flag = true @@ -159,7 +153,7 @@ t.test('#5180 - preClose should be called before closing secondary server', t => }) fastify.listen({ port: 0 }, (err) => { - t.error(err) + t.assert.ifError(err) const addresses = fastify.addresses() const mainServerAddress = fastify.server.address() let secondaryAddress @@ -174,14 +168,17 @@ t.test('#5180 - preClose should be called before closing secondary server', t => } if (!secondaryAddress) { - t.pass('no secondary server') + t.assert.ok(true, 'Secondary address not found') return } undici.request(`http://${secondaryAddress.address}:${secondaryAddress.port}/`) .then( - () => { t.fail('Request should not succeed') }, - () => { t.ok(flag) } + () => { t.assert.fail('Request should not succeed') }, + () => { + t.assert.ok(flag) + end() + } ) setTimeout(() => { From 04cf30e8cdde85233eb3843ea3c753fcee50603f Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 28 Oct 2024 11:20:13 +0100 Subject: [PATCH 0799/1295] test: migrated imports.test.js from tap to node:test (#5788) --- test/imports.test.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/imports.test.js b/test/imports.test.js index 791cc7117fc..d33e01168d1 100644 --- a/test/imports.test.js +++ b/test/imports.test.js @@ -1,18 +1,17 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') test('should import as default', t => { t.plan(2) const fastify = require('..') - t.ok(fastify) - t.equal(typeof fastify, 'function') + t.assert.ok(fastify) + t.assert.equal(typeof fastify, 'function') }) test('should import as esm', t => { t.plan(2) const { fastify } = require('..') - t.ok(fastify) - t.equal(typeof fastify, 'function') + t.assert.ok(fastify) + t.assert.equal(typeof fastify, 'function') }) From 9bcd483bcfaac0ca97532eff3778796e7b8415e7 Mon Sep 17 00:00:00 2001 From: Ran Toledo Date: Mon, 28 Oct 2024 14:07:53 +0200 Subject: [PATCH 0800/1295] migrate-als-and-async-await-tests-to-node-test (#5791) --- test/als.test.js | 93 ++++++------- test/async-await.test.js | 282 ++++++++++++++++++++------------------- 2 files changed, 196 insertions(+), 179 deletions(-) diff --git a/test/als.test.js b/test/als.test.js index 37102b11a9f..91a78e8cff4 100644 --- a/test/als.test.js +++ b/test/als.test.js @@ -1,50 +1,40 @@ 'use strict' const { AsyncLocalStorage } = require('node:async_hooks') -const t = require('tap') +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat -if (!AsyncLocalStorage) { - t.skip('AsyncLocalStorage not available, skipping test') - process.exit(0) -} +test('Async Local Storage test', (t, done) => { + t.plan(13) + if (!AsyncLocalStorage) { + t.skip('AsyncLocalStorage not available, skipping test') + process.exit(0) + } -const storage = new AsyncLocalStorage() -const app = Fastify({ logger: false }) + const storage = new AsyncLocalStorage() + const app = Fastify({ logger: false }) -let counter = 0 -app.addHook('onRequest', (req, reply, next) => { - const id = counter++ - storage.run({ id }, next) -}) - -app.get('/', function (request, reply) { - t.ok(storage.getStore()) - const id = storage.getStore().id - reply.send({ id }) -}) + let counter = 0 + app.addHook('onRequest', (req, reply, next) => { + const id = counter++ + storage.run({ id }, next) + }) -app.post('/', function (request, reply) { - t.ok(storage.getStore()) - const id = storage.getStore().id - reply.send({ id }) -}) + app.get('/', function (request, reply) { + t.assert.ok(storage.getStore()) + const id = storage.getStore().id + reply.send({ id }) + }) -app.listen({ port: 0 }, function (err, address) { - t.error(err) + app.post('/', function (request, reply) { + t.assert.ok(storage.getStore()) + const id = storage.getStore().id + reply.send({ id }) + }) - sget({ - method: 'POST', - url: 'http://localhost:' + app.server.address().port, - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { id: 0 }) + app.listen({ port: 0 }, function (err, address) { + t.assert.ifError(err) sget({ method: 'POST', @@ -54,20 +44,33 @@ app.listen({ port: 0 }, function (err, address) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { id: 1 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { id: 0 }) sget({ - method: 'GET', + method: 'POST', url: 'http://localhost:' + app.server.address().port, + body: { + hello: 'world' + }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { id: 2 }) - app.close() - t.end() + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { id: 1 }) + + sget({ + method: 'GET', + url: 'http://localhost:' + app.server.address().port, + json: true + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { id: 2 }) + app.close() + done() + }) }) }) }) diff --git a/test/async-await.test.js b/test/async-await.test.js index 9bc5242073d..c90d1d44dfb 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('..') const split = require('split2') @@ -44,26 +43,26 @@ const optsWithHostnameAndPort = { } } } -test('async await', t => { - t.plan(13) +test('async await', (t, done) => { + t.plan(16) const fastify = Fastify() try { fastify.get('/', opts, async function awaitMyFunc (req, reply) { await sleep(200) return { hello: 'world' } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } try { fastify.get('/no-await', opts, async function (req, reply) { return { hello: 'world' } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } try { @@ -71,52 +70,50 @@ test('async await', t => { await sleep(200) return { hello: 'world', hostname: req.hostname, port: req.port } }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + fastify.listen({ port: 0 }, async err => { + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/no-await' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - t.test('test for hostname and port in request', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/await/hostname_port' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - const parsedBody = JSON.parse(body) - t.equal(parsedBody.hostname, 'localhost') - t.equal(parseInt(parsedBody.port), fastify.server.address().port) - }) + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/await/hostname_port' + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + const parsedBody = JSON.parse(body) + t.assert.strictEqual(parsedBody.hostname, 'localhost') + t.assert.strictEqual(parseInt(parsedBody.port), fastify.server.address().port) + done() }) }) }) -test('ignore the result of the promise if reply.send is called beforehand (undefined)', t => { +test('ignore the result of the promise if reply.send is called beforehand (undefined)', (t, done) => { t.plan(4) const server = Fastify() @@ -126,22 +123,23 @@ test('ignore the result of the promise if reply.send is called beforehand (undef await reply.send(payload) }) - t.teardown(server.close.bind(server)) + t.after(() => { server.close() }) server.listen({ port: 0 }, (err) => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + server.server.address().port + '/' }, (err, res, body) => { - t.error(err) - t.same(payload, JSON.parse(body)) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(body)) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) }) -test('ignore the result of the promise if reply.send is called beforehand (object)', t => { +test('ignore the result of the promise if reply.send is called beforehand (object)', (t, done) => { t.plan(4) const server = Fastify() @@ -152,28 +150,29 @@ test('ignore the result of the promise if reply.send is called beforehand (objec return { hello: 'world' } }) - t.teardown(server.close.bind(server)) + t.after(() => { server.close() }) server.listen({ port: 0 }, (err) => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + server.server.address().port + '/' }, (err, res, body) => { - t.error(err) - t.same(payload, JSON.parse(body)) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(body)) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) }) -test('server logs an error if reply.send is called and a value is returned via async/await', t => { +test('server logs an error if reply.send is called and a value is returned via async/await', (t, done) => { const lines = ['incoming request', 'request completed', 'Reply was already sent, did you forget to "return reply" in "/" (GET)?'] t.plan(lines.length + 2) const splitStream = split(JSON.parse) splitStream.on('data', (line) => { - t.equal(line.msg, lines.shift()) + t.assert.strictEqual(line.msg, lines.shift()) }) const logger = pino(splitStream) @@ -191,13 +190,14 @@ test('server logs an error if reply.send is called and a value is returned via a method: 'GET', url: '/' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + done() }) }) -test('ignore the result of the promise if reply.send is called beforehand (undefined)', t => { +test('ignore the result of the promise if reply.send is called beforehand (undefined)', (t, done) => { t.plan(4) const server = Fastify() @@ -207,22 +207,23 @@ test('ignore the result of the promise if reply.send is called beforehand (undef await reply.send(payload) }) - t.teardown(server.close.bind(server)) + t.after(() => { server.close() }) server.listen({ port: 0 }, (err) => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + server.server.address().port + '/' }, (err, res, body) => { - t.error(err) - t.same(payload, JSON.parse(body)) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(body)) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) }) -test('ignore the result of the promise if reply.send is called beforehand (object)', t => { +test('ignore the result of the promise if reply.send is called beforehand (object)', (t, done) => { t.plan(4) const server = Fastify() @@ -233,28 +234,29 @@ test('ignore the result of the promise if reply.send is called beforehand (objec return { hello: 'world' } }) - t.teardown(server.close.bind(server)) + t.after(() => { server.close() }) server.listen({ port: 0 }, (err) => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + server.server.address().port + '/' }, (err, res, body) => { - t.error(err) - t.same(payload, JSON.parse(body)) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(body)) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) }) -test('await reply if we will be calling reply.send in the future', t => { +test('await reply if we will be calling reply.send in the future', (t, done) => { const lines = ['incoming request', 'request completed'] t.plan(lines.length + 2) const splitStream = split(JSON.parse) splitStream.on('data', (line) => { - t.equal(line.msg, lines.shift()) + t.assert.strictEqual(line.msg, lines.shift()) }) const server = Fastify({ @@ -276,19 +278,20 @@ test('await reply if we will be calling reply.send in the future', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + done() }) }) -test('await reply if we will be calling reply.send in the future (error case)', t => { +test('await reply if we will be calling reply.send in the future (error case)', (t, done) => { const lines = ['incoming request', 'kaboom', 'request completed'] t.plan(lines.length + 2) const splitStream = split(JSON.parse) splitStream.on('data', (line) => { - t.equal(line.msg, lines.shift()) + t.assert.strictEqual(line.msg, lines.shift()) }) const server = Fastify({ @@ -309,12 +312,13 @@ test('await reply if we will be calling reply.send in the future (error case)', method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + done() }) }) -test('support reply decorators with await', t => { +test('support reply decorators with await', (t, done) => { t.plan(2) const fastify = Fastify() @@ -336,9 +340,10 @@ test('support reply decorators with await', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + done() }) }) @@ -353,9 +358,9 @@ test('inject async await', async t => { try { const res = await fastify.inject({ method: 'GET', url: '/' }) - t.same({ hello: 'world' }, JSON.parse(res.payload)) + t.assert.deepStrictEqual({ hello: 'world' }, JSON.parse(res.payload)) } catch (err) { - t.fail(err) + t.assert.fail(err) } }) @@ -370,18 +375,18 @@ test('inject async await - when the server equal up', async t => { try { const res = await fastify.inject({ method: 'GET', url: '/' }) - t.same({ hello: 'world' }, JSON.parse(res.payload)) + t.assert.deepStrictEqual({ hello: 'world' }, JSON.parse(res.payload)) } catch (err) { - t.fail(err) + t.assert.fail(err) } await sleep(200) try { const res2 = await fastify.inject({ method: 'GET', url: '/' }) - t.same({ hello: 'world' }, JSON.parse(res2.payload)) + t.assert.deepStrictEqual({ hello: 'world' }, JSON.parse(res2.payload)) } catch (err) { - t.fail(err) + t.assert.fail(err) } }) @@ -400,13 +405,13 @@ test('async await plugin', async t => { try { const res = await fastify.inject({ method: 'GET', url: '/' }) - t.same({ hello: 'world' }, JSON.parse(res.payload)) + t.assert.deepStrictEqual({ hello: 'world' }, JSON.parse(res.payload)) } catch (err) { - t.fail(err) + t.assert.fail(err) } }) -test('does not call reply.send() twice if 204 response equal already sent', t => { +test('does not call reply.send() twice if 204 response equal already sent', (t, done) => { t.plan(2) const fastify = Fastify() @@ -422,12 +427,13 @@ test('does not call reply.send() twice if 204 response equal already sent', t => method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 204) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 204) + done() }) }) -test('promise was fulfilled with undefined', t => { +test('promise was fulfilled with undefined', (t, done) => { t.plan(4) let fastify = null @@ -440,29 +446,30 @@ test('promise was fulfilled with undefined', t => { } }) } catch (e) { - t.fail() + t.assert.fail() } - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.get('/', async (req, reply) => { }) stream.once('data', line => { - t.fail('should not log an error') + t.assert.fail('should not log an error') }) fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/' }, (err, res, body) => { - t.error(err) - t.equal(res.body, undefined) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.body, undefined) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) }) @@ -480,16 +487,16 @@ test('promise was fulfilled with undefined using inject', async (t) => { }) stream.once('data', line => { - t.fail('should not log an error') + t.assert.fail('should not log an error') }) const res = await fastify.inject('/') - t.equal(res.body, '') - t.equal(res.statusCode, 200) + t.assert.strictEqual(res.body, '') + t.assert.strictEqual(res.statusCode, 200) }) -test('error is not logged because promise was fulfilled with undefined but response was sent before promise resolution', t => { +test('error is not logged because promise was fulfilled with undefined but response was sent before promise resolution', (t, done) => { t.plan(4) let fastify = null @@ -503,38 +510,39 @@ test('error is not logged because promise was fulfilled with undefined but respo } }) } catch (e) { - t.fail() + t.assert.fail() } - t.teardown(fastify.close.bind(fastify)) + t.after(() => { fastify.close() }) fastify.get('/', async (req, reply) => { reply.send(payload) }) stream.once('data', line => { - t.fail('should not log an error') + t.assert.fail('should not log an error') }) fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/' }, (err, res, body) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same( + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual( payload, JSON.parse(body) ) + done() }) }) }) -test('Thrown Error instance sets HTTP status code', t => { +test('Thrown Error instance sets HTTP status code', (t, done) => { t.plan(3) const fastify = Fastify() @@ -550,9 +558,9 @@ test('Thrown Error instance sets HTTP status code', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same( + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual( { error: statusCodes['418'], message: err.message, @@ -560,10 +568,11 @@ test('Thrown Error instance sets HTTP status code', t => { }, JSON.parse(res.payload) ) + done() }) }) -test('customErrorHandler support', t => { +test('customErrorHandler support', (t, done) => { t.plan(4) const fastify = Fastify() @@ -575,7 +584,7 @@ test('customErrorHandler support', t => { }) fastify.setErrorHandler(async err => { - t.equal(err.message, 'ouch') + t.assert.strictEqual(err.message, 'ouch') const error = new Error('kaboom') error.statusCode = 401 throw error @@ -585,9 +594,9 @@ test('customErrorHandler support', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 401) - t.same( + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 401) + t.assert.deepStrictEqual( { error: statusCodes['401'], message: 'kaboom', @@ -595,10 +604,11 @@ test('customErrorHandler support', t => { }, JSON.parse(res.payload) ) + done() }) }) -test('customErrorHandler support without throwing', t => { +test('customErrorHandler support without throwing', (t, done) => { t.plan(4) const fastify = Fastify() @@ -610,26 +620,27 @@ test('customErrorHandler support without throwing', t => { }) fastify.setErrorHandler(async (err, req, reply) => { - t.equal(err.message, 'ouch') + t.assert.strictEqual(err.message, 'ouch') await reply.code(401).send('kaboom') - reply.send = t.fail.bind(t, 'should not be called') + reply.send = t.assert.fail.bind(t, 'should not be called') }) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 401) - t.same( + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 401) + t.assert.deepStrictEqual( 'kaboom', res.payload ) + done() }) }) // See https://github.com/fastify/fastify/issues/2653 -test('customErrorHandler only called if reply not already sent', t => { +test('customErrorHandler only called if reply not already sent', (t, done) => { t.plan(3) const fastify = Fastify() @@ -641,23 +652,24 @@ test('customErrorHandler only called if reply not already sent', t => { throw error }) - fastify.setErrorHandler(t.fail.bind(t, 'should not be called')) + fastify.setErrorHandler(t.assert.fail.bind(t, 'should not be called')) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same( + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual( 'success', res.payload ) + done() }) }) // See https://github.com/fastify/fastify/issues/3209 -test('setNotFoundHandler should accept return value', t => { +test('setNotFoundHandler should accept return value', (t, done) => { t.plan(3) const fastify = Fastify() @@ -677,9 +689,9 @@ test('setNotFoundHandler should accept return value', t => { method: 'GET', url: '/elsewhere' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - t.same( + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + t.assert.deepStrictEqual( { error: statusCodes['404'], message: 'lost', @@ -687,11 +699,12 @@ test('setNotFoundHandler should accept return value', t => { }, JSON.parse(res.payload) ) + done() }) }) // See https://github.com/fastify/fastify/issues/3209 -test('customErrorHandler should accept return value', t => { +test('customErrorHandler should accept return value', (t, done) => { t.plan(4) const fastify = Fastify() @@ -703,7 +716,7 @@ test('customErrorHandler should accept return value', t => { }) fastify.setErrorHandler((err, req, reply) => { - t.equal(err.message, 'ouch') + t.assert.strictEqual(err.message, 'ouch') reply.code(401) return { error: statusCodes['401'], @@ -716,9 +729,9 @@ test('customErrorHandler should accept return value', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 401) - t.same( + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 401) + t.assert.deepStrictEqual( { error: statusCodes['401'], message: 'kaboom', @@ -726,10 +739,11 @@ test('customErrorHandler should accept return value', t => { }, JSON.parse(res.payload) ) + done() }) }) test('await self', async t => { const app = Fastify() - t.equal(await app, app) + t.assert.strictEqual(await app, app) }) From ccf57e320ed1da532d701c27cf5b3f448b5a4617 Mon Sep 17 00:00:00 2001 From: Krzysztof Witkowski Date: Mon, 28 Oct 2024 14:09:40 +0100 Subject: [PATCH 0801/1295] test: ported route 3 test (#5780) --- test/route.3.test.js | 56 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/test/route.3.test.js b/test/route.3.test.js index b6af1fcdf35..f329ff8e4f1 100644 --- a/test/route.3.test.js +++ b/test/route.3.test.js @@ -1,11 +1,10 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const joi = require('joi') const Fastify = require('..') -test('does not mutate joi schemas', t => { +test('does not mutate joi schemas', (t, done) => { t.plan(4) const fastify = Fastify() @@ -32,7 +31,7 @@ test('does not mutate joi schemas', t => { params: { an_id: joi.number() } }, handler (req, res) { - t.same(req.params, { an_id: 42 }) + t.assert.deepEqual(req.params, { an_id: 42 }) res.send({ hello: 'world' }) } }) @@ -40,14 +39,15 @@ test('does not mutate joi schemas', t => { fastify.inject({ method: 'GET', url: '/foo/42' - }, (err, result) => { - t.error(err) - t.equal(result.statusCode, 200) - t.same(JSON.parse(result.payload), { hello: 'world' }) + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + done() }) }) -test('multiple routes with one schema', t => { +test('multiple routes with one schema', (t, done) => { t.plan(2) const fastify = Fastify() @@ -80,18 +80,19 @@ test('multiple routes with one schema', t => { }) fastify.ready(error => { - t.error(error) - t.same(schema, schema) + t.assert.ifError(error) + t.assert.deepStrictEqual(schema, schema) + done() }) }) -test('route error handler overrides default error handler', t => { +test('route error handler overrides default error handler', (t, done) => { t.plan(4) const fastify = Fastify() const customRouteErrorHandler = (error, request, reply) => { - t.equal(error.message, 'Wrong Pot Error') + t.assert.strictEqual(error.message, 'Wrong Pot Error') reply.code(418).send({ message: 'Make a brew', @@ -113,23 +114,24 @@ test('route error handler overrides default error handler', t => { method: 'GET', url: '/coffee' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same(JSON.parse(res.payload), { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual(res.json(), { message: 'Make a brew', statusCode: 418, error: 'Wrong Pot Error' }) + done() }) }) -test('route error handler does not affect other routes', t => { +test('route error handler does not affect other routes', (t, done) => { t.plan(3) const fastify = Fastify() const customRouteErrorHandler = (error, request, reply) => { - t.equal(error.message, 'Wrong Pot Error') + t.assert.strictEqual(error.message, 'Wrong Pot Error') reply.code(418).send({ message: 'Make a brew', @@ -159,23 +161,24 @@ test('route error handler does not affect other routes', t => { method: 'GET', url: '/tea' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 500) - t.same(JSON.parse(res.payload), { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { message: 'No tea today', statusCode: 500, error: 'Internal Server Error' }) + done() }) }) -test('async error handler for a route', t => { +test('async error handler for a route', (t, done) => { t.plan(4) const fastify = Fastify() const customRouteErrorHandler = async (error, request, reply) => { - t.equal(error.message, 'Delayed Pot Error') + t.assert.strictEqual(error.message, 'Delayed Pot Error') reply.code(418) return { message: 'Make a brew sometime later', @@ -197,12 +200,13 @@ test('async error handler for a route', t => { method: 'GET', url: '/late-coffee' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same(JSON.parse(res.payload), { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual(res.json(), { message: 'Make a brew sometime later', statusCode: 418, error: 'Delayed Pot Error' }) + done() }) }) From d2f28ce17be2e6d1a4abbb9c881575433c464147 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 31 Oct 2024 10:47:16 +0100 Subject: [PATCH 0802/1295] test: migrated async-dispose.test.js from tap to node:test (#5793) --- test/async-dispose.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/async-dispose.test.js b/test/async-dispose.test.js index 576b1648640..786d21f6a41 100644 --- a/test/async-dispose.test.js +++ b/test/async-dispose.test.js @@ -1,21 +1,21 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const Fastify = require('../fastify') // asyncDispose doesn't exist in node <= 16 -t.test('async dispose should close fastify', { skip: !('asyncDispose' in Symbol) }, async t => { +test('async dispose should close fastify', { skip: !('asyncDispose' in Symbol) }, async t => { t.plan(2) const fastify = Fastify() await fastify.listen({ port: 0 }) - t.equal(fastify.server.listening, true) + t.assert.strictEqual(fastify.server.listening, true) // the same as syntax sugar for // await using app = fastify() await fastify[Symbol.asyncDispose]() - t.equal(fastify.server.listening, false) + t.assert.strictEqual(fastify.server.listening, false) }) From 3949ee0b4b65e3f5e55ae3647c627a8df6cba09d Mon Sep 17 00:00:00 2001 From: Ran Toledo Date: Thu, 31 Oct 2024 14:14:07 +0200 Subject: [PATCH 0803/1295] migrate constrained-routes-test.js to use node:test (#5798) --- test/constrained-routes.test.js | 479 ++++++++++++++++---------------- 1 file changed, 243 insertions(+), 236 deletions(-) diff --git a/test/constrained-routes.test.js b/test/constrained-routes.test.js index 250d74567a4..6e5d8a9222f 100644 --- a/test/constrained-routes.test.js +++ b/test/constrained-routes.test.js @@ -1,11 +1,10 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../fastify') -test('Should register a host constrained route', t => { - t.plan(7) +test('Should register a host constrained route', async t => { + t.plan(4) const fastify = Fastify() fastify.route({ @@ -17,40 +16,41 @@ test('Should register a host constrained route', t => { } }) - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'fastify.dev' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.statusCode, 200) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'fastify.dev' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + } - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'example.com' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'example.com' + } + }) - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) + t.assert.strictEqual(res.statusCode, 404) + } + + { + const res = await fastify.inject({ + method: 'GET', + url: '/' + }) + t.assert.strictEqual(res.statusCode, 404) + } }) -test('Should register the same route with host constraints', t => { - t.plan(8) +test('Should register the same route with host constraints', async t => { + t.plan(5) const fastify = Fastify() fastify.route({ @@ -71,44 +71,45 @@ test('Should register the same route with host constraints', t => { } }) - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'fastify.dev' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'fastify.dev') - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'fastify.dev' + } + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'fastify.dev') + } - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'example.com' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'example.com') - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'example.com' + } + }) - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'fancy.ca' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'example.com') + } + + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'fancy.ca' + } + }) + t.assert.strictEqual(res.statusCode, 404) + } }) -test('Should allow registering custom constrained routes', t => { - t.plan(8) +test('Should allow registering custom constrained routes', async t => { + t.plan(5) const constraint = { name: 'secret', @@ -145,44 +146,44 @@ test('Should allow registering custom constrained routes', t => { } }) - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'X-Secret': 'alpha' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from alpha' }) - t.equal(res.statusCode, 200) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'alpha' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from alpha' }) + t.assert.strictEqual(res.statusCode, 200) + } - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'X-Secret': 'beta' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from beta' }) - t.equal(res.statusCode, 200) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'beta' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from beta' }) + t.assert.strictEqual(res.statusCode, 200) + } - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'X-Secret': 'gamma' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'gamma' + } + }) + t.assert.strictEqual(res.statusCode, 404) + } }) -test('Should allow registering custom constrained routes outside constructor', t => { - t.plan(8) +test('Should allow registering custom constrained routes outside constructor', async t => { + t.plan(5) const constraint = { name: 'secret', @@ -220,43 +221,44 @@ test('Should allow registering custom constrained routes outside constructor', t } }) - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'X-Secret': 'alpha' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from alpha' }) - t.equal(res.statusCode, 200) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'alpha' + } + }) - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'X-Secret': 'beta' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from beta' }) - t.equal(res.statusCode, 200) - }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from alpha' }) + t.assert.strictEqual(res.statusCode, 200) + } - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'X-Secret': 'gamma' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'beta' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from beta' }) + t.assert.strictEqual(res.statusCode, 200) + } + + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'gamma' + } + }) + t.assert.strictEqual(res.statusCode, 404) + } }) -test('Custom constrained routes registered also for HEAD method generated by fastify', t => { +test('Custom constrained routes registered also for HEAD method generated by fastify', (t, done) => { t.plan(3) const constraint = { @@ -292,13 +294,14 @@ test('Custom constrained routes registered also for HEAD method generated by fas 'X-Secret': 'mySecret' } }, (err, res) => { - t.error(err) - t.same(res.headers['content-length'], '31') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.headers['content-length'], '31') + t.assert.strictEqual(res.statusCode, 200) + done() }) }) -test('Custom constrained routes registered with addConstraintStrategy apply also for HEAD method generated by fastify', t => { +test('Custom constrained routes registered with addConstraintStrategy apply also for HEAD method generated by fastify', (t, done) => { t.plan(3) const constraint = { @@ -335,13 +338,14 @@ test('Custom constrained routes registered with addConstraintStrategy apply also 'X-Secret': 'mySecret' } }, (err, res) => { - t.error(err) - t.same(res.headers['content-length'], '31') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.headers['content-length'], '31') + t.assert.strictEqual(res.statusCode, 200) + done() }) }) -test('Add a constraint strategy after fastify instance was started', t => { +test('Add a constraint strategy after fastify instance was started', (t, done) => { t.plan(4) const constraint = { @@ -371,14 +375,14 @@ test('Add a constraint strategy after fastify instance was started', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.same(res.payload, 'ok') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'ok') + t.assert.strictEqual(res.statusCode, 200) - t.throws( - () => fastify.addConstraintStrategy(constraint), - 'Cannot add constraint strategy when fastify instance is already started!' + t.assert.throws( + () => fastify.addConstraintStrategy(constraint) ) + done() }) }) @@ -403,9 +407,9 @@ test('Add a constraint strategy should throw an error if there already exist cus const fastify = Fastify() fastify.addConstraintStrategy(constraint) - t.throws( + t.assert.throws( () => fastify.addConstraintStrategy(constraint), - 'There already exists a custom constraint with the name secret.' + /^Error: There already exists a custom constraint with the name secret.$/ ) }) @@ -430,7 +434,7 @@ test('Add a constraint strategy shouldn\'t throw an error if default constraint const fastify = Fastify() fastify.addConstraintStrategy(constraint) - t.pass() + t.assert.ok(true) }) test('Add a constraint strategy should throw an error if default constraint with the same name is used', t => { @@ -462,9 +466,9 @@ test('Add a constraint strategy should throw an error if default constraint with } }) - t.throws( + t.assert.throws( () => fastify.addConstraintStrategy(constraint), - 'There already exists a route with version constraint.' + /^Error: There already exists a route with version constraint.$/ ) }) @@ -473,8 +477,8 @@ test('The hasConstraintStrategy should return false for default constraints unti const fastify = Fastify() - t.equal(fastify.hasConstraintStrategy('version'), false) - t.equal(fastify.hasConstraintStrategy('host'), false) + t.assert.strictEqual(fastify.hasConstraintStrategy('version'), false) + t.assert.strictEqual(fastify.hasConstraintStrategy('host'), false) fastify.route({ method: 'GET', @@ -485,8 +489,8 @@ test('The hasConstraintStrategy should return false for default constraints unti } }) - t.equal(fastify.hasConstraintStrategy('version'), false) - t.equal(fastify.hasConstraintStrategy('host'), true) + t.assert.strictEqual(fastify.hasConstraintStrategy('version'), false) + t.assert.strictEqual(fastify.hasConstraintStrategy('host'), true) fastify.route({ method: 'GET', @@ -497,8 +501,8 @@ test('The hasConstraintStrategy should return false for default constraints unti } }) - t.equal(fastify.hasConstraintStrategy('version'), true) - t.equal(fastify.hasConstraintStrategy('host'), true) + t.assert.strictEqual(fastify.hasConstraintStrategy('version'), true) + t.assert.strictEqual(fastify.hasConstraintStrategy('host'), true) }) test('The hasConstraintStrategy should return true if there already exist a custom constraint with the same name', t => { @@ -521,13 +525,13 @@ test('The hasConstraintStrategy should return true if there already exist a cust const fastify = Fastify() - t.equal(fastify.hasConstraintStrategy('secret'), false) + t.assert.strictEqual(fastify.hasConstraintStrategy('secret'), false) fastify.addConstraintStrategy(constraint) - t.equal(fastify.hasConstraintStrategy('secret'), true) + t.assert.strictEqual(fastify.hasConstraintStrategy('secret'), true) }) -test('Should allow registering an unconstrained route after a constrained route', t => { - t.plan(6) +test('Should allow registering an unconstrained route after a constrained route', async t => { + t.plan(4) const fastify = Fastify() fastify.route({ @@ -547,32 +551,32 @@ test('Should allow registering an unconstrained route after a constrained route' } }) - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'fastify.dev' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from fastify.dev' }) - t.equal(res.statusCode, 200) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'fastify.dev' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from fastify.dev' }) + t.assert.strictEqual(res.statusCode, 200) + } - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'example.com' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from any other domain' }) - t.equal(res.statusCode, 200) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'example.com' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from any other domain' }) + t.assert.strictEqual(res.statusCode, 200) + } }) -test('Should allow registering constrained routes in a prefixed plugin', t => { +test('Should allow registering constrained routes in a prefixed plugin', (t, done) => { t.plan(3) const fastify = Fastify() @@ -595,13 +599,14 @@ test('Should allow registering constrained routes in a prefixed plugin', t => { host: 'fastify.dev' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { ok: true }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { ok: true }) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) -test('Should allow registering a constrained GET route after a constrained HEAD route', t => { +test('Should allow registering a constrained GET route after a constrained HEAD route', (t, done) => { t.plan(3) const fastify = Fastify() @@ -631,13 +636,14 @@ test('Should allow registering a constrained GET route after a constrained HEAD host: 'fastify.dev' } }, (err, res) => { - t.error(err) - t.same(res.payload, 'custom HEAD response') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'custom HEAD response') + t.assert.strictEqual(res.statusCode, 200) + done() }) }) -test('Should allow registering a constrained GET route after an unconstrained HEAD route', t => { +test('Should allow registering a constrained GET route after an unconstrained HEAD route', (t, done) => { t.plan(3) const fastify = Fastify() @@ -667,9 +673,10 @@ test('Should allow registering a constrained GET route after an unconstrained HE host: 'fastify.dev' } }, (err, res) => { - t.error(err) - t.same(res.headers['content-length'], '41') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.headers['content-length'], '41') + t.assert.strictEqual(res.statusCode, 200) + done() }) }) @@ -702,7 +709,7 @@ test('Will not try to re-createprefixed HEAD route if it already exists and expo await fastify.ready() - t.ok(true) + t.assert.ok(true) }) test('allows separate constrained and unconstrained HEAD routes', async (t) => { @@ -744,7 +751,7 @@ test('allows separate constrained and unconstrained HEAD routes', async (t) => { await fastify.ready() - t.ok(true) + t.assert.ok(true) }) test('allow async constraints', async (t) => { @@ -787,17 +794,17 @@ test('allow async constraints', async (t) => { { const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'alpha' } }) - t.same(JSON.parse(payload), { hello: 'from alpha' }) - t.equal(statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(payload), { hello: 'from alpha' }) + t.assert.strictEqual(statusCode, 200) } { const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'beta' } }) - t.same(JSON.parse(payload), { hello: 'from beta' }) - t.equal(statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(payload), { hello: 'from beta' }) + t.assert.strictEqual(statusCode, 200) } { const { statusCode } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'gamma' } }) - t.equal(statusCode, 404) + t.assert.strictEqual(statusCode, 404) } }) @@ -841,28 +848,28 @@ test('error in async constraints', async (t) => { { const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'alpha' } }) - t.same(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) - t.equal(statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.assert.strictEqual(statusCode, 500) } { const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'beta' } }) - t.same(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) - t.equal(statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.assert.strictEqual(statusCode, 500) } { const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'gamma' } }) - t.same(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) - t.equal(statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.assert.strictEqual(statusCode, 500) } { const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/' }) - t.same(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) - t.equal(statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.assert.strictEqual(statusCode, 500) } }) -test('Allow regex constraints in routes', t => { - t.plan(5) +test('Allow regex constraints in routes', async t => { + t.plan(3) const fastify = Fastify() @@ -875,26 +882,26 @@ test('Allow regex constraints in routes', t => { } }) - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'dev.fastify.dev' - } - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'from fastify dev domain' }) - t.equal(res.statusCode, 200) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'dev.fastify.dev' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from fastify dev domain' }) + t.assert.strictEqual(res.statusCode, 200) + } - fastify.inject({ - method: 'GET', - url: '/', - headers: { - host: 'google.com' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) - }) + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + host: 'google.com' + } + }) + t.assert.strictEqual(res.statusCode, 404) + } }) From 8e0008511c677045d95618c9928a8906ed701215 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 31 Oct 2024 13:14:42 +0100 Subject: [PATCH 0804/1295] test: migrated keep-alive-timeout.test.js from tap to node:test (#5796) * test: migrated keepAliveTimeout.test.js from tap to node:test * test: renamed file to kabab-case --- ...out.test.js => keep-alive-timeout.test.js} | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) rename test/{keepAliveTimeout.test.js => keep-alive-timeout.test.js} (64%) diff --git a/test/keepAliveTimeout.test.js b/test/keep-alive-timeout.test.js similarity index 64% rename from test/keepAliveTimeout.test.js rename to test/keep-alive-timeout.test.js index 16eab7d9011..84b14bc4cb5 100644 --- a/test/keepAliveTimeout.test.js +++ b/test/keep-alive-timeout.test.js @@ -2,34 +2,33 @@ const Fastify = require('..') const http = require('node:http') -const t = require('tap') -const test = t.test +const { test } = require('node:test') test('keepAliveTimeout', t => { t.plan(6) try { Fastify({ keepAliveTimeout: 1.3 }) - t.fail('option must be an integer') + t.assert.fail('option must be an integer') } catch (err) { - t.ok(err) + t.assert.ok(err) } try { Fastify({ keepAliveTimeout: [] }) - t.fail('option must be an integer') + t.assert.fail('option must be an integer') } catch (err) { - t.ok(err) + t.assert.ok(err) } const httpServer = Fastify({ keepAliveTimeout: 1 }).server - t.equal(httpServer.keepAliveTimeout, 1) + t.assert.strictEqual(httpServer.keepAliveTimeout, 1) const httpsServer = Fastify({ keepAliveTimeout: 2, https: {} }).server - t.equal(httpsServer.keepAliveTimeout, 2) + t.assert.strictEqual(httpsServer.keepAliveTimeout, 2) const http2Server = Fastify({ keepAliveTimeout: 3, http2: true }).server - t.not(http2Server.keepAliveTimeout, 3) + t.assert.notStrictEqual(http2Server.keepAliveTimeout, 3) const serverFactory = (handler, _) => { const server = http.createServer((req, res) => { @@ -39,5 +38,5 @@ test('keepAliveTimeout', t => { return server } const customServer = Fastify({ keepAliveTimeout: 4, serverFactory }).server - t.equal(customServer.keepAliveTimeout, 5) + t.assert.strictEqual(customServer.keepAliveTimeout, 5) }) From 1665c0c8109f22f3f76bac7c827f7a0044e8d0f6 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 31 Oct 2024 13:15:17 +0100 Subject: [PATCH 0805/1295] test: migrated noop-set.test.js from tap to node:test (#5794) * test: migrated noop-set.test.js from tap to node:test * test: improvement Co-authored-by: Dan Castillo Signed-off-by: Antonio Tripodi * test: improvement Co-authored-by: Dan Castillo Signed-off-by: Antonio Tripodi --------- Signed-off-by: Antonio Tripodi Co-authored-by: Dan Castillo --- test/noop-set.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/noop-set.test.js b/test/noop-set.test.js index 44f0b5fad99..a004d88c0f0 100644 --- a/test/noop-set.test.js +++ b/test/noop-set.test.js @@ -1,19 +1,19 @@ 'use strict' -const tap = require('tap') +const { test } = require('node:test') const noopSet = require('../lib/noop-set') -tap.test('does a lot of nothing', async t => { +test('does a lot of nothing', async t => { const aSet = noopSet() - t.type(aSet, 'object') + t.assert.ok(aSet, 'object') const item = {} aSet.add(item) aSet.add({ another: 'item' }) aSet.delete(item) - t.equal(aSet.has(item), true) + t.assert.strictEqual(aSet.has(item), true) for (const i of aSet) { - t.fail('should not have any items', i) + t.assert.fail('should not have any items', i) } }) From fa414be21b2f94a6de7fc000179f3456be9dec7f Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 31 Oct 2024 14:04:29 +0100 Subject: [PATCH 0806/1295] test: migrated close-pipelining.test.js from tap to node:test (#5797) --- test/close-pipelining.test.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index ecac0ab51ed..c347318dfa2 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const { Client } = require('undici') @@ -30,7 +29,7 @@ test('Should return 503 while closing - pipelining', async t => { ]) const actual = responses.map(r => r.statusCode) - t.same(actual, codes) + t.assert.deepStrictEqual(actual, codes) await instance.close() }) @@ -61,10 +60,10 @@ test('Should close the socket abruptly - pipelining - return503OnClosing: false' instance.request({ path: '/', method: 'GET' }) ]) - t.equal(responses[0].status, 'fulfilled') - t.equal(responses[1].status, 'fulfilled') - t.equal(responses[2].status, 'rejected') - t.equal(responses[3].status, 'rejected') + t.assert.strictEqual(responses[0].status, 'fulfilled') + t.assert.strictEqual(responses[1].status, 'fulfilled') + t.assert.strictEqual(responses[2].status, 'rejected') + t.assert.strictEqual(responses[3].status, 'rejected') await instance.close() }) From 54544f5b0f0c9de7c5bad237f7b7c2d6663d76fd Mon Sep 17 00:00:00 2001 From: Krzysztof Witkowski Date: Thu, 31 Oct 2024 14:06:45 +0100 Subject: [PATCH 0807/1295] test: porting to node:test (#5772) --- test/connectionTimeout.test.js | 21 +++-- test/route.6.test.js | 159 +++++++++++++++------------------ 2 files changed, 80 insertions(+), 100 deletions(-) diff --git a/test/connectionTimeout.test.js b/test/connectionTimeout.test.js index 6c2c0192097..fc30e7dd3aa 100644 --- a/test/connectionTimeout.test.js +++ b/test/connectionTimeout.test.js @@ -2,34 +2,33 @@ const Fastify = require('..') const http = require('node:http') -const t = require('tap') -const test = t.test +const { test } = require('node:test') -test('connectionTimeout', t => { +test('connectionTimeout', async t => { t.plan(6) try { Fastify({ connectionTimeout: 1.3 }) - t.fail('option must be an integer') + t.assert.fail('option must be an integer') } catch (err) { - t.ok(err) + t.assert.ok(err) } try { Fastify({ connectionTimeout: [] }) - t.fail('option must be an integer') + t.assert.fail('option must be an integer') } catch (err) { - t.ok(err) + t.assert.ok(err) } const httpServer = Fastify({ connectionTimeout: 1 }).server - t.equal(httpServer.timeout, 1) + t.assert.strictEqual(httpServer.timeout, 1) const httpsServer = Fastify({ connectionTimeout: 2, https: {} }).server - t.equal(httpsServer.timeout, 2) + t.assert.strictEqual(httpsServer.timeout, 2) const http2Server = Fastify({ connectionTimeout: 3, http2: true }).server - t.equal(http2Server.timeout, 3) + t.assert.strictEqual(http2Server.timeout, 3) const serverFactory = (handler, _) => { const server = http.createServer((req, res) => { @@ -39,5 +38,5 @@ test('connectionTimeout', t => { return server } const customServer = Fastify({ connectionTimeout: 4, serverFactory }).server - t.equal(customServer.timeout, 5) + t.assert.strictEqual(customServer.timeout, 5) }) diff --git a/test/route.6.test.js b/test/route.6.test.js index 60fe5d74959..32858e88672 100644 --- a/test/route.6.test.js +++ b/test/route.6.test.js @@ -1,8 +1,7 @@ 'use strict' const stream = require('node:stream') -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') test('Creates a HEAD route for a GET one with prefixTrailingSlash', async (t) => { @@ -31,11 +30,11 @@ test('Creates a HEAD route for a GET one with prefixTrailingSlash', async (t) => await fastify.ready() - t.ok(true) + t.assert.ok(true) }) -test('Will not create a HEAD route that is not GET', t => { - t.plan(11) +test('Will not create a HEAD route that is not GET', async t => { + t.plan(8) const fastify = Fastify({ exposeHeadRoutes: true }) @@ -63,38 +62,35 @@ test('Will not create a HEAD route that is not GET', t => { } }) - fastify.inject({ + let res = await fastify.inject({ method: 'HEAD', url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(res.body, '') }) - fastify.inject({ + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.body, '') + + res = await fastify.inject({ method: 'HEAD', url: '/some-light' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '0') - t.equal(res.body, '') }) - fastify.inject({ + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + t.assert.strictEqual(res.headers['content-length'], '0') + t.assert.strictEqual(res.body, '') + + res = await fastify.inject({ method: 'HEAD', url: '/something' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) }) + + t.assert.strictEqual(res.statusCode, 404) }) -test('HEAD route should handle properly each response type', t => { - t.plan(25) +test('HEAD route should handle properly each response type', async t => { + t.plan(20) const fastify = Fastify({ exposeHeadRoutes: true }) const resString = 'Found me!' @@ -143,64 +139,54 @@ test('HEAD route should handle properly each response type', t => { } }) - fastify.inject({ + let res = await fastify.inject({ method: 'HEAD', url: '/json' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJSON))}`) - t.same(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.strictEqual(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJSON))}`) + t.assert.deepStrictEqual(res.body, '') - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/string' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') - t.equal(res.headers['content-length'], `${Buffer.byteLength(resString)}`) - t.equal(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(res.headers['content-length'], `${Buffer.byteLength(resString)}`) + t.assert.strictEqual(res.body, '') - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/buffer' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/buffer-with-content-type' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'image/jpeg') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'image/jpeg') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') - fastify.inject({ + res = await fastify.inject({ method: 'HEAD', url: '/stream' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], undefined) - t.equal(res.body, '') }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + t.assert.strictEqual(res.headers['content-length'], undefined) + t.assert.strictEqual(res.body, '') }) -test('HEAD route should respect custom onSend handlers', t => { - t.plan(6) +test('HEAD route should respect custom onSend handlers', async t => { + t.plan(5) let counter = 0 const resBuffer = Buffer.from('I am a coffee!') @@ -219,21 +205,20 @@ test('HEAD route should respect custom onSend handlers', t => { onSend: [customOnSend, customOnSend] }) - fastify.inject({ + const res = await fastify.inject({ method: 'HEAD', url: '/more-coffee' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - t.equal(counter, 2) }) + + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + t.assert.strictEqual(counter, 2) }) -test('route onSend can be function or array of functions', t => { - t.plan(12) +test('route onSend can be function or array of functions', async t => { + t.plan(10) const counters = { single: 0, multiple: 0 } const resBuffer = Buffer.from('I am a coffee!') @@ -261,23 +246,19 @@ test('route onSend can be function or array of functions', t => { onSend: [customOnSend, customOnSend] }) - fastify.inject({ method: 'HEAD', url: '/coffee' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - t.equal(counters.single, 1) - }) - - fastify.inject({ method: 'HEAD', url: '/more-coffee' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-type'], 'application/octet-stream') - t.equal(res.headers['content-length'], `${resBuffer.byteLength}`) - t.equal(res.body, '') - t.equal(counters.multiple, 2) - }) + let res = await fastify.inject({ method: 'HEAD', url: '/coffee' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + t.assert.strictEqual(counters.single, 1) + + res = await fastify.inject({ method: 'HEAD', url: '/more-coffee' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + t.assert.strictEqual(counters.multiple, 2) }) test('no warning for exposeHeadRoute', async t => { @@ -293,7 +274,7 @@ test('no warning for exposeHeadRoute', async t => { }) const listener = (w) => { - t.fail('no warning') + t.assert.fail('no warning') } process.on('warning', listener) From 109b4e506b73a14243ea7f81e86f64a753c4890a Mon Sep 17 00:00:00 2001 From: Federico Date: Thu, 31 Oct 2024 14:08:27 +0100 Subject: [PATCH 0808/1295] test: migrate serialize-response tap to node-test (#5775) --- test/serialize-response.test.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/serialize-response.test.js b/test/serialize-response.test.js index 3e066d3722a..4adf5ece388 100644 --- a/test/serialize-response.test.js +++ b/test/serialize-response.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const { S } = require('fluent-json-schema') const Fastify = require('../fastify') const sjson = require('secure-json-parse') @@ -117,8 +116,8 @@ test('serialize the response for a Bad Request error, as defined on the schema', url: '/' }) - t.equal(response.statusCode, 400) - t.same(sjson(response.body), { + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(sjson(response.body), { statusCode: 400, error: 'Bad Request', message: 'body must be object' @@ -138,8 +137,8 @@ test('serialize the response for a Not Found error, as defined on the schema', a body: { id: '404' } }) - t.equal(response.statusCode, 404) - t.same(sjson(response.body), { + t.assert.strictEqual(response.statusCode, 404) + t.assert.deepStrictEqual(sjson(response.body), { statusCode: 404, error: 'Not Found', message: 'Custom Not Found' @@ -159,8 +158,8 @@ test('serialize the response for a Internal Server Error error, as defined on th body: { id: '500' } }) - t.equal(response.statusCode, 500) - t.same(sjson(response.body), { + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(sjson(response.body), { statusCode: 500, error: 'Internal Server Error', message: 'Custom Internal Server Error' @@ -180,8 +179,8 @@ test('serialize the success response, as defined on the schema', async t => { body: { id: 'test' } }) - t.equal(response.statusCode, 200) - t.same(sjson(response.body), { + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(sjson(response.body), { id: 'test' }) }) From aaaf30f6f34df86f7ccc6f23664f39475d2fe3ff Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 31 Oct 2024 20:03:27 +0100 Subject: [PATCH 0809/1295] ci: fix typescript tests (#5799) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 380631d89a8..a44ee8ccd9e 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "test:ci": "npm run unit && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", - "test:typescript": "tsc test/types/import.ts --noEmit && tsd", + "test:typescript": "tsc test/types/import.ts --target es2022 --moduleResolution node16 --module node16 --noEmit && tsd", "test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse", "unit": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage", "unit:report": "c8 --reporter html borp --reporter=./test/test-reporter.mjs", From 3eddee4a2ce5ba4632298769ba405171121948a6 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 31 Oct 2024 20:24:42 +0100 Subject: [PATCH 0810/1295] Bumped v5.1.0 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 991147c6c2a..162111f4d5d 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.0.0' +const VERSION = '5.1.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index a44ee8ccd9e..3bb8ee89516 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.0.0", + "version": "5.1.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 83b1909f218e6e3f0d9174ccc920db239f1c039d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Angelantoni?= <154626053+AndreAngelantoni@users.noreply.github.com> Date: Fri, 1 Nov 2024 03:51:11 -0600 Subject: [PATCH 0811/1295] docs: add HeroDevs mentions to README and LTS docs (#5730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 5623: Add HeroDevs mention to README and LTS docs. * docs: Updated README.md. Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: André Angelantoni <154626053+AndreAngelantoni@users.noreply.github.com> * docs: Revise which versions are support, remove support matrix. * docs: correct borked links. * docs: Change grammar of partner sentence. Co-authored-by: Frazer Smith Signed-off-by: André Angelantoni <154626053+AndreAngelantoni@users.noreply.github.com> * docs: shorten some md lines --------- Signed-off-by: André Angelantoni <154626053+AndreAngelantoni@users.noreply.github.com> Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Co-authored-by: Frazer Smith --- README.md | 10 ++++++++++ docs/Reference/LTS.md | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 0b897b11593..644be93df64 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,13 @@ application, you should __always__ benchmark if performance matters to you. Please visit [Fastify help](https://github.com/fastify/help) to view prior support issues and to ask new support questions. +Version 3 of Fastify and lower are EOL and will not receive any security or bug fixes. + +Fastify's partner, HeroDevs, provides commercial security fixes for all +unsupported versions at [https://herodevs.com/support/fastify-nes][hd-link]. +Fastify's supported version matrix is available in the +[Long Term Support][lts-link] documentation. + ## Contributing Whether reporting bugs, discussing improvements and new ideas or writing code, @@ -414,3 +421,6 @@ dependencies: - ISC - BSD-3-Clause - BSD-2-Clause + +[hd-link]: https://www.herodevs.com/support/fastify-nes?utm_source=fastify&utm_medium=link&utm_campaign=github_readme +[lts-link]: https://fastify.dev/docs/latest/Reference/LTS/ \ No newline at end of file diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 12aee52a982..94a58b48d9b 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -48,6 +48,12 @@ A "month" is defined as 30 consecutive days. > dependency as `"fastify": "~3.15.x"`. This will leave your application > vulnerable, so please use with caution. +### Security Support Beyond LTS + +Fastify's partner, HeroDevs, provides commercial security support through the +OpenJS Ecosystem Sustainability Program for versions of Fastify that are EOL. +For more information, see their [Never Ending Support][hd-link] service. + ### Schedule `` @@ -81,3 +87,5 @@ Using [yarn](https://yarnpkg.com/) might require passing the `--ignore-engines` flag. [semver]: https://semver.org/ + +[hd-link]: https://www.herodevs.com/support/fastify-nes?utm_source=fastify&utm_medium=link&utm_campaign=eol_support_fastify From aade4620792aa9039731b7c4f4e758178b6494ed Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Fri, 1 Nov 2024 12:27:44 +0100 Subject: [PATCH 0812/1295] test: migrated reply-early-hints.test.js from tap to node:test (#5803) * test: migrated reply-early-hints.test.js from tap to node:test * test: update test --- ...ints.test.js => reply-early-hints.test.js} | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) rename test/{reply-earlyHints.test.js => reply-early-hints.test.js} (62%) diff --git a/test/reply-earlyHints.test.js b/test/reply-early-hints.test.js similarity index 62% rename from test/reply-earlyHints.test.js rename to test/reply-early-hints.test.js index 48228208d82..f5c54419bf7 100644 --- a/test/reply-earlyHints.test.js +++ b/test/reply-early-hints.test.js @@ -1,13 +1,13 @@ 'use strict' const Fastify = require('..') -const { test } = require('tap') +const { test } = require('node:test') const http = require('http') const http2 = require('http2') const testResBody = 'Hello, world!' -test('sends early hints', (t) => { +test('sends early hints', (t, done) => { t.plan(6) const fastify = Fastify({ @@ -18,24 +18,24 @@ test('sends early hints', (t) => { reply.writeEarlyHints({ link: '; rel=preload; as=style' }, () => { - t.pass('callback called') + t.assert.ok('callback called') }) return testResBody }) fastify.listen({ port: 0 }, (err, address) => { - t.error(err) + t.assert.ifError(err) const req = http.get(address) req.on('information', (res) => { - t.equal(res.statusCode, 103) - t.equal(res.headers.link, '; rel=preload; as=style') + t.assert.strictEqual(res.statusCode, 103) + t.assert.strictEqual(res.headers.link, '; rel=preload; as=style') }) req.on('response', (res) => { - t.equal(res.statusCode, 200) + t.assert.strictEqual(res.statusCode, 200) let data = '' res.on('data', (chunk) => { @@ -43,14 +43,15 @@ test('sends early hints', (t) => { }) res.on('end', () => { - t.equal(data, testResBody) - fastify.close(t.end) + t.assert.strictEqual(data, testResBody) + fastify.close() + done() }) }) }) }) -test('sends early hints (http2)', (t) => { +test('sends early hints (http2)', (t, done) => { t.plan(6) const fastify = Fastify({ @@ -67,19 +68,19 @@ test('sends early hints (http2)', (t) => { }) fastify.listen({ port: 0 }, (err, address) => { - t.error(err) + t.assert.ifError(err) const client = http2.connect(address) const req = client.request() req.on('headers', (headers) => { - t.not(headers, undefined) - t.equal(headers[':status'], 103) - t.equal(headers.link, '; rel=preload; as=style') + t.assert.notStrictEqual(headers, undefined) + t.assert.strictEqual(headers[':status'], 103) + t.assert.strictEqual(headers.link, '; rel=preload; as=style') }) req.on('response', (headers) => { - t.equal(headers[':status'], 200) + t.assert.strictEqual(headers[':status'], 200) }) let data = '' @@ -88,9 +89,10 @@ test('sends early hints (http2)', (t) => { }) req.on('end', () => { - t.equal(data, testResBody) + t.assert.strictEqual(data, testResBody) client.close() - fastify.close(t.end) + fastify.close() + done() }) req.end() From beae500e1bf2093a6bc714038994a47dbcd4d5df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:25:32 +0000 Subject: [PATCH 0813/1295] chore: Bump lycheeverse/lychee-action from 1.10.0 to 2.0.2 (#5807) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.10.0 to 2.0.2. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/2b973e86fc7b1f6b36a93795fe2c9c6ae1118621...7cd0af4c74a61395d455af97419279d86aafaede) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 9560597069e..c3d3328472f 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 + uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede with: fail: true # As external links behavior is not predictable, we check only internal links From 17f1c6eb35046d3ef78cb1257aff557c189fc902 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:25:55 +0000 Subject: [PATCH 0814/1295] chore: Bump thollander/actions-comment-pull-request from 2 to 3 (#5806) Bumps [thollander/actions-comment-pull-request](https://github.com/thollander/actions-comment-pull-request) from 2 to 3. - [Release notes](https://github.com/thollander/actions-comment-pull-request/releases) - [Commits](https://github.com/thollander/actions-comment-pull-request/compare/v2...v3) --- updated-dependencies: - dependency-name: thollander/actions-comment-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark-parser.yml | 2 +- .github/workflows/benchmark.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 8282c166faa..ea316d43c3e 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -70,7 +70,7 @@ jobs: pull-requests: write steps: - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6824f625cbd..65b0092338f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -70,7 +70,7 @@ jobs: pull-requests: write steps: - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | From 78678ab29e5bd9225b8c9906aab936682c2e231f Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Fri, 1 Nov 2024 20:14:52 +0100 Subject: [PATCH 0815/1295] test: migrated request-timeout.test.js from tap to node:test (#5805) --- ...imeout.test.js => request-timeout.test.js} | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) rename test/{requestTimeout.test.js => request-timeout.test.js} (64%) diff --git a/test/requestTimeout.test.js b/test/request-timeout.test.js similarity index 64% rename from test/requestTimeout.test.js rename to test/request-timeout.test.js index af811143be9..faf5455dc24 100644 --- a/test/requestTimeout.test.js +++ b/test/request-timeout.test.js @@ -1,31 +1,31 @@ 'use strict' const http = require('node:http') -const { test } = require('tap') -const Fastify = require('../fastify') +const { test } = require('node:test') +const Fastify = require('..') test('requestTimeout passed to server', t => { t.plan(5) try { Fastify({ requestTimeout: 500.1 }) - t.fail('option must be an integer') + t.assert.fail('option must be an integer') } catch (err) { - t.ok(err) + t.assert.ok(err) } try { Fastify({ requestTimeout: [] }) - t.fail('option must be an integer') + t.assert.fail('option must be an integer') } catch (err) { - t.ok(err) + t.assert.ok(err) } const httpServer = Fastify({ requestTimeout: 1000 }).server - t.equal(httpServer.requestTimeout, 1000) + t.assert.strictEqual(httpServer.requestTimeout, 1000) const httpsServer = Fastify({ requestTimeout: 1000, https: true }).server - t.equal(httpsServer.requestTimeout, 1000) + t.assert.strictEqual(httpsServer.requestTimeout, 1000) const serverFactory = (handler, _) => { const server = http.createServer((req, res) => { @@ -35,19 +35,19 @@ test('requestTimeout passed to server', t => { return server } const customServer = Fastify({ requestTimeout: 4000, serverFactory }).server - t.equal(customServer.requestTimeout, 5000) + t.assert.strictEqual(customServer.requestTimeout, 5000) }) test('requestTimeout should be set', async (t) => { t.plan(1) const initialConfig = Fastify({ requestTimeout: 5000 }).initialConfig - t.same(initialConfig.requestTimeout, 5000) + t.assert.deepEqual(initialConfig.requestTimeout, 5000) }) test('requestTimeout should 0', async (t) => { t.plan(1) const initialConfig = Fastify().initialConfig - t.same(initialConfig.requestTimeout, 0) + t.assert.deepEqual(initialConfig.requestTimeout, 0) }) From bec067a84cada96f9918cf68396d12165ac1248b Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 1 Nov 2024 20:15:31 +0100 Subject: [PATCH 0816/1295] fix: streamline migrated tests, make migrated the filenames of migrated tests kebab-case (#5800) * fix: streamline migrated tests * souvlaki or kebab? * kebabify connectionTimeout.test.js * fix pr comment --- ...gex.test.js => allow-unsafe-regex.test.js} | 6 ++--- test/async_hooks.test.js | 8 +++---- test/buffer.test.js | 4 ++-- test/case-insensitive.test.js | 12 +++++----- test/check.test.js | 2 +- ...y.test.js => child-logger-factory.test.js} | 2 +- test/client-timeout.test.js | 2 +- test/conditional-pino.test.js | 2 +- ...out.test.js => connection-timeout.test.js} | 0 test/imports.test.js | 4 ++-- ...er.test.js => content-type-parser.test.js} | 0 ...Request.test.js => handle-request.test.js} | 0 ...hookRunner.test.js => hook-runner.test.js} | 0 ...lConfig.test.js => initial-config.test.js} | 0 ...ory.test.js => req-id-gen-factory.test.js} | 0 test/request-header-host.test.js | 24 +++++++++---------- test/server.test.js | 24 +++++++++---------- 17 files changed, 45 insertions(+), 45 deletions(-) rename test/{allowUnsafeRegex.test.js => allow-unsafe-regex.test.js} (93%) rename test/{childLoggerFactory.test.js => child-logger-factory.test.js} (98%) rename test/{connectionTimeout.test.js => connection-timeout.test.js} (100%) rename test/internals/{contentTypeParser.test.js => content-type-parser.test.js} (100%) rename test/internals/{handleRequest.test.js => handle-request.test.js} (100%) rename test/internals/{hookRunner.test.js => hook-runner.test.js} (100%) rename test/internals/{initialConfig.test.js => initial-config.test.js} (100%) rename test/internals/{reqIdGenFactory.test.js => req-id-gen-factory.test.js} (100%) diff --git a/test/allowUnsafeRegex.test.js b/test/allow-unsafe-regex.test.js similarity index 93% rename from test/allowUnsafeRegex.test.js rename to test/allow-unsafe-regex.test.js index f700295dc59..6b9b7848120 100644 --- a/test/allowUnsafeRegex.test.js +++ b/test/allow-unsafe-regex.test.js @@ -24,7 +24,7 @@ test('allow unsafe regex', (t, done) => { url: 'http://localhost:' + fastify.server.address().port + '/1234' }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) t.assert.deepStrictEqual(JSON.parse(body), { foo: '1234' }) @@ -53,7 +53,7 @@ test('allow unsafe regex not match', (t, done) => { url: 'http://localhost:' + fastify.server.address().port + '/a1234' }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 404) + t.assert.strictEqual(response.statusCode, 404) done() }) }) @@ -111,7 +111,7 @@ test('allow unsafe regex allow unsafe', (t, done) => { url: 'http://localhost:' + fastify.server.address().port + '/1234' }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) t.assert.deepEqual(JSON.parse(body), { foo: '1234' }) diff --git a/test/async_hooks.test.js b/test/async_hooks.test.js index 1e2eea4e8af..72fdb8f9259 100644 --- a/test/async_hooks.test.js +++ b/test/async_hooks.test.js @@ -41,7 +41,7 @@ test('test async hooks', (t, done) => { json: true }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) sget({ method: 'POST', @@ -52,7 +52,7 @@ test('test async hooks', (t, done) => { json: true }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) sget({ method: 'GET', @@ -60,9 +60,9 @@ test('test async hooks', (t, done) => { json: true }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) app.close() - t.assert.equal(remainingIds.size, 0) + t.assert.strictEqual(remainingIds.size, 0) done() }) }) diff --git a/test/buffer.test.js b/test/buffer.test.js index 49b26f372e4..c06d634a61b 100644 --- a/test/buffer.test.js +++ b/test/buffer.test.js @@ -24,7 +24,7 @@ test('Buffer test', async t => { }) t.assert.ifError(response.error) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) t.assert.deepEqual(response.payload.toString(), '{"hello":"world"}') }) @@ -41,7 +41,7 @@ test('Buffer test', async t => { }) t.assert.ifError(response.error) - t.assert.equal(response.statusCode, 400) + t.assert.strictEqual(response.statusCode, 400) t.assert.deepEqual(JSON.parse(response.payload.toString()), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', diff --git a/test/case-insensitive.test.js b/test/case-insensitive.test.js index c54dab0ad1c..8604154b5a3 100644 --- a/test/case-insensitive.test.js +++ b/test/case-insensitive.test.js @@ -24,7 +24,7 @@ test('case insensitive', (t, done) => { url: 'http://localhost:' + fastify.server.address().port + '/FOO' }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) t.assert.deepEqual(JSON.parse(body), { hello: 'world' }) @@ -53,7 +53,7 @@ test('case insensitive inject', (t, done) => { url: 'http://localhost:' + fastify.server.address().port + '/FOO' }, (err, response) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) t.assert.deepEqual(JSON.parse(response.payload), { hello: 'world' }) @@ -71,7 +71,7 @@ test('case insensitive (parametric)', (t, done) => { t.after(() => fastify.close()) fastify.get('/foo/:param', (req, reply) => { - t.assert.equal(req.params.param, 'bAr') + t.assert.strictEqual(req.params.param, 'bAr') reply.send({ hello: 'world' }) }) @@ -83,7 +83,7 @@ test('case insensitive (parametric)', (t, done) => { url: 'http://localhost:' + fastify.server.address().port + '/FoO/bAr' }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) t.assert.deepEqual(JSON.parse(body), { hello: 'world' }) @@ -101,7 +101,7 @@ test('case insensitive (wildcard)', (t, done) => { t.after(() => fastify.close()) fastify.get('/foo/*', (req, reply) => { - t.assert.equal(req.params['*'], 'bAr/baZ') + t.assert.strictEqual(req.params['*'], 'bAr/baZ') reply.send({ hello: 'world' }) }) @@ -113,7 +113,7 @@ test('case insensitive (wildcard)', (t, done) => { url: 'http://localhost:' + fastify.server.address().port + '/FoO/bAr/baZ' }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) t.assert.deepEqual(JSON.parse(body), { hello: 'world' }) diff --git a/test/check.test.js b/test/check.test.js index bf9fd34543c..0d3dd3a60c6 100644 --- a/test/check.test.js +++ b/test/check.test.js @@ -124,7 +124,7 @@ test('serialize the response for a Bad Request error, as defined on the schema', json: true }, (err, response, body) => { t.assert.ifError(err) - t.assert.equal(response.statusCode, 400) + t.assert.strictEqual(response.statusCode, 400) t.assert.deepEqual(body, { statusCode: 400, error: 'Bad Request', diff --git a/test/childLoggerFactory.test.js b/test/child-logger-factory.test.js similarity index 98% rename from test/childLoggerFactory.test.js rename to test/child-logger-factory.test.js index 644f98be7a2..3dd5f4f417b 100644 --- a/test/childLoggerFactory.test.js +++ b/test/child-logger-factory.test.js @@ -74,7 +74,7 @@ test('req.log should be the instance returned by the factory', (t, done) => { }) fastify.get('/', (req, reply) => { - t.assert.equal(req.log, fastify.log) + t.assert.strictEqual(req.log, fastify.log) req.log.info('log message') reply.send() }) diff --git a/test/client-timeout.test.js b/test/client-timeout.test.js index 7661a1456bd..2205b2a4384 100644 --- a/test/client-timeout.test.js +++ b/test/client-timeout.test.js @@ -28,7 +28,7 @@ test('requestTimeout should return 408', (t, done) => { socket.on('data', c => (data = Buffer.concat([data, c]))) socket.on('end', () => { - t.assert.equal( + t.assert.strictEqual( data.toString('utf-8'), 'HTTP/1.1 408 Request Timeout\r\nContent-Length: 71\r\nContent-Type: application/json\r\n\r\n{"error":"Request Timeout","message":"Client Timeout","statusCode":408}' ) diff --git a/test/conditional-pino.test.js b/test/conditional-pino.test.js index 45dbc48f06b..a6705c1b926 100644 --- a/test/conditional-pino.test.js +++ b/test/conditional-pino.test.js @@ -9,7 +9,7 @@ test("pino is not require'd if logger is not passed", t => { fastify() - t.assert.equal(require.cache[require.resolve('pino')], undefined) + t.assert.strictEqual(require.cache[require.resolve('pino')], undefined) }) test("pino is require'd if logger is passed", t => { diff --git a/test/connectionTimeout.test.js b/test/connection-timeout.test.js similarity index 100% rename from test/connectionTimeout.test.js rename to test/connection-timeout.test.js diff --git a/test/imports.test.js b/test/imports.test.js index d33e01168d1..62f6d9a752c 100644 --- a/test/imports.test.js +++ b/test/imports.test.js @@ -6,12 +6,12 @@ test('should import as default', t => { t.plan(2) const fastify = require('..') t.assert.ok(fastify) - t.assert.equal(typeof fastify, 'function') + t.assert.strictEqual(typeof fastify, 'function') }) test('should import as esm', t => { t.plan(2) const { fastify } = require('..') t.assert.ok(fastify) - t.assert.equal(typeof fastify, 'function') + t.assert.strictEqual(typeof fastify, 'function') }) diff --git a/test/internals/contentTypeParser.test.js b/test/internals/content-type-parser.test.js similarity index 100% rename from test/internals/contentTypeParser.test.js rename to test/internals/content-type-parser.test.js diff --git a/test/internals/handleRequest.test.js b/test/internals/handle-request.test.js similarity index 100% rename from test/internals/handleRequest.test.js rename to test/internals/handle-request.test.js diff --git a/test/internals/hookRunner.test.js b/test/internals/hook-runner.test.js similarity index 100% rename from test/internals/hookRunner.test.js rename to test/internals/hook-runner.test.js diff --git a/test/internals/initialConfig.test.js b/test/internals/initial-config.test.js similarity index 100% rename from test/internals/initialConfig.test.js rename to test/internals/initial-config.test.js diff --git a/test/internals/reqIdGenFactory.test.js b/test/internals/req-id-gen-factory.test.js similarity index 100% rename from test/internals/reqIdGenFactory.test.js rename to test/internals/req-id-gen-factory.test.js diff --git a/test/request-header-host.test.js b/test/request-header-host.test.js index 33727b1072b..b6f259d5487 100644 --- a/test/request-header-host.test.js +++ b/test/request-header-host.test.js @@ -72,9 +72,9 @@ test('Return 200 when Host header is empty', (t, done) => { t.after(() => fastify.close()) fastify.get('/', async function (request) { - t.assert.equal(request.host, '') - t.assert.equal(request.hostname, '') - t.assert.equal(request.port, null) + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) return { ok: true } }) fastify.listen({ port: 0 }, err => { @@ -104,9 +104,9 @@ test('Return 200 when Host header is empty with trust proxy', (t, done) => { t.after(() => fastify.close()) fastify.get('/', async function (request) { - t.assert.equal(request.host, '') - t.assert.equal(request.hostname, '') - t.assert.equal(request.port, null) + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) return { ok: true } }) fastify.listen({ port: 0 }, err => { @@ -140,9 +140,9 @@ test('Return 200 when Host header is missing and http.requireHostHeader = false' t.after(() => fastify.close()) fastify.get('/', async function (request) { - t.assert.equal(request.host, '') - t.assert.equal(request.hostname, '') - t.assert.equal(request.port, null) + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) return { ok: true } }) fastify.listen({ port: 0 }, err => { @@ -175,9 +175,9 @@ test('Return 200 when Host header is missing and http.requireHostHeader = false t.after(() => fastify.close()) fastify.get('/', async function (request) { - t.assert.equal(request.host, '') - t.assert.equal(request.hostname, '') - t.assert.equal(request.port, null) + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) return { ok: true } }) fastify.listen({ port: 0 }, err => { diff --git a/test/server.test.js b/test/server.test.js index b3f3b361060..1247c77a4d0 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -85,7 +85,7 @@ test('abort signal', async t => { await t.test('listen should not start server', (t, end) => { t.plan(2) function onClose (instance, done) { - t.assert.equal(instance, fastify) + t.assert.strictEqual(instance, fastify) done() end() } @@ -103,7 +103,7 @@ test('abort signal', async t => { await t.test('listen should not start server if already aborted', (t, end) => { t.plan(2) function onClose (instance, done) { - t.assert.equal(instance, fastify) + t.assert.strictEqual(instance, fastify) done() end() } @@ -134,21 +134,19 @@ test('abort signal', async t => { }) }) -test('#5180 - preClose should be called before closing secondary server', (t, end) => { +test('#5180 - preClose should be called before closing secondary server', async (t) => { t.plan(2) const fastify = Fastify({ forceCloseConnections: true }) let flag = false t.after(() => fastify.close()) - fastify.addHook('preClose', async () => { + fastify.addHook('preClose', () => { flag = true }) fastify.get('/', async (req, reply) => { - await new Promise((resolve) => { - setTimeout(() => resolve(1), 1000) - }) - + // request will be pending for 1 second to simulate a slow request + await new Promise((resolve) => { setTimeout(resolve, 1000) }) return { hello: 'world' } }) @@ -177,12 +175,14 @@ test('#5180 - preClose should be called before closing secondary server', (t, en () => { t.assert.fail('Request should not succeed') }, () => { t.assert.ok(flag) - end() } ) - setTimeout(() => { - fastify.close() - }, 250) + // Close the server while the slow request is pending + setTimeout(fastify.close, 250) }) + + // Wait 1000ms to ensure that the test is finished and async operations are + // completed + await new Promise((resolve) => { setTimeout(resolve, 1000) }) }) From 8a7f128c4fa90bc40605e8e93b9b9a5c44afbb49 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Fri, 1 Nov 2024 19:16:38 +0000 Subject: [PATCH 0817/1295] ci(benchmark): fix github token input (#5809) --- .github/workflows/benchmark-parser.yml | 2 +- .github/workflows/benchmark.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index ea316d43c3e..e4e9414279a 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -72,7 +72,7 @@ jobs: - name: Comment PR uses: thollander/actions-comment-pull-request@v3 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} message: | **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 65b0092338f..82ebbe61b28 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -72,7 +72,7 @@ jobs: - name: Comment PR uses: thollander/actions-comment-pull-request@v3 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} message: | **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} From fb169eb206586b60bf9bc3802110900020980ea7 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Fri, 1 Nov 2024 20:18:10 +0100 Subject: [PATCH 0818/1295] test: migrated reply-code.test.js from tap to node:test (#5808) --- test/reply-code.test.js | 64 +++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/test/reply-code.test.js b/test/reply-code.test.js index 13724da6ed5..b12e42d64b9 100644 --- a/test/reply-code.test.js +++ b/test/reply-code.test.js @@ -1,11 +1,10 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const { Readable } = require('stream') -const test = t.test const Fastify = require('..') -test('code should handle null/undefined/float', t => { +test('code should handle null/undefined/float', (t, done) => { t.plan(8) const fastify = Fastify() @@ -26,9 +25,9 @@ test('code should handle null/undefined/float', t => { method: 'GET', url: '/null' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 500) - t.same(res.json(), { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { statusCode: 500, code: 'FST_ERR_BAD_STATUS_CODE', error: 'Internal Server Error', @@ -40,9 +39,9 @@ test('code should handle null/undefined/float', t => { method: 'GET', url: '/undefined' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 500) - t.same(res.json(), { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { statusCode: 500, code: 'FST_ERR_BAD_STATUS_CODE', error: 'Internal Server Error', @@ -54,12 +53,13 @@ test('code should handle null/undefined/float', t => { method: 'GET', url: '/404.5' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('code should handle 204', t => { +test('code should handle 204', (t, done) => { t.plan(13) const fastify = Fastify() @@ -80,7 +80,7 @@ test('code should handle 204', t => { } }) stream.on('end', () => { - t.pass('stream ended') + t.assert.ok('stream ended') }) reply.status(204).send(stream) }) @@ -89,34 +89,35 @@ test('code should handle 204', t => { method: 'GET', url: '/204' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 204) - t.equal(res.payload, '') - t.equal(res.headers['content-length'], undefined) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 204) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) }) fastify.inject({ method: 'GET', url: '/undefined/204' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 204) - t.equal(res.payload, '') - t.equal(res.headers['content-length'], undefined) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 204) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) }) fastify.inject({ method: 'GET', url: '/stream/204' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 204) - t.equal(res.payload, '') - t.equal(res.headers['content-length'], undefined) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 204) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) + done() }) }) -test('code should handle onSend hook on 204', t => { +test('code should handle onSend hook on 204', (t, done) => { t.plan(5) const fastify = Fastify() @@ -137,10 +138,11 @@ test('code should handle onSend hook on 204', t => { method: 'GET', url: '/204' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 204) - t.equal(res.payload, '') - t.equal(res.headers['content-length'], undefined) - t.equal(res.headers['content-type'], undefined) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 204) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) + t.assert.strictEqual(res.headers['content-type'], undefined) + done() }) }) From 9b636073ba6ee9e68a0cbba2c89797dc0c9758ba Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 2 Nov 2024 17:48:27 +0100 Subject: [PATCH 0819/1295] test: migrated middleware.test.js from tap to node:test (#5795) * test: migrated middleware.test.js from tap to node:test * test: improvement test Co-authored-by: Dan Castillo Signed-off-by: Antonio Tripodi * chore: update test middleware.test.js Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> Signed-off-by: Antonio Tripodi --------- Signed-off-by: Antonio Tripodi Co-authored-by: Dan Castillo Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> Co-authored-by: Aras Abbasi --- test/middleware.test.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/middleware.test.js b/test/middleware.test.js index 990c505d22c..05da9566735 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') const { FST_ERR_DEC_ALREADY_PRESENT @@ -10,7 +10,7 @@ test('Should be able to override the default use API', t => { t.plan(1) const fastify = Fastify() fastify.decorate('use', () => true) - t.equal(fastify.use(), true) + t.assert.strictEqual(fastify.use(), true) }) test('Cannot decorate use twice', t => { @@ -20,17 +20,16 @@ test('Cannot decorate use twice', t => { try { fastify.decorate('use', () => true) } catch (err) { - t.ok(err instanceof FST_ERR_DEC_ALREADY_PRESENT) + t.assert.ok(err instanceof FST_ERR_DEC_ALREADY_PRESENT) } }) test('Encapsulation works', t => { - t.plan(1) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.decorate('use', () => true) - t.equal(instance.use(), true) + t.assert.strictEqual(instance.use(), true) done() }) From 0b3a2c30b26fb351032898cd2c67d8ea71709c9c Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Sun, 3 Nov 2024 02:40:43 -0500 Subject: [PATCH 0820/1295] fix: test asserts to strict asserts (#5815) --- test/allow-unsafe-regex.test.js | 2 +- test/buffer.test.js | 4 ++-- test/case-insensitive.test.js | 8 ++++---- test/check.test.js | 3 +-- test/conditional-pino.test.js | 4 ++-- test/http-methods/head.test.js | 3 --- test/request-timeout.test.js | 4 ++-- test/route.3.test.js | 5 +++-- 8 files changed, 15 insertions(+), 18 deletions(-) diff --git a/test/allow-unsafe-regex.test.js b/test/allow-unsafe-regex.test.js index 6b9b7848120..5f48eed8c37 100644 --- a/test/allow-unsafe-regex.test.js +++ b/test/allow-unsafe-regex.test.js @@ -112,7 +112,7 @@ test('allow unsafe regex allow unsafe', (t, done) => { }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 200) - t.assert.deepEqual(JSON.parse(body), { + t.assert.deepStrictEqual(JSON.parse(body), { foo: '1234' }) done() diff --git a/test/buffer.test.js b/test/buffer.test.js index c06d634a61b..2f09456ead3 100644 --- a/test/buffer.test.js +++ b/test/buffer.test.js @@ -25,7 +25,7 @@ test('Buffer test', async t => { t.assert.ifError(response.error) t.assert.strictEqual(response.statusCode, 200) - t.assert.deepEqual(response.payload.toString(), '{"hello":"world"}') + t.assert.deepStrictEqual(response.payload.toString(), '{"hello":"world"}') }) await test('should return 400 if the body is empty', async t => { @@ -42,7 +42,7 @@ test('Buffer test', async t => { t.assert.ifError(response.error) t.assert.strictEqual(response.statusCode, 400) - t.assert.deepEqual(JSON.parse(response.payload.toString()), { + t.assert.deepStrictEqual(JSON.parse(response.payload.toString()), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', diff --git a/test/case-insensitive.test.js b/test/case-insensitive.test.js index 8604154b5a3..b71c34db359 100644 --- a/test/case-insensitive.test.js +++ b/test/case-insensitive.test.js @@ -25,7 +25,7 @@ test('case insensitive', (t, done) => { }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 200) - t.assert.deepEqual(JSON.parse(body), { + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) done() @@ -54,7 +54,7 @@ test('case insensitive inject', (t, done) => { }, (err, response) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 200) - t.assert.deepEqual(JSON.parse(response.payload), { + t.assert.deepStrictEqual(JSON.parse(response.payload), { hello: 'world' }) done() @@ -84,7 +84,7 @@ test('case insensitive (parametric)', (t, done) => { }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 200) - t.assert.deepEqual(JSON.parse(body), { + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) done() @@ -114,7 +114,7 @@ test('case insensitive (wildcard)', (t, done) => { }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 200) - t.assert.deepEqual(JSON.parse(body), { + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) done() diff --git a/test/check.test.js b/test/check.test.js index 0d3dd3a60c6..e932b4a05f7 100644 --- a/test/check.test.js +++ b/test/check.test.js @@ -72,7 +72,6 @@ const options = { } const handler = (request, reply) => { - console.log('in handler') if (request.body.id === '400') { return reply.status(400).send({ statusCode: 400, @@ -125,7 +124,7 @@ test('serialize the response for a Bad Request error, as defined on the schema', }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 400) - t.assert.deepEqual(body, { + t.assert.deepStrictEqual(body, { statusCode: 400, error: 'Bad Request', message: 'body must be object' diff --git a/test/conditional-pino.test.js b/test/conditional-pino.test.js index a6705c1b926..4bce8eb9c83 100644 --- a/test/conditional-pino.test.js +++ b/test/conditional-pino.test.js @@ -21,7 +21,7 @@ test("pino is require'd if logger is passed", t => { logger: true }) - t.assert.notEqual(require.cache[require.resolve('pino')], undefined) + t.assert.notStrictEqual(require.cache[require.resolve('pino')], undefined) }) test("pino is require'd if loggerInstance is passed", t => { @@ -43,5 +43,5 @@ test("pino is require'd if loggerInstance is passed", t => { loggerInstance }) - t.assert.notEqual(require.cache[require.resolve('pino')], undefined) + t.assert.notStrictEqual(require.cache[require.resolve('pino')], undefined) }) diff --git a/test/http-methods/head.test.js b/test/http-methods/head.test.js index f9d39b58bfc..0b4d169a3dc 100644 --- a/test/http-methods/head.test.js +++ b/test/http-methods/head.test.js @@ -122,7 +122,6 @@ test('shorthand - should set get and head route in the same api call', t => { t.assert.ok(true) } catch (e) { - console.log(e) t.assert.fail() } }) @@ -147,7 +146,6 @@ test('shorthand - head, querystring schema', t => { }) t.assert.ok(true) } catch (e) { - console.log(e) t.assert.fail() } }) @@ -160,7 +158,6 @@ test('missing schema - head', t => { }) t.assert.ok(true) } catch (e) { - console.log(e) t.assert.fail() } }) diff --git a/test/request-timeout.test.js b/test/request-timeout.test.js index faf5455dc24..995dc384043 100644 --- a/test/request-timeout.test.js +++ b/test/request-timeout.test.js @@ -42,12 +42,12 @@ test('requestTimeout should be set', async (t) => { t.plan(1) const initialConfig = Fastify({ requestTimeout: 5000 }).initialConfig - t.assert.deepEqual(initialConfig.requestTimeout, 5000) + t.assert.strictEqual(initialConfig.requestTimeout, 5000) }) test('requestTimeout should 0', async (t) => { t.plan(1) const initialConfig = Fastify().initialConfig - t.assert.deepEqual(initialConfig.requestTimeout, 0) + t.assert.strictEqual(initialConfig.requestTimeout, 0) }) diff --git a/test/route.3.test.js b/test/route.3.test.js index f329ff8e4f1..d324b972bef 100644 --- a/test/route.3.test.js +++ b/test/route.3.test.js @@ -5,7 +5,7 @@ const joi = require('joi') const Fastify = require('..') test('does not mutate joi schemas', (t, done) => { - t.plan(4) + t.plan(5) const fastify = Fastify() function validatorCompiler ({ schema, method, url, httpPart }) { @@ -31,7 +31,8 @@ test('does not mutate joi schemas', (t, done) => { params: { an_id: joi.number() } }, handler (req, res) { - t.assert.deepEqual(req.params, { an_id: 42 }) + t.assert.strictEqual(Object.keys(req.params).length, 1) + t.assert.strictEqual(req.params.an_id, '42') res.send({ hello: 'world' }) } }) From f9bd013abf1518ad33f5d80a37df009026a781ed Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 3 Nov 2024 12:39:33 +0100 Subject: [PATCH 0821/1295] chore: renamed files in kebab-case (#5814) Signed-off-by: Antonio Tripodi --- test/{wrapThenable.test.js => wrap-thenable.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{wrapThenable.test.js => wrap-thenable.test.js} (100%) diff --git a/test/wrapThenable.test.js b/test/wrap-thenable.test.js similarity index 100% rename from test/wrapThenable.test.js rename to test/wrap-thenable.test.js From c8286ea22e3709cdc7d8f217801836fedc90fd1e Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 3 Nov 2024 12:39:26 +0000 Subject: [PATCH 0822/1295] style(.gitattributes): standardise style across fastify repos (#5816) Signed-off-by: Frazer Smith --- .gitattributes | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitattributes b/.gitattributes index cad1c32e3de..a0e7df931f9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,2 @@ -# Set the default behavior, in case people don't have core.autocrlf set -* text=auto - -# Require Unix line endings -* text eol=lf +# Set default behavior to automatically convert line endings +* text=auto eol=lf From 6f7b78191111ba1336c1b0cd6efe14e4bb7873ab Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 3 Nov 2024 13:35:34 +0000 Subject: [PATCH 0823/1295] style: remove trailing whitespace (#5817) --- .github/workflows/benchmark-parser.yml | 4 +- .github/workflows/benchmark.yml | 4 +- .github/workflows/md-lint.yml | 2 +- CONTRIBUTING.md | 2 +- EXPENSE_POLICY.md | 2 +- docs/Guides/Database.md | 30 +++---- docs/Guides/Detecting-When-Clients-Abort.md | 56 ++++++------ docs/Guides/Ecosystem.md | 8 +- docs/Guides/Index.md | 2 +- docs/Guides/Migration-Guide-V4.md | 22 ++--- docs/Guides/Migration-Guide-V5.md | 10 +-- docs/Guides/Plugins-Guide.md | 2 +- docs/Guides/Prototype-Poisoning.md | 6 +- docs/Guides/Recommendations.md | 18 ++-- docs/Guides/Serverless.md | 10 +-- docs/Guides/Testing.md | 16 ++-- docs/Guides/Write-Plugin.md | 2 +- docs/Guides/Write-Type-Provider.md | 6 +- docs/Reference/Decorators.md | 4 +- docs/Reference/Errors.md | 4 +- docs/Reference/Hooks.md | 14 +-- docs/Reference/Reply.md | 94 ++++++++++----------- docs/Reference/Request.md | 78 ++++++++--------- docs/Reference/Routes.md | 14 +-- docs/Reference/Server.md | 36 ++++---- docs/Reference/TypeScript.md | 4 +- lib/error-serializer.js | 26 +++--- test/bundler/README.md | 10 +-- 28 files changed, 243 insertions(+), 243 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index e4e9414279a..886c069c7c0 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -77,9 +77,9 @@ jobs: **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} - + --- - + **Node**: 22 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-22 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-22 }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 82ebbe61b28..108988f0504 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -77,9 +77,9 @@ jobs: **Node**: 20 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} - + --- - + **Node**: 22 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-22 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-22 }} diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index 95862f259f0..97e5facc1c4 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4 with: persist-credentials: false - + - name: Setup Node uses: actions/setup-node@v4 with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e246e21b67c..387760a75a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,7 +107,7 @@ the following tasks: 5. The person that does the onboarding must add you to the [npm org](https://www.npmjs.com/org/fastify), so that you can help maintaining the official plugins. -6. Optionally, the person can be added as an Open Collective member +6. Optionally, the person can be added as an Open Collective member by the lead team. ### Offboarding Collaborators diff --git a/EXPENSE_POLICY.md b/EXPENSE_POLICY.md index 5e5bb7867fa..3baa6884267 100644 --- a/EXPENSE_POLICY.md +++ b/EXPENSE_POLICY.md @@ -98,7 +98,7 @@ To claim a bounty: - The expense will be validated by a lead maintainer and then the payment will be processed by Open Collective -If the Open Collective budget is insufficient, the expense will be rejected. +If the Open Collective budget is insufficient, the expense will be rejected. Unclaimed bounties are available for other issues. [submit]: https://opencollective.com/fastify/expenses/new diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md index 3369b33498c..72449133f16 100644 --- a/docs/Guides/Database.md +++ b/docs/Guides/Database.md @@ -2,17 +2,17 @@ ## Database -Fastify's ecosystem provides a handful of -plugins for connecting to various database engines. -This guide covers engines that have Fastify +Fastify's ecosystem provides a handful of +plugins for connecting to various database engines. +This guide covers engines that have Fastify plugins maintained within the Fastify organization. -> If a plugin for your database of choice does not exist -> you can still use the database as Fastify is database agnostic. -> By following the examples of the database plugins listed in this guide, -> a plugin can be written for the missing database engine. +> If a plugin for your database of choice does not exist +> you can still use the database as Fastify is database agnostic. +> By following the examples of the database plugins listed in this guide, +> a plugin can be written for the missing database engine. -> If you would like to write your own Fastify plugin +> If you would like to write your own Fastify plugin > please take a look at the [plugins guide](./Plugins-Guide.md) ### [MySQL](https://github.com/fastify/fastify-mysql) @@ -104,8 +104,8 @@ fastify.listen({ port: 3000 }, err => { }) ``` -By default `@fastify/redis` doesn't close -the client connection when Fastify server shuts down. +By default `@fastify/redis` doesn't close +the client connection when Fastify server shuts down. To opt-in to this behavior, register the client like so: ```javascript @@ -126,7 +126,7 @@ fastify.register(require('@fastify/mongodb'), { // force to close the mongodb connection when app stopped // the default value is false forceClose: true, - + url: 'mongodb://mongo/mydb' }) @@ -178,8 +178,8 @@ fastify.listen({ port: 3000 }, err => { ``` ### Writing plugin for a database library -We could write a plugin for a database -library too (e.g. Knex, Prisma, or TypeORM). +We could write a plugin for a database +library too (e.g. Knex, Prisma, or TypeORM). We will use [Knex](https://knexjs.org/) in our example. ```javascript @@ -281,7 +281,7 @@ async function migrate() { const client = new pg.Client({ host: 'localhost', port: 5432, - database: 'example', + database: 'example', user: 'example', password: 'example', }); @@ -313,7 +313,7 @@ async function migrate() { console.error(err) process.exitCode = 1 } - + await client.end() } diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md index 644f3f6f747..e2771013b7a 100644 --- a/docs/Guides/Detecting-When-Clients-Abort.md +++ b/docs/Guides/Detecting-When-Clients-Abort.md @@ -4,30 +4,30 @@ ## Introduction -Fastify provides request events to trigger at certain points in a request's -lifecycle. However, there isn't a built-in mechanism to -detect unintentional client disconnection scenarios such as when the client's +Fastify provides request events to trigger at certain points in a request's +lifecycle. However, there isn't a built-in mechanism to +detect unintentional client disconnection scenarios such as when the client's internet connection is interrupted. This guide covers methods to detect if and when a client intentionally aborts a request. -Keep in mind, Fastify's `clientErrorHandler` is not designed to detect when a -client aborts a request. This works in the same way as the standard Node HTTP -module, which triggers the `clientError` event when there is a bad request or -exceedingly large header data. When a client aborts a request, there is no +Keep in mind, Fastify's `clientErrorHandler` is not designed to detect when a +client aborts a request. This works in the same way as the standard Node HTTP +module, which triggers the `clientError` event when there is a bad request or +exceedingly large header data. When a client aborts a request, there is no error on the socket and the `clientErrorHandler` will not be triggered. ## Solution ### Overview -The proposed solution is a possible way of detecting when a client -intentionally aborts a request, such as when a browser is closed or the HTTP -request is aborted from your client application. If there is an error in your -application code that results in the server crashing, you may require +The proposed solution is a possible way of detecting when a client +intentionally aborts a request, such as when a browser is closed or the HTTP +request is aborted from your client application. If there is an error in your +application code that results in the server crashing, you may require additional logic to avoid a false abort detection. -The goal here is to detect when a client intentionally aborts a connection -so your application logic can proceed accordingly. This can be useful for +The goal here is to detect when a client intentionally aborts a connection +so your application logic can proceed accordingly. This can be useful for logging purposes or halting business logic. ### Hands-on @@ -78,10 +78,10 @@ const start = async () => { start() ``` -Our code is setting up a Fastify server which includes the following +Our code is setting up a Fastify server which includes the following functionality: -- Accepting requests at http://localhost:3000, with a 3 second delayed response +- Accepting requests at http://localhost:3000, with a 3 second delayed response of `{ ok: true }`. - An onRequest hook that triggers when every request is received. - Logic that triggers in the hook when the request is closed. @@ -108,7 +108,7 @@ app.get('/', async (request, reply) => { }) ``` -At any point in your business logic, you can check if the request has been +At any point in your business logic, you can check if the request has been aborted and perform alternative actions. ```js @@ -122,14 +122,14 @@ app.get('/', async (request, reply) => { }) ``` -A benefit to adding this in your application code is that you can log Fastify -details such as the reqId, which may be unavailable in lower-level code that +A benefit to adding this in your application code is that you can log Fastify +details such as the reqId, which may be unavailable in lower-level code that only has access to the raw request information. ### Testing -To test this functionality you can use an app like Postman and cancel your -request within 3 seconds. Alternatively, you can use Node to send an HTTP +To test this functionality you can use an app like Postman and cancel your +request within 3 seconds. Alternatively, you can use Node to send an HTTP request with logic to abort the request before 3 seconds. Example: ```js @@ -151,7 +151,7 @@ setTimeout(() => { }, 1000); ``` -With either approach, you should see the Fastify log appear at the moment the +With either approach, you should see the Fastify log appear at the moment the request is aborted. ## Conclusion @@ -160,13 +160,13 @@ Specifics of the implementation will vary from one problem to another, but the main goal of this guide was to show a very specific use case of an issue that could be solved within Fastify's ecosystem. -You can listen to the request close event and determine if the request was -aborted or if it was successfully delivered. You can implement this solution +You can listen to the request close event and determine if the request was +aborted or if it was successfully delivered. You can implement this solution in an onRequest hook or directly in an individual route. -This approach will not trigger in the event of internet disruption, and such -detection would require additional business logic. If you have flawed backend -application logic that results in a server crash, then you could trigger a -false detection. The `clientErrorHandler`, either by default or with custom -logic, is not intended to handle this scenario and will not trigger when the +This approach will not trigger in the event of internet disruption, and such +detection would require additional business logic. If you have flawed backend +application logic that results in a server crash, then you could trigger a +false detection. The `clientErrorHandler`, either by default or with custom +logic, is not intended to handle this scenario and will not trigger when the client aborts a request. diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 81e4ed99f1a..bee56704fe4 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -249,7 +249,7 @@ section. plugin to authenticate HTTP requests based on API key and signature - [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify Plugin for interacting with Appwrite server. -- [`fastify-asyncforge`](https://github.com/mcollina/fastify-asyncforge) Plugin +- [`fastify-asyncforge`](https://github.com/mcollina/fastify-asyncforge) Plugin to access Fastify instance, logger, request and reply from Node.js [Async Local Storage](https://nodejs.org/api/async_context.html#class-asynclocalstorage). - [`fastify-at-mysql`](https://github.com/mateonunez/fastify-at-mysql) Fastify @@ -277,7 +277,7 @@ section. development servers that require Babel transformations of JavaScript sources. - [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash generator & checker. -- [`fastify-better-sqlite3`](https://github.com/punkish/fastify-better-sqlite3) +- [`fastify-better-sqlite3`](https://github.com/punkish/fastify-better-sqlite3) Plugin for better-sqlite3. - [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your routes to the console, so you definitely know which endpoints are available. @@ -289,7 +289,7 @@ section. to add [bree](https://github.com/breejs/bree) support. - [`fastify-bugsnag`](https://github.com/ZigaStrgar/fastify-bugsnag) Fastify plugin to add support for [Bugsnag](https://www.bugsnag.com/) error reporting. -- [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman) +- [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman) Small and efficient cache provider for Node.js with In-memory, File, Redis and MongoDB engines for Fastify - [`fastify-casbin`](https://github.com/nearform/fastify-casbin) Casbin support @@ -344,7 +344,7 @@ section. - [`fastify-event-bus`](https://github.com/Shiva127/fastify-event-bus) Event bus support for Fastify. Built upon [js-event-bus](https://github.com/bcerati/js-event-bus). - [`fastify-evervault`](https://github.com/Briscoooe/fastify-evervault/) Fastify - plugin for instantiating and encapsulating the + plugin for instantiating and encapsulating the [Evervault](https://evervault.com/) client. - [`fastify-explorer`](https://github.com/Eomm/fastify-explorer) Get control of your decorators across all the encapsulated contexts. diff --git a/docs/Guides/Index.md b/docs/Guides/Index.md index 9e80b166055..a26fac6fecb 100644 --- a/docs/Guides/Index.md +++ b/docs/Guides/Index.md @@ -15,7 +15,7 @@ This table of contents is in alphabetical order. met in your application. This guide focuses on solving the problem using [`Hooks`](../Reference/Hooks.md), [`Decorators`](../Reference/Decorators.md), and [`Plugins`](../Reference/Plugins.md). -+ [Detecting When Clients Abort](./Detecting-When-Clients-Abort.md): A ++ [Detecting When Clients Abort](./Detecting-When-Clients-Abort.md): A practical guide on detecting if and when a client aborts a request. + [Ecosystem](./Ecosystem.md): Lists all core plugins and many known community plugins. diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index ccabda5f044..0049f439544 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -9,13 +9,13 @@ work after upgrading. ## Codemods ### Fastify v4 Codemods -To help with the upgrade, we’ve worked with the team at +To help with the upgrade, we’ve worked with the team at [Codemod](https://github.com/codemod-com/codemod) to -publish codemods that will automatically update your code to many of +publish codemods that will automatically update your code to many of the new APIs and patterns in Fastify v4. -Run the following -[migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to +Run the following +[migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to automatically update your code to Fastify v4: ``` @@ -30,7 +30,7 @@ This will run the following codemods: - [`fastify/4/await-register-calls`](https://go.codemod.com/fastify-4-await-register-calls) Each of these codemods automates the changes listed in the v4 migration guide. -For a complete list of available Fastify codemods and further details, +For a complete list of available Fastify codemods and further details, see [Codemod Registry](https://go.codemod.com/fastify). @@ -52,14 +52,14 @@ fastify.register(async fastify => { console.log(err.message) // 'kaboom' throw new Error('caught') }) - + fastify.get('/encapsulated', async () => { throw new Error('kaboom') }) }) fastify.setErrorHandler(async err => { - console.log(err.message) // 'caught' + console.log(err.message) // 'caught' throw new Error('wrapped') }) @@ -67,10 +67,10 @@ const res = await fastify.inject('/encapsulated') console.log(res.json().message) // 'wrapped' ``` ->The root error handler is Fastify’s generic error handler. ->This error handler will use the headers and status code in the Error object, +>The root error handler is Fastify’s generic error handler. +>This error handler will use the headers and status code in the Error object, >if they exist. **The headers and status code will not be automatically set if ->a custom error handler is provided**. +>a custom error handler is provided**. ### Removed `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506)) @@ -242,7 +242,7 @@ As such, schemas like below will need to be changed from: properties: { api_key: { type: 'string' }, image: { type: ['object', 'array'] } - } + } } ``` diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index 7848bdb1ed3..a288215e1c7 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -159,7 +159,7 @@ the following: +++ b/index.ts @@ -11,7 +11,8 @@ import { import { FromSchema, FromSchemaDefaultOptions, FromSchemaOptions, JSONSchema } from 'json-schema-to-ts' - + export interface JsonSchemaToTsProvider< Options extends FromSchemaOptions = FromSchemaDefaultOptions > extends FastifyTypeProvider { @@ -298,7 +298,7 @@ use the `constraints` option instead. We have a more strict requirement for custom `HEAD` route when `exposeHeadRoutes: true`. -When you provides a custom `HEAD` route, you must either explicitly +When you provides a custom `HEAD` route, you must either explicitly set `exposeHeadRoutes` to `false` ```js @@ -403,7 +403,7 @@ and requires the route definition to be passed as it is defined in the route. fastify.get('/example/:file(^\\d+).png', function (request, reply) { }) console.log(fastify.hasRoute({ - method: 'GET', + method: 'GET', url: '/example/12345.png' )); // true ``` @@ -414,7 +414,7 @@ console.log(fastify.hasRoute({ fastify.get('/example/:file(^\\d+).png', function (request, reply) { }) console.log(fastify.hasRoute({ - method: 'GET', + method: 'GET', url: '/example/:file(^\\d+).png' )); // true ``` @@ -480,7 +480,7 @@ or as a getter ```js // v5 fastify.decorateRequest('myObject', { - getter () { + getter () { return { hello: 'world' } } }); diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index ae5e7d6f599..4fc8821b3ae 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -316,7 +316,7 @@ based on a [route config option](../Reference/Routes.md#routes-options): ```js fastify.register((instance, opts, done) => { instance.decorate('util', (request, key, value) => { request[key] = value }) - + function handler(request, reply, done) { instance.util(request, 'timestamp', new Date()) done() diff --git a/docs/Guides/Prototype-Poisoning.md b/docs/Guides/Prototype-Poisoning.md index c2ff2f1cc31..0d01aa90477 100644 --- a/docs/Guides/Prototype-Poisoning.md +++ b/docs/Guides/Prototype-Poisoning.md @@ -8,7 +8,7 @@ Based on the article by Eran Hammer,the issue is created by a web security bug. -It is also a perfect illustration of the efforts required to maintain +It is also a perfect illustration of the efforts required to maintain open-source software and the limitations of existing communication channels. But first, if we use a JavaScript framework to process incoming JSON data, take @@ -16,7 +16,7 @@ a moment to read up on [Prototype Poisoning](https://medium.com/intrinsic/javasc in general, and the specific [technical details](https://github.com/hapijs/hapi/issues/3916) of this issue. This could be a critical issue so, we might need to verify your own code first. -It focuses on specific framework however, any solution that uses `JSON.parse()` +It focuses on specific framework however, any solution that uses `JSON.parse()` to process external data is potentially at risk. ### BOOM @@ -42,7 +42,7 @@ defect a validation library can have. To understand this, we need to understand how JavaScript works a bit. Every object in JavaScript can have a prototype. It is a set of methods and -properties it "inherits" from another object. I have put inherits in quotes +properties it "inherits" from another object. I have put inherits in quotes because JavaScript isn't really an object-oriented language. It is a prototype- based object-oriented language. diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index 58e921d6149..3e23be6224a 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -307,22 +307,22 @@ readinessProbe: ## Capacity Planning For Production -In order to rightsize the production environment for your Fastify application, -it is highly recommended that you perform your own measurements against +In order to rightsize the production environment for your Fastify application, +it is highly recommended that you perform your own measurements against different configurations of the environment, which may use real CPU cores, virtual CPU cores (vCPU), or even fractional vCPU cores. We will use the term vCPU throughout this recommendation to represent any CPU type. -Tools such as [k6](https://github.com/grafana/k6) +Tools such as [k6](https://github.com/grafana/k6) or [autocannon](https://github.com/mcollina/autocannon) can be used for conducting the necessary performance tests. That said, you may also consider the following as a rule of thumb: -* To have the lowest possible latency, 2 vCPU are recommended per app -instance (e.g., a k8s pod). The second vCPU will mostly be used by the -garbage collector (GC) and libuv threadpool. This will minimize the latency +* To have the lowest possible latency, 2 vCPU are recommended per app +instance (e.g., a k8s pod). The second vCPU will mostly be used by the +garbage collector (GC) and libuv threadpool. This will minimize the latency for your users, as well as the memory usage, as the GC will be run more frequently. Also, the main thread won't have to stop to let the GC run. @@ -330,7 +330,7 @@ frequently. Also, the main thread won't have to stop to let the GC run. requests per second per vCPU available), consider using a smaller amount of vCPUs per app instance. It is totally fine to run Node.js applications with 1 vCPU. -* You may experiment with an even smaller amount of vCPU, which may provide +* You may experiment with an even smaller amount of vCPU, which may provide even better throughput in certain use-cases. There are reports of API gateway solutions working well with 100m-200m vCPU in Kubernetes. @@ -347,7 +347,7 @@ would be exposing metrics endpoints on a separate port, to prevent public access, when using a reverse proxy or an ingress firewall is not an option. -It is perfectly fine to spin up several Fastify instances within the same -Node.js process and run them concurrently, even in high load systems. +It is perfectly fine to spin up several Fastify instances within the same +Node.js process and run them concurrently, even in high load systems. Each Fastify instance only generates as much load as the traffic it receives, plus the memory used for that Fastify instance. diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index be434b967f8..454d77bbae7 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -36,10 +36,10 @@ snippet of code. To integrate with AWS, you have two choices of library: -- Using [@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify) +- Using [@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify) which only adds API Gateway support but has heavy optimizations for fastify. -- Using [@h4ad/serverless-adapter](https://github.com/H4ad/serverless-adapter) - which is a little slower as it creates an HTTP request for each AWS event but +- Using [@h4ad/serverless-adapter](https://github.com/H4ad/serverless-adapter) + which is a little slower as it creates an HTTP request for each AWS event but has support for more AWS services such as: AWS SQS, AWS SNS and others. So you can decide which option is best for you, but you can test both libraries. @@ -263,7 +263,7 @@ curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \ ## Google Firebase Functions -Follow this guide if you want to use Fastify as the HTTP framework for +Follow this guide if you want to use Fastify as the HTTP framework for Firebase Functions instead of the vanilla JavaScript router provided with `onRequest(async (req, res) => {}`. @@ -280,7 +280,7 @@ const { onRequest } = require("firebase-functions/v2/https") ### Creation of Fastify instance Create the Fastify instance and encapsulate the returned application instance -in a function which will register routes, await the server's processing of +in a function which will register routes, await the server's processing of plugins, hooks and other settings. As follows: ```js diff --git a/docs/Guides/Testing.md b/docs/Guides/Testing.md index 1dfc59247f6..3fb40816600 100644 --- a/docs/Guides/Testing.md +++ b/docs/Guides/Testing.md @@ -264,7 +264,7 @@ test('should work with undici', async t => { 'http://localhost:' + fastify.server.address().port, { keepAliveTimeout: 10, keepAliveMaxTimeout: 10 - } + } ) t.after(() => { @@ -279,8 +279,8 @@ test('should work with undici', async t => { }) ``` -Alternatively, starting with Node.js 18, -[`fetch`](https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch) +Alternatively, starting with Node.js 18, +[`fetch`](https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch) may be used without requiring any extra dependencies: **test-listen.js** @@ -296,7 +296,7 @@ test('should work with fetch', async t => { t.after(() => fastify.close()) await fastify.listen() - + const response = await fetch( 'http://localhost:' + fastify.server.address().port ) @@ -386,7 +386,7 @@ test("Test the Plugin Route", async t => { fastify.register(myPlugin) - // Add an endpoint of your choice + // Add an endpoint of your choice fastify.get("/", async (request, reply) => { return ({ message: request.helloRequest }) }) @@ -396,7 +396,7 @@ test("Test the Plugin Route", async t => { method: "GET", url: "/" }) - + console.log('status code: ', fastifyResponse.statusCode) console.log('body: ', fastifyResponse.body) }) @@ -440,7 +440,7 @@ test("Test the Plugin Route", async t => { method: "GET", url: "/" }) - + t.assert.strictEqual(fastifyResponse.statusCode, 200) t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" }) }) @@ -465,7 +465,7 @@ test("Test the Plugin Route", async t => { fastify.get("/", async (request, reply) => { // Testing the fastify decorators - t.assert.ifError(request.helloRequest) + t.assert.ifError(request.helloRequest) t.assert.ok(request.helloRequest, "Hello World") t.assert.ok(fastify.helloInstance, "Hello Fastify Instance") return ({ message: request.helloRequest }) diff --git a/docs/Guides/Write-Plugin.md b/docs/Guides/Write-Plugin.md index cc5b70266be..00eee854673 100644 --- a/docs/Guides/Write-Plugin.md +++ b/docs/Guides/Write-Plugin.md @@ -63,7 +63,7 @@ among different versions of its dependencies. We do not enforce any testing library. We use [`node:test`](https://nodejs.org/api/test.html) since it offers out-of-the-box parallel testing and code coverage, but it is up to you to choose your library of preference. -We highly recommend you read the [Plugin Testing](./Testing.md#plugins) to +We highly recommend you read the [Plugin Testing](./Testing.md#plugins) to learn about how to test your plugins. ## Code Linter diff --git a/docs/Guides/Write-Type-Provider.md b/docs/Guides/Write-Type-Provider.md index 4078345015b..0f0d750fdf9 100644 --- a/docs/Guides/Write-Type-Provider.md +++ b/docs/Guides/Write-Type-Provider.md @@ -10,9 +10,9 @@ Whereas exhaustive type narrowing checks normally rely on `never` to represent an unreachable state, reduction in type provider interfaces should only be done up to `unknown`. -The reasoning is that certain methods of `FastifyInstance` are -contravariant on `TypeProvider`, which can lead to TypeScript surfacing -assignability issues unless the custom type provider interface is +The reasoning is that certain methods of `FastifyInstance` are +contravariant on `TypeProvider`, which can lead to TypeScript surfacing +assignability issues unless the custom type provider interface is substitutable with `FastifyTypeProviderDefault`. For example, `FastifyTypeProviderDefault` will not be assignable to the following: diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 4465ad5e9ef..735bd2386c5 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -59,7 +59,7 @@ close as possible to the value intended to be set dynamically in the future. Initialize a decorator as a `''` if the intended value is a string, and as `null` if it will be an object or a function. -Remember this example works only with value types as reference types will +Remember this example works only with value types as reference types will thrown and error during the fastify startup. See [decorateRequest](#decorate-request). See [JavaScript engine fundamentals: Shapes and Inline @@ -109,7 +109,7 @@ fastify.decorate('db', new DbConnection()) fastify.get('/', async function (request, reply) { // using return return { hello: await this.db.query('world') } - + // or // using reply.send() reply.send({ hello: await this.db.query('world') }) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 34f1621b2e6..ad19abafd81 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -178,13 +178,13 @@ When utilizing Fastify's custom error handling through [`setErrorHandler`](./Ser you should be aware of how errors are propagated between custom and default error handlers. -If a plugin's error handler re-throws an error, and the error is not an +If a plugin's error handler re-throws an error, and the error is not an instance of [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) (as seen in the `/bad` route in the following example), it will not propagate to the parent context error handler. Instead, it will be caught by the default error handler. -To ensure consistent error handling, it is recommended to throw instances of +To ensure consistent error handling, it is recommended to throw instances of `Error`. For instance, in the following example, replacing `throw 'foo'` with `throw new Error('foo')` in the `/bad` route ensures that errors propagate through the custom error handling chain as intended. This practice helps avoid potential diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 13a07d02a0b..206a2ab6fe8 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -107,7 +107,7 @@ returned stream. This property is used to correctly match the request payload with the `Content-Length` header value. Ideally, this property should be updated on each received chunk. -**Notice:** The size of the returned stream is checked to not exceed the limit +**Notice:** The size of the returned stream is checked to not exceed the limit set in [`bodyLimit`](./Server.md#bodylimit) option. ### preValidation @@ -256,8 +256,8 @@ The `onResponse` hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example, to gather statistics. -**Note:** setting `disableRequestLogging` to `true` will disable any error log -inside the `onResponse` hook. In this case use `try - catch` to log errors. +**Note:** setting `disableRequestLogging` to `true` will disable any error log +inside the `onResponse` hook. In this case use `try - catch` to log errors. ### onTimeout @@ -428,8 +428,8 @@ fastify.addHook('onReady', async function () { ### onListen -Triggered when the server starts listening for requests. The hooks run one -after another. If a hook function causes an error, it is logged and +Triggered when the server starts listening for requests. The hooks run one +after another. If a hook function causes an error, it is logged and ignored, allowing the queue of hooks to continue. Hook functions accept one argument: a callback, `done`, to be invoked after the hook function is complete. Hook functions are invoked with `this` bound to the associated @@ -451,7 +451,7 @@ fastify.addHook('onListen', async function () { }) ``` -> **Note** +> **Note** > This hook will not run when the server is started using `fastify.inject()` or `fastify.ready()` ### onClose @@ -462,7 +462,7 @@ HTTP requests have been completed. It is useful when [plugins](./Plugins.md) need a "shutdown" event, for example, to close an open connection to a database. -The hook function takes the Fastify instance as a first argument, +The hook function takes the Fastify instance as a first argument, and a `done` callback for synchronous hook functions. ```js // callback style diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 31e6c0f19da..daaf599314c 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -71,14 +71,14 @@ since the request was received by Fastify. serialized payload. - `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the serialization function for the specified schema or http status, if any of either are set. -- `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles - the specified schema and returns a serialization function using the default - (or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded +- `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles + the specified schema and returns a serialization function using the default + (or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded to the `SerializerCompiler` if provided, default to `undefined`. -- `.serializeInput(data, schema, [,httpStatus], [contentType])` - Serializes +- `.serializeInput(data, schema, [,httpStatus], [contentType])` - Serializes the specified data using the specified schema and returns the serialized payload. - If the optional `httpStatus`, and `contentType` are provided, the function - will use the serializer function given for that specific content type and + If the optional `httpStatus`, and `contentType` are provided, the function + will use the serializer function given for that specific content type and HTTP Status Code. Default to `undefined`. - `.serializer(function)` - Sets a custom serializer for the payload. - `.send(payload)` - Sends the payload to the user, could be a plain text, a @@ -243,7 +243,7 @@ The hints parameter is an object containing the early hint key-value pairs. Example: ```js -reply.writeEarlyHints({ +reply.writeEarlyHints({ Link: '; rel=preload; as=style' }); ``` @@ -366,8 +366,8 @@ If the `Content-Type` has a JSON subtype, and the charset parameter is not set, ### .getSerializationFunction(schema | httpStatus, [contentType]) -By calling this function using a provided `schema` or `httpStatus`, -and the optional `contentType`, it will return a `serialzation` function +By calling this function using a provided `schema` or `httpStatus`, +and the optional `contentType`, it will return a `serialzation` function that can be used to serialize diverse inputs. It returns `undefined` if no serialization function was found using either of the provided inputs. @@ -377,12 +377,12 @@ the serialization functions compiled by using `compileSerializationSchema`. ```js const serialize = reply .getSerializationFunction({ - type: 'object', - properties: { - foo: { - type: 'string' - } - } + type: 'object', + properties: { + foo: { + type: 'string' + } + } }) serialize({ foo: 'bar' }) // '{"foo":"bar"}' @@ -411,8 +411,8 @@ The function returned (a.k.a. _serialization function_) returned is compiled by using the provided `SerializerCompiler`. Also this is cached by using a `WeakMap` for reducing compilation calls. -The optional parameters `httpStatus` and `contentType`, if provided, -are forwarded directly to the `SerializerCompiler`, so it can be used +The optional parameters `httpStatus` and `contentType`, if provided, +are forwarded directly to the `SerializerCompiler`, so it can be used to compile the serialization function if a custom `SerializerCompiler` is used. This heavily depends of the `schema#responses` attached to the route, or @@ -421,12 +421,12 @@ the serialization functions compiled by using `compileSerializationSchema`. ```js const serialize = reply .compileSerializationSchema({ - type: 'object', - properties: { - foo: { - type: 'string' - } - } + type: 'object', + properties: { + foo: { + type: 'string' + } + } }) serialize({ foo: 'bar' }) // '{"foo":"bar"}' @@ -434,12 +434,12 @@ serialize({ foo: 'bar' }) // '{"foo":"bar"}' const serialize = reply .compileSerializationSchema({ - type: 'object', - properties: { - foo: { - type: 'string' - } - } + type: 'object', + properties: { + foo: { + type: 'string' + } + } }, 200) serialize({ foo: 'bar' }) // '{"foo":"bar"}' @@ -485,7 +485,7 @@ const schema1 = { ``` *Not* -```js +```js const serialize = reply.compileSerializationSchema(schema1) // Later on... @@ -519,25 +519,25 @@ function will be compiled, forwarding the `httpStatus` and `contentType` if prov ```js reply - .serializeInput({ foo: 'bar'}, { - type: 'object', - properties: { - foo: { - type: 'string' - } - } + .serializeInput({ foo: 'bar'}, { + type: 'object', + properties: { + foo: { + type: 'string' + } + } }) // '{"foo":"bar"}' // or reply .serializeInput({ foo: 'bar'}, { - type: 'object', - properties: { - foo: { - type: 'string' - } - } + type: 'object', + properties: { + foo: { + type: 'string' + } + } }, 200) // '{"foo":"bar"}' // or @@ -712,7 +712,7 @@ fastify.get('/streams', async function (request, reply) { If you are sending a buffer and you have not set a `'Content-Type'` header, *send* will set it to `'application/octet-stream'`. -As noted above, Buffers are considered to be pre-serialized, so they will be +As noted above, Buffers are considered to be pre-serialized, so they will be sent unmodified without response validation. ```js @@ -743,7 +743,7 @@ fastify.get('/streams', async function (request, reply) { `send` manages TypedArray like a Buffer, and sets the `'Content-Type'` header to `'application/octet-stream'` if not already set. -As noted above, TypedArray/Buffers are considered to be pre-serialized, so they +As noted above, TypedArray/Buffers are considered to be pre-serialized, so they will be sent unmodified without response validation. ```js @@ -759,7 +759,7 @@ fastify.get('/streams', function (request, reply) { `ReadableStream` will be treated as a node stream mentioned above, -the content is considered to be pre-serialized, so they will be +the content is considered to be pre-serialized, so they will be sent unmodified without response validation. ```js @@ -778,7 +778,7 @@ fastify.get('/streams', function (request, reply) { `Response` allows to manage the reply payload, status code and headers in one place. The payload provided inside `Response` is -considered to be pre-serialized, so they will be sent unmodified +considered to be pre-serialized, so they will be sent unmodified without response validation. Please be aware when using `Response`, the status code and headers diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 2acd11e92fb..a1fdcf149fc 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -30,13 +30,13 @@ Request is a core Fastify object containing the following fields: - `protocol` - the protocol of the incoming request (`https` or `http`) - `method` - the method of the incoming request - `url` - the URL of the incoming request -- `originalUrl` - similar to `url`, this allows you to access the - original `url` in case of internal re-routing +- `originalUrl` - similar to `url`, this allows you to access the + original `url` in case of internal re-routing - `is404` - true if request is being handled by 404 handler, false if it is not - `socket` - the underlying connection of the incoming request - `context` - Deprecated, use `request.routeOptions.config` instead. A Fastify internal object. You should not use -it directly or modify it. It is useful to access one special key: +it directly or modify it. It is useful to access one special key: - `context.config` - The route [`config`](./Routes.md#routes-config) object. - `routeOptions` - The route [`option`](./Routes.md#routes-options) object - `bodyLimit` - either server limit or route limit @@ -44,15 +44,15 @@ it directly or modify it. It is useful to access one special key: - `method` - the http method for the route - `url` - the path of the URL to match this route - `handler` - the handler for this route - - `attachValidation` - attach `validationError` to request + - `attachValidation` - attach `validationError` to request (if there is a schema defined) - `logLevel` - log level defined for this route - `schema` - the JSON schemas definition for this route - `version` - a semver compatible string that defines the version of the endpoint - `exposeHeadRoute` - creates a sibling HEAD route for any GET routes - - `prefixTrailingSlash` - string used to determine how to handle passing / + - `prefixTrailingSlash` - string used to determine how to handle passing / as a route with a prefix. -- [.getValidationFunction(schema | httpPart)](#getvalidationfunction) - +- [.getValidationFunction(schema | httpPart)](#getvalidationfunction) - Returns a validation function for the specified schema or http part, if any of either are set or cached. - [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema) - @@ -117,7 +117,7 @@ fastify.post('/:params', options, function (request, reply) { ### .getValidationFunction(schema | httpPart) -By calling this function using a provided `schema` or `httpPart`, +By calling this function using a provided `schema` or `httpPart`, it will return a `validation` function that can be used to validate diverse inputs. It returns `undefined` if no serialization function was found using either of the provided inputs. @@ -128,12 +128,12 @@ are assigned to errors ```js const validate = request .getValidationFunction({ - type: 'object', - properties: { - foo: { - type: 'string' - } - } + type: 'object', + properties: { + foo: { + type: 'string' + } + } }) console.log(validate({ foo: 'bar' })) // true console.log(validate.errors) // null @@ -168,12 +168,12 @@ are assigned to errors ```js const validate = request .compileValidationSchema({ - type: 'object', - properties: { - foo: { - type: 'string' - } - } + type: 'object', + properties: { + foo: { + type: 'string' + } + } }) console.log(validate({ foo: 'bar' })) // true console.log(validate.errors) // null @@ -182,12 +182,12 @@ console.log(validate.errors) // null const validate = request .compileValidationSchema({ - type: 'object', - properties: { - foo: { - type: 'string' - } - } + type: 'object', + properties: { + foo: { + type: 'string' + } + } }, 200) console.log(validate({ hello: 'world' })) // false console.log(validate.errors) // validation errors @@ -217,7 +217,7 @@ const schema1 = { ``` *Not* -```js +```js const validate = request.compileValidationSchema(schema1) // Later on... @@ -252,25 +252,25 @@ function will be compiled, forwarding the `httpPart` if provided. ```js request - .validateInput({ foo: 'bar'}, { - type: 'object', - properties: { - foo: { - type: 'string' - } - } + .validateInput({ foo: 'bar'}, { + type: 'object', + properties: { + foo: { + type: 'string' + } + } }) // true // or request .validateInput({ foo: 'bar'}, { - type: 'object', - properties: { - foo: { - type: 'string' - } - } + type: 'object', + properties: { + foo: { + type: 'string' + } + } }, 'body') // true // or diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 9a030f20c79..94a3b5912b9 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -93,16 +93,16 @@ fastify.route(options) * `childLoggerFactory(logger, binding, opts, rawReq)`: a custom factory function that will be called to produce a child logger instance for every request. See [`childLoggerFactory`](./Server.md#childloggerfactory) for more info. - Overrides the default logger factory, and anything set by + Overrides the default logger factory, and anything set by [`setChildLoggerFactory`](./Server.md#setchildloggerfactory), for requests to - the route. To access the default factory, you can access + the route. To access the default factory, you can access `instance.childLoggerFactory`. Note that this will point to Fastify's default `childLoggerFactory` only if a plugin hasn't overridden it already. * `validatorCompiler({ schema, method, url, httpPart })`: function that builds schemas for request validations. See the [Validation and Serialization](./Validation-and-Serialization.md#schema-validator) documentation. -* `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`: +* `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`: function that builds schemas for response serialization. See the [Validation and Serialization](./Validation-and-Serialization.md#schema-serializer) documentation. @@ -121,8 +121,8 @@ fastify.route(options) * `version`: a [semver](https://semver.org/) compatible string that defined the version of the endpoint. [Example](#version-constraints). * `constraints`: defines route restrictions based on request properties or - values, enabling customized matching using - [find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes + values, enabling customized matching using + [find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes built-in `version` and `host` constraints, with support for custom constraint strategies. * `prefixTrailingSlash`: string used to determine how to handle passing `/` as a @@ -796,10 +796,10 @@ const secret = { > inside the callback. If the error is not preventable, it is recommended to provide > a custom `frameworkErrors` handler to deal with it. Otherwise, you route selection > may break or expose sensitive information to attackers. -> +> > ```js > const Fastify = require('fastify') -> +> > const fastify = Fastify({ > frameworkErrors: function (err, res, res) { > if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) { diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 8376ae88cbd..21c5afe962d 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -152,7 +152,7 @@ Defines the server keep-alive timeout in milliseconds. See documentation for [`server.keepAliveTimeout` property](https://nodejs.org/api/http.html#http_server_keepalivetimeout) to understand the effect of this option. This option only applies when HTTP/1 is in -use. +use. When `serverFactory` option is specified this option is ignored. @@ -203,7 +203,7 @@ ignored. Defines the maximum number of milliseconds for receiving the entire request from the client. See [`server.requestTimeout` property](https://nodejs.org/dist/latest/docs/api/http.html#http_server_requesttimeout) -to understand the effect of this option. +to understand the effect of this option. When `serverFactory` option is specified, this option is ignored. It must be set to a non-zero value (e.g. 120 seconds) to protect against potential @@ -387,12 +387,12 @@ attaching custom `onRequest` and `onResponse` hooks. The other log entries that will be disabled are: - an error log written by the default `onResponse` hook on reply callback errors -- the error and info logs written by the `defaultErrorHandler` +- the error and info logs written by the `defaultErrorHandler` on error management -- the info log written by the `fourOhFour` handler when a +- the info log written by the `fourOhFour` handler when a non existent route is requested -Other log messages emitted by Fastify will stay enabled, +Other log messages emitted by Fastify will stay enabled, like deprecation warnings and messages emitted when requests are received while the server is closing. @@ -456,7 +456,7 @@ Please note that setting this option to `false` goes against By setting `caseSensitive` to `false`, all paths will be matched as lowercase, but the route parameters or wildcards will maintain their original letter -casing. +casing. This option does not affect query strings, please refer to [`querystringParser`](#querystringparser) to change their handling. @@ -493,7 +493,7 @@ Setting `requestIdHeader` to `true` will set the `requestIdHeader` to Setting `requestIdHeader` to a non-empty string will use the specified string as the `requestIdHeader`. By default `requestIdHeader` is set to `false` and will immediately use [genReqId](#genreqid). -Setting `requestIdHeader` to an empty String (`""`) will set the +Setting `requestIdHeader` to an empty String (`""`) will set the requestIdHeader to `false`. + Default: `false` @@ -827,7 +827,7 @@ is an instance-wide configuration. // @param {object} req The raw Node.js HTTP request, not the `FastifyRequest` object. // @this Fastify The root Fastify instance (not an encapsulated instance). // @returns {string} The path that the request should be mapped to. -function rewriteUrl (req) { +function rewriteUrl (req) { if (req.url === '/hi') { this.log.debug({ originalUrl: req.url, url: '/hello' }, 'rewrite url'); return '/hello' @@ -948,7 +948,7 @@ Starts the server and internally waits for the `.ready()` event. The signature is `.listen([options][, callback])`. Both the `options` object and the `callback` parameters extend the [Node.js core](https://nodejs.org/api/net.html#serverlistenoptions-callback) options -object. Thus, all core options are available with the following additional +object. Thus, all core options are available with the following additional Fastify specific options: ### `listenTextResolver` @@ -956,13 +956,13 @@ Fastify specific options: Set an optional resolver for the text to log after server has been successfully started. -It is possible to override the default `Server listening at [address]` log +It is possible to override the default `Server listening at [address]` log entry using this option. ```js -server.listen({ - port: 9080, - listenTextResolver: (address) => { return `Prometheus metrics server is listening at ${address}` } +server.listen({ + port: 9080, + listenTextResolver: (address) => { return `Prometheus metrics server is listening at ${address}` } }) ``` @@ -1090,7 +1090,7 @@ Method to add routes to the server, it also has shorthand functions, check Method to check if a route is already registered to the internal router. It -expects an object as the payload. `url` and `method` are mandatory fields. It +expects an object as the payload. `url` and `method` are mandatory fields. It is possible to also specify `constraints`. The method returns `true` if the route is registered or `false` if not. @@ -1110,8 +1110,8 @@ if (routeExists === false) { Method to retrieve a route already registered to the internal router. It -expects an object as the payload. `url` and `method` are mandatory fields. It -is possible to also specify `constraints`. +expects an object as the payload. `url` and `method` are mandatory fields. It +is possible to also specify `constraints`. The method returns a route object or `null` if the route cannot be found. ```js @@ -1351,7 +1351,7 @@ Set the schema error formatter for all routes. See Set the schema serializer compiler for all routes. See [#schema-serializer](./Validation-and-Serialization.md#schema-serializer). -> **Note** +> **Note** > [`setReplySerializer`](#set-reply-serializer) has priority if set! #### validatorCompiler @@ -1966,7 +1966,7 @@ The properties that can currently be exposed are: - requestIdHeader - requestIdLogLabel - http2SessionTimeout -- useSemicolonDelimiter +- useSemicolonDelimiter ```js const { readFileSync } = require('node:fs') diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 18297ff277a..7176192bc6d 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -182,7 +182,7 @@ route-level `request` object. admin"}` 🎉 Good work, now you can define interfaces for each route and have strictly -typed request and reply instances. Other parts of the Fastify type system rely +typed request and reply instances. Other parts of the Fastify type system rely on generic properties. Make sure to reference the detailed type system documentation below to learn more about what is available. @@ -877,7 +877,7 @@ a more detailed http server walkthrough. import path from 'path' import fastify from 'fastify' ``` -2. Perform the following steps before setting up a Fastify HTTPS server +2. Perform the following steps before setting up a Fastify HTTPS server to create the `key.pem` and `cert.pem` files: ```sh openssl genrsa -out key.pem diff --git a/lib/error-serializer.js b/lib/error-serializer.js index 71fb87b9ce1..5fb7b8146e9 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -24,15 +24,15 @@ const JSON_STR_EMPTY_ARRAY = JSON_STR_BEGIN_ARRAY + JSON_STR_END_ARRAY const JSON_STR_EMPTY_STRING = JSON_STR_QUOTE + JSON_STR_QUOTE const JSON_STR_NULL = 'null' - - - + + + // # function anonymous0 (input) { const obj = (input && typeof input.toJSON === 'function') ? input.toJSON() : input - + if (obj === null) return JSON_STR_EMPTY_OBJECT let value @@ -50,7 +50,7 @@ let addComma = false if (value !== undefined) { !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"code\":" - + if (typeof value !== 'string') { if (value === null) { json += JSON_STR_EMPTY_STRING @@ -64,14 +64,14 @@ let addComma = false } else { json += serializer.asString(value) } - + } value = obj["error"] if (value !== undefined) { !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"error\":" - + if (typeof value !== 'string') { if (value === null) { json += JSON_STR_EMPTY_STRING @@ -85,14 +85,14 @@ let addComma = false } else { json += serializer.asString(value) } - + } value = obj["message"] if (value !== undefined) { !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"message\":" - + if (typeof value !== 'string') { if (value === null) { json += JSON_STR_EMPTY_STRING @@ -106,15 +106,15 @@ let addComma = false } else { json += serializer.asString(value) } - + } return json + JSON_STR_END_OBJECT - + } - + const main = anonymous0 return main - + }(validator, serializer) /* c8 ignore stop */ diff --git a/test/bundler/README.md b/test/bundler/README.md index e4040bfb8b7..d0f7c46eee4 100644 --- a/test/bundler/README.md +++ b/test/bundler/README.md @@ -1,12 +1,12 @@ # Bundlers test stack -In some cases, developers bundle their apps for several targets such as serverless applications. -Even if it's not recommended by Fastify team; we need to ensure we do not break the build process. +In some cases, developers bundle their apps for several targets such as serverless applications. +Even if it's not recommended by Fastify team; we need to ensure we do not break the build process. Please note this might result in features behaving differently, like the version handling check for plugins. ## Test bundlers -The bundler test stack has been defined separately from the rest of the Unit testing stack because it's not a +The bundler test stack has been defined separately from the rest of the Unit testing stack because it's not a part of the fastify lib itself. Note that the tests run in CI only on NodeJs LTS version. Developers do not need to install every bundler to run unit tests. @@ -23,7 +23,7 @@ stack dependencies. See: ## Bundler test development -To not break the fastify unit testing stack please name test files like this `*-test.js` and not `*.test.js`, +To not break the fastify unit testing stack please name test files like this `*-test.js` and not `*.test.js`, otherwise it will be targeted by the regular expression used for unit tests for fastify. -Tests need to ensure the build process works and the fastify application can be run, +Tests need to ensure the build process works and the fastify application can be run, no need to go in deep testing unless an issue is raised. From e44c7ba10135ed98906c274914d4ff60c01e71c0 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Tue, 5 Nov 2024 13:20:52 +0100 Subject: [PATCH 0824/1295] test: migrated encapsulated-error-handler.test.js from tap to node:test (#5824) --- test/encapsulated-error-handler.test.js | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/encapsulated-error-handler.test.js b/test/encapsulated-error-handler.test.js index 88927324af9..48c29d3e8ee 100644 --- a/test/encapsulated-error-handler.test.js +++ b/test/encapsulated-error-handler.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') // Because of how error handlers wrap things, following the control flow can be tricky @@ -13,7 +13,7 @@ test('encapsulates an asynchronous error handler', async t => { fastify.register(async function (fastify) { fastify.setErrorHandler(async function a (err) { // 3. the inner error handler catches the error, and throws a new error - t.equal(err.message, 'from_endpoint') + t.assert.strictEqual(err.message, 'from_endpoint') throw new Error('from_inner') }) fastify.get('/encapsulated', async () => { @@ -24,7 +24,7 @@ test('encapsulates an asynchronous error handler', async t => { fastify.setErrorHandler(async function b (err) { // 4. the outer error handler catches the error thrown by the inner error handler - t.equal(err.message, 'from_inner') + t.assert.strictEqual(err.message, 'from_inner') // 5. the outer error handler throws a new error throw new Error('from_outer') }) @@ -32,7 +32,7 @@ test('encapsulates an asynchronous error handler', async t => { // 1. the endpoint is called const res = await fastify.inject('/encapsulated') // 6. the default error handler returns the error from the outer error handler - t.equal(res.json().message, 'from_outer') + t.assert.strictEqual(res.json().message, 'from_outer') }) // See discussion in https://github.com/fastify/fastify/pull/5222#discussion_r1432573655 @@ -43,7 +43,7 @@ test('encapsulates a synchronous error handler', async t => { fastify.register(async function (fastify) { fastify.setErrorHandler(function a (err) { // 3. the inner error handler catches the error, and throws a new error - t.equal(err.message, 'from_endpoint') + t.assert.strictEqual(err.message, 'from_endpoint') throw new Error('from_inner') }) fastify.get('/encapsulated', async () => { @@ -54,7 +54,7 @@ test('encapsulates a synchronous error handler', async t => { fastify.setErrorHandler(async function b (err) { // 4. the outer error handler catches the error thrown by the inner error handler - t.equal(err.message, 'from_inner') + t.assert.strictEqual(err.message, 'from_inner') // 5. the outer error handler throws a new error throw new Error('from_outer') }) @@ -62,7 +62,7 @@ test('encapsulates a synchronous error handler', async t => { // 1. the endpoint is called const res = await fastify.inject('/encapsulated') // 6. the default error handler returns the error from the outer error handler - t.equal(res.json().message, 'from_outer') + t.assert.strictEqual(res.json().message, 'from_outer') }) test('onError hook nested', async t => { @@ -72,7 +72,7 @@ test('onError hook nested', async t => { fastify.register(async function (fastify) { fastify.setErrorHandler(async function a (err) { // 4. the inner error handler catches the error, and throws a new error - t.equal(err.message, 'from_endpoint') + t.assert.strictEqual(err.message, 'from_endpoint') throw new Error('from_inner') }) fastify.get('/encapsulated', async () => { @@ -83,20 +83,20 @@ test('onError hook nested', async t => { fastify.setErrorHandler(async function b (err) { // 5. the outer error handler catches the error thrown by the inner error handler - t.equal(err.message, 'from_inner') + t.assert.strictEqual(err.message, 'from_inner') // 6. the outer error handler throws a new error throw new Error('from_outer') }) fastify.addHook('onError', async function (request, reply, err) { // 3. the hook receives the error - t.equal(err.message, 'from_endpoint') + t.assert.strictEqual(err.message, 'from_endpoint') }) // 1. the endpoint is called const res = await fastify.inject('/encapsulated') // 7. the default error handler returns the error from the outer error handler - t.equal(res.json().message, 'from_outer') + t.assert.strictEqual(res.json().message, 'from_outer') }) // See https://github.com/fastify/fastify/issues/5220 @@ -107,7 +107,7 @@ test('encapuslates an error handler, for errors thrown in hooks', async t => { fastify.register(async function (fastify) { fastify.setErrorHandler(function a (err) { // 3. the inner error handler catches the error, and throws a new error - t.equal(err.message, 'from_hook') + t.assert.strictEqual(err.message, 'from_hook') throw new Error('from_inner') }) fastify.addHook('onRequest', async () => { @@ -119,7 +119,7 @@ test('encapuslates an error handler, for errors thrown in hooks', async t => { fastify.setErrorHandler(function b (err) { // 4. the outer error handler catches the error thrown by the inner error handler - t.equal(err.message, 'from_inner') + t.assert.strictEqual(err.message, 'from_inner') // 5. the outer error handler throws a new error throw new Error('from_outer') }) @@ -127,7 +127,7 @@ test('encapuslates an error handler, for errors thrown in hooks', async t => { // 1. the endpoint is called const res = await fastify.inject('/encapsulated') // 6. the default error handler returns the error from the outer error handler - t.equal(res.json().message, 'from_outer') + t.assert.strictEqual(res.json().message, 'from_outer') }) // See https://github.com/fastify/fastify/issues/5220 @@ -153,7 +153,7 @@ test('encapuslates many synchronous error handlers that rethrow errors', async t } else if (depth === 0) { fastify.setErrorHandler(function a (err) { // 3. innermost error handler catches the error, and throws a new error - t.equal(err.message, 'from_route') + t.assert.strictEqual(err.message, 'from_route') throw new Error(`from_handler_${depth}`) }) fastify.get('/encapsulated', async () => { @@ -163,7 +163,7 @@ test('encapuslates many synchronous error handlers that rethrow errors', async t } else { fastify.setErrorHandler(function d (err) { // 4 to {DEPTH+4}. error handlers each catch errors, and then throws a new error - t.equal(err.message, `from_handler_${depth - 1}`) + t.assert.strictEqual(err.message, `from_handler_${depth - 1}`) throw new Error(`from_handler_${depth}`) }) @@ -179,7 +179,7 @@ test('encapuslates many synchronous error handlers that rethrow errors', async t // 1. the endpoint is called const res = await fastify.inject('/encapsulated') // {DEPTH+5}. the default error handler returns the error from the outermost error handler - t.equal(res.json().message, `from_handler_${DEPTH}`) + t.assert.strictEqual(res.json().message, `from_handler_${DEPTH}`) }) // See https://github.com/fastify/fastify/issues/5220 @@ -207,7 +207,7 @@ test('encapuslates many asynchronous error handlers that rethrow errors', async } else if (depth === 0) { fastify.setErrorHandler(async function a (err) { // 3. innermost error handler catches the error, and throws a new error - t.equal(err.message, 'from_route') + t.assert.strictEqual(err.message, 'from_route') throw new Error(`from_handler_${depth}`) }) fastify.get('/encapsulated', async () => { @@ -217,7 +217,7 @@ test('encapuslates many asynchronous error handlers that rethrow errors', async } else { fastify.setErrorHandler(async function m (err) { // 4 to {DEPTH+4}. error handlers each catch errors, and then throws a new error - t.equal(err.message, `from_handler_${depth - 1}`) + t.assert.strictEqual(err.message, `from_handler_${depth - 1}`) throw new Error(`from_handler_${depth}`) }) @@ -233,5 +233,5 @@ test('encapuslates many asynchronous error handlers that rethrow errors', async // 1. the endpoint is called const res = await fastify.inject('/encapsulated') // {DEPTH+5}. the default error handler returns the error from the outermost error handler - t.equal(res.json().message, `from_handler_${DEPTH}`) + t.assert.strictEqual(res.json().message, `from_handler_${DEPTH}`) }) From 9be884aab7c929bd82fda938e2cfa9aa204df3db Mon Sep 17 00:00:00 2001 From: Memet <54851701+ExorTek@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:52:57 +0300 Subject: [PATCH 0825/1295] docs: Add `fastify-mongo-sanitize` and ` remix-fastify` to comm. (#5822) --- docs/Guides/Ecosystem.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index bee56704fe4..7b30501e522 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -175,6 +175,10 @@ section. - [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server) A plugin to easily create git server and make one/many Git repositories available for clone/fetch/push through the standard `git` (over http) commands. +- [`@exortek/fastify-mongo-sanitize`](https://github.com/ExorTek/fastify-mongo-sanitize) + A Fastify plugin that protects against No(n)SQL injection by sanitizing data. +- [`@exortek/remix-fastify`](https://github.com/ExorTek/remix-fastify) + Fastify plugin for Remix. - [`@fastify-userland/request-id`](https://github.com/fastify-userland/request-id) Fastify Request ID Plugin - [`@fastify-userland/typeorm-query-runner`](https://github.com/fastify-userland/typeorm-query-runner) From 7438c88b11536677782750134bc4f39133ff453d Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Wed, 6 Nov 2024 21:12:56 +0100 Subject: [PATCH 0826/1295] test: migrated from tap to node:test (#5823) --- test/encapsulated-child-logger-factory.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/encapsulated-child-logger-factory.test.js b/test/encapsulated-child-logger-factory.test.js index 1f857320471..9ef7c9fd35b 100644 --- a/test/encapsulated-child-logger-factory.test.js +++ b/test/encapsulated-child-logger-factory.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') const fp = require('fastify-plugin') @@ -12,7 +12,7 @@ test('encapsulates an child logger factory', async t => { fastify.setChildLoggerFactory(function pluginFactory (logger, bindings, opts) { const child = logger.child(bindings, opts) child.customLog = function (message) { - t.equal(message, 'custom') + t.assert.strictEqual(message, 'custom') } return child }) @@ -24,7 +24,7 @@ test('encapsulates an child logger factory', async t => { fastify.setChildLoggerFactory(function globalFactory (logger, bindings, opts) { const child = logger.child(bindings, opts) child.globalLog = function (message) { - t.equal(message, 'global') + t.assert.strictEqual(message, 'global') } return child }) @@ -33,10 +33,10 @@ test('encapsulates an child logger factory', async t => { }) const res1 = await fastify.inject('/encapsulated') - t.equal(res1.statusCode, 200) + t.assert.strictEqual(res1.statusCode, 200) const res2 = await fastify.inject('/not-encapsulated') - t.equal(res2.statusCode, 200) + t.assert.strictEqual(res2.statusCode, 200) }) test('child logger factory set on root scope when using fastify-plugin', async t => { @@ -48,7 +48,7 @@ test('child logger factory set on root scope when using fastify-plugin', async t fastify.setChildLoggerFactory(function pluginFactory (logger, bindings, opts) { const child = logger.child(bindings, opts) child.customLog = function (message) { - t.equal(message, 'custom') + t.assert.strictEqual(message, 'custom') } return child }) @@ -62,8 +62,8 @@ test('child logger factory set on root scope when using fastify-plugin', async t }) const res1 = await fastify.inject('/not-encapsulated-1') - t.equal(res1.statusCode, 200) + t.assert.strictEqual(res1.statusCode, 200) const res2 = await fastify.inject('/not-encapsulated-2') - t.equal(res2.statusCode, 200) + t.assert.strictEqual(res2.statusCode, 200) }) From 4eb26880ae4c6a1e52fe7e4b3f15b1cc60d6f512 Mon Sep 17 00:00:00 2001 From: Bill Mill Date: Wed, 6 Nov 2024 14:14:00 -0600 Subject: [PATCH 0827/1295] docs: add loggerInstance to Server doc (#5786) --- docs/Reference/Server.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 21c5afe962d..f8610bfdc8a 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -24,6 +24,7 @@ describes the properties available in that options object. - [`onProtoPoisoning`](#onprotopoisoning) - [`onConstructorPoisoning`](#onconstructorpoisoning) - [`logger`](#logger) + - [`loggerInstance`](#loggerInstance) - [`disableRequestLogging`](#disablerequestlogging) - [`serverFactory`](#serverfactory) - [`caseSensitive`](#casesensitive) @@ -329,9 +330,6 @@ The possible values this property may have are: + Default: `false`. The logger is disabled. All logging methods will point to a null logger [abstract-logging](https://npm.im/abstract-logging) instance. -+ `pinoInstance`: a previously instantiated instance of Pino. The internal - logger will point to this instance. - + `object`: a standard Pino [options object](https://github.com/pinojs/pino/blob/c77d8ec5ce/docs/API.md#constructor). This will be passed directly to the Pino constructor. If the following @@ -351,9 +349,15 @@ The possible values this property may have are: ``` Any user-supplied serializer will override the default serializer of the corresponding property. -+ `loggerInstance`: a custom logger instance. The logger must conform to the - Pino interface by having the following methods: `info`, `error`, `debug`, - `fatal`, `warn`, `trace`, `child`. For example: + +### `loggerInstance` + + ++ Default: `null` + +A custom logger instance. The logger must be a Pino instance or conform to the +Pino interface by having the following methods: `info`, `error`, `debug`, +`fatal`, `warn`, `trace`, `child`. For example: ```js const pino = require('pino')(); From f89fd9e9527a5dc61fc4cd59eb5ab09a2a5fbcb8 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Wed, 6 Nov 2024 21:19:59 +0100 Subject: [PATCH 0828/1295] test: migrated post-empty-body.test.js from tap to node:test (#5813) --- test/post-empty-body.test.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/test/post-empty-body.test.js b/test/post-empty-body.test.js index b3cd5547132..fdd23141bb8 100644 --- a/test/post-empty-body.test.js +++ b/test/post-empty-body.test.js @@ -1,7 +1,7 @@ 'use strict' -const { test } = require('tap') -const fastify = require('../') +const { test } = require('node:test') +const Fastify = require('..') const { request, setGlobalDispatcher, Agent } = require('undici') setGlobalDispatcher(new Agent({ @@ -10,22 +10,27 @@ setGlobalDispatcher(new Agent({ })) test('post empty body', async t => { - const app = fastify() - t.teardown(app.close.bind(app)) - - app.post('/bug', async (request, reply) => { + const fastify = Fastify() + const abortController = new AbortController() + const { signal } = abortController + t.after(() => { + fastify.close() + abortController.abort() }) - await app.listen({ port: 0 }) + fastify.post('/bug', async (request, reply) => {}) + + await fastify.listen({ port: 0 }) - const res = await request(`http://127.0.0.1:${app.server.address().port}/bug`, { + const res = await request(`http://127.0.0.1:${fastify.server.address().port}/bug`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ foo: 'bar' }) + body: JSON.stringify({ foo: 'bar' }), + signal }) - t.equal(res.statusCode, 200) - t.equal(await res.body.text(), '') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(await res.body.text(), '') }) From eb52533e661e2292908e1229aa55544a18ca499e Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 7 Nov 2024 09:38:06 +0100 Subject: [PATCH 0829/1295] docs(CONTRIBUTING.md): read the announcements (#5825) --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 387760a75a3..be2de589e7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,10 +104,12 @@ the following tasks: [team.yml](https://github.com/fastify/website/blob/HEAD/static/data/team.yml) file. This list is also sorted alphabetically so make sure to add your name in the proper order. Use your GitHub profile icon for the `picture:` field. -5. The person that does the onboarding must add you to the [npm +5. Read the [pinned announcements](https://github.com/orgs/fastify/discussions/categories/announcements) + to be updated with the organisation’s news. +6. The person that does the onboarding must add you to the [npm org](https://www.npmjs.com/org/fastify), so that you can help maintaining the official plugins. -6. Optionally, the person can be added as an Open Collective member +7. Optionally, the person can be added as an Open Collective member by the lead team. ### Offboarding Collaborators From 73f85427f9ef39dfffe6f5e74f1d9d0c871aeabd Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 7 Nov 2024 15:28:16 +0100 Subject: [PATCH 0830/1295] test: migrated listen.5.test.js from tap to node:test (#5827) --- test/listen.5.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/listen.5.test.js b/test/listen.5.test.js index 8976a10d64b..2cc00aa86f6 100644 --- a/test/listen.5.test.js +++ b/test/listen.5.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const net = require('node:net') const Fastify = require('../fastify') const { once } = require('node:events') @@ -26,9 +26,9 @@ test('same port conflict and success should not fire callback multiple times - c switch (count) { case 6: { // success in here - t.error(err) + t.assert.ifError(err) fastify.close((err) => { - t.error(err) + t.assert.ifError(err) promise.resolve() }) break @@ -42,7 +42,7 @@ test('same port conflict and success should not fire callback multiple times - c } default: { // expect error - t.equal(err.code, 'EADDRINUSE') + t.assert.strictEqual(err.code, 'EADDRINUSE') setTimeout(() => { fastify.listen(option, callback) }, 100) @@ -65,27 +65,27 @@ test('same port conflict and success should not fire callback multiple times - p try { await fastify.listen(option) } catch (err) { - t.equal(err.code, 'EADDRINUSE') + t.assert.strictEqual(err.code, 'EADDRINUSE') } try { await fastify.listen(option) } catch (err) { - t.equal(err.code, 'EADDRINUSE') + t.assert.strictEqual(err.code, 'EADDRINUSE') } try { await fastify.listen(option) } catch (err) { - t.equal(err.code, 'EADDRINUSE') + t.assert.strictEqual(err.code, 'EADDRINUSE') } try { await fastify.listen(option) } catch (err) { - t.equal(err.code, 'EADDRINUSE') + t.assert.strictEqual(err.code, 'EADDRINUSE') } try { await fastify.listen(option) } catch (err) { - t.equal(err.code, 'EADDRINUSE') + t.assert.strictEqual(err.code, 'EADDRINUSE') } server.close() From 2e930945dafb682b767c7b147a64512110e8c7e8 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 7 Nov 2024 18:32:29 +0100 Subject: [PATCH 0831/1295] test: migrated use-semicolon-delimiter.test.js from tap to node:test (#5812) --- ...est.js => use-semicolon-delimiter.test.js} | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) rename test/{useSemicolonDelimiter.test.js => use-semicolon-delimiter.test.js} (57%) diff --git a/test/useSemicolonDelimiter.test.js b/test/use-semicolon-delimiter.test.js similarity index 57% rename from test/useSemicolonDelimiter.test.js rename to test/use-semicolon-delimiter.test.js index 639e9f88a76..3d44542c426 100644 --- a/test/useSemicolonDelimiter.test.js +++ b/test/use-semicolon-delimiter.test.js @@ -1,110 +1,110 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat -test('use semicolon delimiter default false', t => { +test('use semicolon delimiter default false', (t, done) => { t.plan(4) - const fastify = Fastify({}) - - t.teardown(fastify.close.bind(fastify)) + const fastify = Fastify() fastify.get('/1234;foo=bar', (req, reply) => { reply.send(req.query) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - + fastify.listen({ port: 0, }, err => { + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), {}) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), {}) + fastify.close() + done() }) }) }) -test('use semicolon delimiter set to true', t => { +test('use semicolon delimiter set to true', (t, done) => { t.plan(4) - const fastify = Fastify({ useSemicolonDelimiter: true }) - t.teardown(fastify.close.bind(fastify)) fastify.get('/1234', (req, reply) => { reply.send(req.query) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'bar' }) + fastify.close() + done() }) }) }) -test('use semicolon delimiter set to false', t => { +test('use semicolon delimiter set to false', (t, done) => { t.plan(4) const fastify = Fastify({ useSemicolonDelimiter: false }) - t.teardown(fastify.close.bind(fastify)) fastify.get('/1234;foo=bar', (req, reply) => { reply.send(req.query) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), {}) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), {}) + fastify.close() + done() }) }) }) -test('use semicolon delimiter set to false return 404', t => { +test('use semicolon delimiter set to false return 404', (t, done) => { t.plan(3) const fastify = Fastify({ useSemicolonDelimiter: false }) - t.teardown(fastify.close.bind(fastify)) fastify.get('/1234', (req, reply) => { reply.send(req.query) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + fastify.close() + done() }) }) }) From 3ac4322f03d09614b0aa1825e8092982c007318a Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Fri, 8 Nov 2024 12:45:24 +0100 Subject: [PATCH 0832/1295] docs: smaller documentation fixes (#5834) --- docs/Reference/Reply.md | 13 +++++++------ docs/Reference/Server.md | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index daaf599314c..d3a521a2cac 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -11,9 +11,9 @@ - [.headers(object)](#headersobject) - [.getHeader(key)](#getheaderkey) - [.getHeaders()](#getheaders) - - [set-cookie](#set-cookie) - [.removeHeader(key)](#removeheaderkey) - [.hasHeader(key)](#hasheaderkey) + - [.writeEarlyHints(hints, callback)](#writeearlyhintshints-callback) - [.trailer(key, function)](#trailerkey-function) - [.hasTrailer(key)](#hastrailerkey) - [.removeTrailer(key)](#removetrailerkey) @@ -32,8 +32,9 @@ - [Strings](#strings) - [Streams](#streams) - [Buffers](#buffers) - - [ReadableStream](#send-readablestream) - - [Response](#send-response) + - [TypedArrays](#typedarrays) + - [ReadableStream](#readablestream) + - [Response](#response) - [Errors](#errors) - [Type of the final payload](#type-of-the-final-payload) - [Async-Await and Promises](#async-await-and-promises) @@ -87,7 +88,7 @@ since the request was received by Fastify. already been called. - `.hijack()` - interrupt the normal request lifecycle. - `.raw` - The - [`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse) + [`http.ServerResponse`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_class_http_serverresponse) from Node core. - `.log` - The logger instance of the incoming request. - `.request` - The incoming request. @@ -157,7 +158,7 @@ Sets a response header. If the value is omitted or undefined, it is coerced to > will result in a 500 `TypeError` response. For more information, see -[`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_response_setheader_name_value). +[`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_response_setheader_name_value). - ### set-cookie @@ -586,7 +587,7 @@ values. This is the -[`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse) +[`http.ServerResponse`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_class_http_serverresponse) from Node core. Whilst you are using the Fastify `Reply` object, the use of `Reply.raw` functions is at your own risk as you are skipping all the Fastify logic of handling the HTTP response. e.g.: diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index f8610bfdc8a..d9438197c73 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -106,7 +106,7 @@ describes the properties available in that options object. An object used to configure the server's listening socket. The options are the same as the Node.js core [`createServer` -method](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_createserver_options_requestlistener). +method](https://nodejs.org/docs/latest-v20.x/api/http.html#httpcreateserveroptions-requestlistener). This option is ignored if options [`http2`](#factory-http2) or [`https`](#factory-https) are set. @@ -117,7 +117,7 @@ This option is ignored if options [`http2`](#factory-http2) or + Default: `false` If `true` Node.js core's -[HTTP/2](https://nodejs.org/dist/latest-v14.x/docs/api/http2.html) module is +[HTTP/2](https://nodejs.org/dist/latest-v20.x/docs/api/http2.html) module is used for binding the socket. ### `https` @@ -127,7 +127,7 @@ used for binding the socket. An object used to configure the server's listening socket for TLS. The options are the same as the Node.js core [`createServer` -method](https://nodejs.org/dist/latest-v14.x/docs/api/https.html#https_https_createserver_options_requestlistener). +method](https://nodejs.org/dist/latest-v20.x/docs/api/https.html#https_https_createserver_options_requestlistener). When this property is `null`, the socket will not be configured for TLS. This option also applies when the [`http2`](#factory-http2) option is set. From c95906abdab29858d9244198f8f883df7206a3ac Mon Sep 17 00:00:00 2001 From: Kalven Schraut <30308012+kalvenschraut@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:05:20 -0600 Subject: [PATCH 0833/1295] fix(types): addHttpMethod type signature on fastify instance (#5810) (#5811) * fix(types): addHttpMethod type signature on fastify instance (#5810) * test: add test for addHttpMethod --- test/types/instance.test-d.ts | 2 ++ types/instance.d.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index f41f92bb952..c797ba21f05 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -193,6 +193,8 @@ function invalidSchemaErrorFormatter (err: Error) { } expectError(server.setSchemaErrorFormatter(invalidSchemaErrorFormatter)) +expectType(server.addHttpMethod('SEARCH', { hasBody: true })) + // test listen opts objects expectAssignable>(server.listen()) expectAssignable>(server.listen({ port: 3000 })) diff --git a/types/instance.d.ts b/types/instance.d.ts index b18c2127635..00ad258080c 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -548,6 +548,13 @@ export interface FastifyInstance< * Remove all content type parsers, including the default ones */ removeAllContentTypeParsers: removeAllContentTypeParsers + /** + * Add a non-standard HTTP method + * + * Methods defined by default include `GET`, `HEAD`, `TRACE`, `DELETE`, + * `OPTIONS`, `PATCH`, `PUT` and `POST` + */ + addHttpMethod(method: string, methodOptions?: { hasBody: boolean }): FastifyInstance; /** * Fastify default JSON parser */ From 9661baa147ce0f054a9aded5c9d0d67383319d84 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 9 Nov 2024 10:08:05 +0100 Subject: [PATCH 0834/1295] test: migrated schema-examples.test.js from tap to node:test (#5833) --- test/schema-examples.test.js | 110 +++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/test/schema-examples.test.js b/test/schema-examples.test.js index 127073b255f..96fd03f68ee 100644 --- a/test/schema-examples.test.js +++ b/test/schema-examples.test.js @@ -1,10 +1,10 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const localize = require('ajv-i18n') const Fastify = require('..') -test('Example - URI $id', t => { +test('Example - URI $id', (t, done) => { t.plan(1) const fastify = Fastify() fastify.addSchema({ @@ -25,10 +25,13 @@ test('Example - URI $id', t => { } }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example - string $id', t => { +test('Example - string $id', (t, done) => { t.plan(1) const fastify = Fastify() fastify.addSchema({ @@ -47,10 +50,13 @@ test('Example - string $id', t => { } }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example - get schema', t => { +test('Example - get schema', (t, done) => { t.plan(1) const fastify = Fastify() fastify.addSchema({ @@ -63,7 +69,8 @@ test('Example - get schema', t => { const mySchemas = fastify.getSchemas() const mySchema = fastify.getSchema('schemaId') - t.same(mySchemas.schemaId, mySchema) + t.assert.deepStrictEqual(mySchemas.schemaId, mySchema) + done() }) test('Example - get schema encapsulated', async t => { @@ -91,12 +98,12 @@ test('Example - get schema encapsulated', async t => { const r2 = await fastify.inject('/sub') const r3 = await fastify.inject('/deep') - t.same(Object.keys(r1.json()), ['one']) - t.same(Object.keys(r2.json()), ['one', 'two']) - t.same(Object.keys(r3.json()), ['one', 'two', 'three']) + t.assert.deepStrictEqual(Object.keys(r1.json()), ['one']) + t.assert.deepStrictEqual(Object.keys(r2.json()), ['one', 'two']) + t.assert.deepStrictEqual(Object.keys(r3.json()), ['one', 'two', 'three']) }) -test('Example - validation', t => { +test('Example - validation', (t, done) => { t.plan(1) const fastify = Fastify({ ajv: { @@ -168,10 +175,13 @@ test('Example - validation', t => { } fastify.post('/the/url', { schema }, handler) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example - ajv config', t => { +test('Example - ajv config', (t, done) => { t.plan(1) const fastify = Fastify({ @@ -228,10 +238,13 @@ test('Example - ajv config', t => { } }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example Joi', t => { +test('Example Joi', (t, done) => { t.plan(1) const fastify = Fastify() const handler = () => { } @@ -248,10 +261,13 @@ test('Example Joi', t => { } }, handler) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example yup', t => { +test('Example yup', (t, done) => { t.plan(1) const fastify = Fastify() const handler = () => { } @@ -287,10 +303,13 @@ test('Example yup', t => { } }, handler) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example - serialization', t => { +test('Example - serialization', (t, done) => { t.plan(1) const fastify = Fastify() const handler = () => { } @@ -308,10 +327,13 @@ test('Example - serialization', t => { } fastify.post('/the/url', { schema }, handler) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example - serialization 2', t => { +test('Example - serialization 2', (t, done) => { t.plan(1) const fastify = Fastify() const handler = () => { } @@ -333,10 +355,13 @@ test('Example - serialization 2', t => { } fastify.post('/the/url', { schema }, handler) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example - serializator', t => { +test('Example - serializator', (t, done) => { t.plan(1) const fastify = Fastify() @@ -361,10 +386,13 @@ test('Example - serializator', t => { } }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Example - schemas examples', t => { +test('Example - schemas examples', (t, done) => { t.plan(1) const fastify = Fastify() const handler = () => { } @@ -457,10 +485,13 @@ test('Example - schemas examples', t => { }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('should return custom error messages with ajv-errors', t => { +test('should return custom error messages with ajv-errors', (t, done) => { t.plan(3) const fastify = Fastify({ @@ -508,18 +539,19 @@ test('should return custom error messages with ajv-errors', t => { }, url: '/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/age bad age - should be num, body name please, body work please' }) - t.equal(res.statusCode, 400) + t.assert.strictEqual(res.statusCode, 400) + done() }) }) -test('should be able to handle formats of ajv-formats when added by plugins option', t => { +test('should be able to handle formats of ajv-formats when added by plugins option', (t, done) => { t.plan(3) const fastify = Fastify({ @@ -553,8 +585,8 @@ test('should be able to handle formats of ajv-formats when added by plugins opti }, url: '/' }, (_err, res) => { - t.equal(res.body, '254381a5-888c-4b41-8116-e3b1a54980bd') - t.equal(res.statusCode, 200) + t.assert.strictEqual(res.body, '254381a5-888c-4b41-8116-e3b1a54980bd') + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ @@ -565,16 +597,17 @@ test('should be able to handle formats of ajv-formats when added by plugins opti }, url: '/' }, (_err, res) => { - t.same(JSON.parse(res.payload), { + t.assert.deepStrictEqual(JSON.parse(res.payload), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/id must match format "uuid"' }) + done() }) }) -test('should return localized error messages with ajv-i18n', t => { +test('should return localized error messages with ajv-i18n', (t, done) => { t.plan(3) const schema = { @@ -614,14 +647,15 @@ test('should return localized error messages with ajv-i18n', t => { }, url: '/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), [{ + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), [{ instancePath: '', keyword: 'required', message: 'должно иметь обязательное поле work', params: { missingProperty: 'work' }, schemaPath: '#/required' }]) - t.equal(res.statusCode, 400) + t.assert.strictEqual(res.statusCode, 400) + done() }) }) From 01370fab7bc4065ecd87f61e682bb21c8340ad59 Mon Sep 17 00:00:00 2001 From: Kristian Lentino <60007558+KristianLentino99@users.noreply.github.com> Date: Sat, 9 Nov 2024 10:10:45 +0100 Subject: [PATCH 0835/1295] test: Migrate tests to Node test container (#5777) --- test/async-dispose.test.js | 1 - test/has-route.test.js | 27 ++++++++++++--------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/test/async-dispose.test.js b/test/async-dispose.test.js index 786d21f6a41..f6a726692cd 100644 --- a/test/async-dispose.test.js +++ b/test/async-dispose.test.js @@ -16,6 +16,5 @@ test('async dispose should close fastify', { skip: !('asyncDispose' in Symbol) } // the same as syntax sugar for // await using app = fastify() await fastify[Symbol.asyncDispose]() - t.assert.strictEqual(fastify.server.listening, false) }) diff --git a/test/has-route.test.js b/test/has-route.test.js index 76b2862b580..a00ee2cb31f 100644 --- a/test/has-route.test.js +++ b/test/has-route.test.js @@ -1,22 +1,19 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test, describe } = require('node:test') const Fastify = require('../fastify') -test('hasRoute', t => { - t.plan(5) - const test = t.test - const fastify = Fastify() +const fastify = Fastify() +describe('hasRoute', async t => { test('hasRoute - invalid options', t => { t.plan(3) - t.equal(fastify.hasRoute({ }), false) + t.assert.strictEqual(fastify.hasRoute({ }), false) - t.equal(fastify.hasRoute({ method: 'GET' }), false) + t.assert.strictEqual(fastify.hasRoute({ method: 'GET' }), false) - t.equal(fastify.hasRoute({ constraints: [] }), false) + t.assert.strictEqual(fastify.hasRoute({ constraints: [] }), false) }) test('hasRoute - primitive method', t => { @@ -29,12 +26,12 @@ test('hasRoute', t => { } }) - t.equal(fastify.hasRoute({ + t.assert.strictEqual(fastify.hasRoute({ method: 'GET', url: '/' }), true) - t.equal(fastify.hasRoute({ + t.assert.strictEqual(fastify.hasRoute({ method: 'POST', url: '/' }), false) @@ -51,13 +48,13 @@ test('hasRoute', t => { } }) - t.equal(fastify.hasRoute({ + t.assert.strictEqual(fastify.hasRoute({ method: 'GET', url: '/', constraints: { version: '1.2.0' } }), true) - t.equal(fastify.hasRoute({ + t.assert.strictEqual(fastify.hasRoute({ method: 'GET', url: '/', constraints: { version: '1.3.0' } @@ -69,7 +66,7 @@ test('hasRoute', t => { // parametric with regexp fastify.get('/example/:file(^\\d+).png', function (request, reply) { }) - t.equal(fastify.hasRoute({ + t.assert.strictEqual(fastify.hasRoute({ method: 'GET', url: '/example/:file(^\\d+).png' }), true) @@ -85,7 +82,7 @@ test('hasRoute', t => { } }) - t.equal(fastify.hasRoute({ + t.assert.strictEqual(fastify.hasRoute({ method: 'get', url: '/equal' }), true) From 6354638cd279a144c3cb7d70d12c7671a0114294 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 9 Nov 2024 10:11:58 +0100 Subject: [PATCH 0836/1295] test: migrated max-requests-per-socket.test.js from tap to node:test (#5828) --- ...est.js => max-requests-per-socket.test.js} | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) rename test/{maxRequestsPerSocket.test.js => max-requests-per-socket.test.js} (54%) diff --git a/test/maxRequestsPerSocket.test.js b/test/max-requests-per-socket.test.js similarity index 54% rename from test/maxRequestsPerSocket.test.js rename to test/max-requests-per-socket.test.js index a345067f4e7..7e4401e5f62 100644 --- a/test/maxRequestsPerSocket.test.js +++ b/test/max-requests-per-socket.test.js @@ -1,10 +1,10 @@ 'use strict' const net = require('node:net') -const { test } = require('tap') -const Fastify = require('../fastify') +const { test } = require('node:test') +const Fastify = require('..') -test('maxRequestsPerSocket', t => { +test('maxRequestsPerSocket', (t, done) => { t.plan(8) const fastify = Fastify({ maxRequestsPerSocket: 2 }) @@ -12,32 +12,32 @@ test('maxRequestsPerSocket', t => { reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) const port = fastify.server.address().port const client = net.createConnection({ port }, () => { client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { - t.match(data.toString(), /Connection:\s*keep-alive/i) - t.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) - t.match(data.toString(), /200 OK/i) + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) + t.assert.match(data.toString(), /200 OK/i) client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { - t.match(data.toString(), /Connection:\s*close/i) - t.match(data.toString(), /200 OK/i) + t.assert.match(data.toString(), /Connection:\s*close/i) + t.assert.match(data.toString(), /200 OK/i) client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { - t.match(data.toString(), /Connection:\s*close/i) - t.match(data.toString(), /503 Service Unavailable/i) + t.assert.match(data.toString(), /Connection:\s*close/i) + t.assert.match(data.toString(), /503 Service Unavailable/i) client.end() + fastify.close() + done() }) }) }) @@ -45,7 +45,7 @@ test('maxRequestsPerSocket', t => { }) }) -test('maxRequestsPerSocket zero should behave same as null', t => { +test('maxRequestsPerSocket zero should behave same as null', (t, done) => { t.plan(10) const fastify = Fastify({ maxRequestsPerSocket: 0 }) @@ -53,34 +53,34 @@ test('maxRequestsPerSocket zero should behave same as null', t => { reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) const port = fastify.server.address().port const client = net.createConnection({ port }, () => { client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { - t.match(data.toString(), /Connection:\s*keep-alive/i) - t.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) - t.match(data.toString(), /200 OK/i) + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) + t.assert.match(data.toString(), /200 OK/i) client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { - t.match(data.toString(), /Connection:\s*keep-alive/i) - t.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) - t.match(data.toString(), /200 OK/i) + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) + t.assert.match(data.toString(), /200 OK/i) client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') client.once('data', data => { - t.match(data.toString(), /Connection:\s*keep-alive/i) - t.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) - t.match(data.toString(), /200 OK/i) + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) + t.assert.match(data.toString(), /200 OK/i) client.end() + fastify.close() + done() }) }) }) @@ -92,22 +92,22 @@ test('maxRequestsPerSocket should be set', async (t) => { t.plan(1) const initialConfig = Fastify({ maxRequestsPerSocket: 5 }).initialConfig - t.same(initialConfig.maxRequestsPerSocket, 5) + t.assert.deepStrictEqual(initialConfig.maxRequestsPerSocket, 5) }) test('maxRequestsPerSocket should 0', async (t) => { t.plan(1) const initialConfig = Fastify().initialConfig - t.same(initialConfig.maxRequestsPerSocket, 0) + t.assert.deepStrictEqual(initialConfig.maxRequestsPerSocket, 0) }) test('requestTimeout passed to server', t => { t.plan(2) const httpServer = Fastify({ maxRequestsPerSocket: 5 }).server - t.equal(httpServer.maxRequestsPerSocket, 5) + t.assert.strictEqual(httpServer.maxRequestsPerSocket, 5) const httpsServer = Fastify({ maxRequestsPerSocket: 5, https: true }).server - t.equal(httpsServer.maxRequestsPerSocket, 5) + t.assert.strictEqual(httpsServer.maxRequestsPerSocket, 5) }) From 54b1022839a85f32899932e237e1a5463bfd96da Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 11 Nov 2024 22:13:19 +0100 Subject: [PATCH 0837/1295] test: migrated proto-poisoning.test.js from tap to node:test (#5836) --- test/proto-poisoning.test.js | 79 +++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/test/proto-poisoning.test.js b/test/proto-poisoning.test.js index c0200f88ddd..5eeb2e0d741 100644 --- a/test/proto-poisoning.test.js +++ b/test/proto-poisoning.test.js @@ -2,21 +2,19 @@ const Fastify = require('..') const sget = require('simple-get').concat -const t = require('tap') -const test = t.test +const { test } = require('node:test') -test('proto-poisoning error', t => { +test('proto-poisoning error', (t, done) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) fastify.post('/', (request, reply) => { - t.fail('handler should not be called') + t.assert.fail('handler should not be called') }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -24,25 +22,26 @@ test('proto-poisoning error', t => { headers: { 'Content-Type': 'application/json' }, body: '{ "__proto__": { "a": 42 } }' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + fastify.close() + done() }) }) }) -test('proto-poisoning remove', t => { +test('proto-poisoning remove', (t, done) => { t.plan(4) const fastify = Fastify({ onProtoPoisoning: 'remove' }) - t.teardown(fastify.close.bind(fastify)) fastify.post('/', (request, reply) => { - t.equal(undefined, Object.assign({}, request.body).a) + t.assert.strictEqual(undefined, Object.assign({}, request.body).a) reply.send({ ok: true }) }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -50,25 +49,26 @@ test('proto-poisoning remove', t => { headers: { 'Content-Type': 'application/json' }, body: '{ "__proto__": { "a": 42 }, "b": 42 }' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + fastify.close() + done() }) }) }) -test('proto-poisoning ignore', t => { +test('proto-poisoning ignore', (t, done) => { t.plan(4) const fastify = Fastify({ onProtoPoisoning: 'ignore' }) - t.teardown(fastify.close.bind(fastify)) fastify.post('/', (request, reply) => { - t.equal(42, Object.assign({}, request.body).a) + t.assert.strictEqual(42, Object.assign({}, request.body).a) reply.send({ ok: true }) }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -76,24 +76,25 @@ test('proto-poisoning ignore', t => { headers: { 'Content-Type': 'application/json' }, body: '{ "__proto__": { "a": 42 }, "b": 42 }' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + fastify.close() + done() }) }) }) -test('constructor-poisoning error (default in v3)', t => { +test('constructor-poisoning error (default in v3)', (t, done) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) fastify.post('/', (request, reply) => { reply.send('ok') }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -101,24 +102,25 @@ test('constructor-poisoning error (default in v3)', t => { headers: { 'Content-Type': 'application/json' }, body: '{ "constructor": { "prototype": { "foo": "bar" } } }' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + fastify.close() + done() }) }) }) -test('constructor-poisoning error', t => { +test('constructor-poisoning error', (t, done) => { t.plan(3) const fastify = Fastify({ onConstructorPoisoning: 'error' }) - t.teardown(fastify.close.bind(fastify)) fastify.post('/', (request, reply) => { - t.fail('handler should not be called') + t.assert.fail('handler should not be called') }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -126,25 +128,26 @@ test('constructor-poisoning error', t => { headers: { 'Content-Type': 'application/json' }, body: '{ "constructor": { "prototype": { "foo": "bar" } } }' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + fastify.close() + done() }) }) }) -test('constructor-poisoning remove', t => { +test('constructor-poisoning remove', (t, done) => { t.plan(4) const fastify = Fastify({ onConstructorPoisoning: 'remove' }) - t.teardown(fastify.close.bind(fastify)) fastify.post('/', (request, reply) => { - t.equal(undefined, Object.assign({}, request.body).foo) + t.assert.strictEqual(undefined, Object.assign({}, request.body).foo) reply.send({ ok: true }) }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -152,8 +155,10 @@ test('constructor-poisoning remove', t => { headers: { 'Content-Type': 'application/json' }, body: '{ "constructor": { "prototype": { "foo": "bar" } } }' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + fastify.close() + done() }) }) }) From c92a72c0b9848af46451a5239629e5ee84406fc7 Mon Sep 17 00:00:00 2001 From: Zoran Stojkov <39105055+stojkov-z@users.noreply.github.com> Date: Sun, 17 Nov 2024 12:01:43 +0100 Subject: [PATCH 0838/1295] test: migrated content-type and context-config tests from tap to node:test (#5778) --- test/content-type.test.js | 17 +++---- test/context-config.test.js | 98 +++++++++++++++++-------------------- 2 files changed, 52 insertions(+), 63 deletions(-) diff --git a/test/content-type.test.js b/test/content-type.test.js index 06e91f9b544..ab9c45835e9 100644 --- a/test/content-type.test.js +++ b/test/content-type.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') test('should remove content-type for setErrorHandler', async t => { @@ -10,22 +9,22 @@ test('should remove content-type for setErrorHandler', async t => { const fastify = Fastify() fastify.setErrorHandler(function (error, request, reply) { - t.same(error.message, 'kaboom') - t.same(reply.hasHeader('content-type'), false) + t.assert.strictEqual(error.message, 'kaboom') + t.assert.strictEqual(reply.hasHeader('content-type'), false) reply.code(400).send({ foo: 'bar' }) }) fastify.addHook('onSend', async function (request, reply, payload) { count++ - t.same(typeof payload, 'string') + t.assert.strictEqual(typeof payload, 'string') switch (count) { case 1: { // should guess the correct content-type based on payload - t.same(reply.getHeader('content-type'), 'text/plain; charset=utf-8') + t.assert.strictEqual(reply.getHeader('content-type'), 'text/plain; charset=utf-8') throw Error('kaboom') } case 2: { // should guess the correct content-type based on payload - t.same(reply.getHeader('content-type'), 'application/json; charset=utf-8') + t.assert.strictEqual(reply.getHeader('content-type'), 'application/json; charset=utf-8') return payload } default: { @@ -38,6 +37,6 @@ test('should remove content-type for setErrorHandler', async t => { }) const { statusCode, body } = await fastify.inject({ method: 'GET', path: '/' }) - t.same(statusCode, 400) - t.same(body, JSON.stringify({ foo: 'bar' })) + t.assert.strictEqual(statusCode, 400) + t.assert.strictEqual(body, JSON.stringify({ foo: 'bar' })) }) diff --git a/test/context-config.test.js b/test/context-config.test.js index 4f8592ed65b..00f4bc62068 100644 --- a/test/context-config.test.js +++ b/test/context-config.test.js @@ -1,7 +1,7 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') + const { kRouteContext } = require('../lib/symbols') const Fastify = require('..') @@ -17,8 +17,8 @@ function handler (req, reply) { reply.send(reply[kRouteContext].config) } -test('config', t => { - t.plan(9) +test('config', async t => { + t.plan(6) const fastify = Fastify() fastify.get('/get', { @@ -41,36 +41,33 @@ test('config', t => { handler }) - fastify.inject({ + let response = await fastify.inject({ method: 'GET', - url: '/get' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), Object.assign({ url: '/get', method: 'GET' }, schema.config)) + url: '/route' }) - fastify.inject({ + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/route', method: 'GET' }, schema.config)) + + response = await fastify.inject({ method: 'GET', url: '/route' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), Object.assign({ url: '/route', method: 'GET' }, schema.config)) }) - fastify.inject({ + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/route', method: 'GET' }, schema.config)) + + response = await fastify.inject({ method: 'GET', url: '/no-config' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), { url: '/no-config', method: 'GET' }) }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { url: '/no-config', method: 'GET' }) }) -test('config with exposeHeadRoutes', t => { - t.plan(9) +test('config with exposeHeadRoutes', async t => { + t.plan(6) const fastify = Fastify({ exposeHeadRoutes: true }) fastify.get('/get', { @@ -93,36 +90,33 @@ test('config with exposeHeadRoutes', t => { handler }) - fastify.inject({ + let response = await fastify.inject({ method: 'GET', url: '/get' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), Object.assign({ url: '/get', method: 'GET' }, schema.config)) }) - fastify.inject({ + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/get', method: 'GET' }, schema.config)) + + response = await fastify.inject({ method: 'GET', url: '/route' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), Object.assign({ url: '/route', method: 'GET' }, schema.config)) }) - fastify.inject({ + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/route', method: 'GET' }, schema.config)) + + response = await fastify.inject({ method: 'GET', url: '/no-config' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), { url: '/no-config', method: 'GET' }) }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { url: '/no-config', method: 'GET' }) }) -test('config without exposeHeadRoutes', t => { - t.plan(9) +test('config without exposeHeadRoutes', async t => { + t.plan(6) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.get('/get', { @@ -145,30 +139,26 @@ test('config without exposeHeadRoutes', t => { handler }) - fastify.inject({ + let response = await fastify.inject({ method: 'GET', url: '/get' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), Object.assign({ url: '/get', method: 'GET' }, schema.config)) }) - fastify.inject({ + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/get', method: 'GET' }, schema.config)) + + response = await fastify.inject({ method: 'GET', url: '/route' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), Object.assign({ url: '/route', method: 'GET' }, schema.config)) }) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/route', method: 'GET' }, schema.config)) - fastify.inject({ + response = await fastify.inject({ method: 'GET', url: '/no-config' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(response.payload), { url: '/no-config', method: 'GET' }) }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { url: '/no-config', method: 'GET' }) }) From 63472be3a6a882b3e85a3f23b6221ec02bf210f8 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Sun, 17 Nov 2024 12:06:22 +0100 Subject: [PATCH 0839/1295] docs: added fastify-passkit-webservice to community plugins (#5819) Signed-off-by: Alexander Cerutti --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 7b30501e522..e78b690bed4 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -542,6 +542,8 @@ middlewares into Fastify plugins OSM plugin to run overpass queries by OpenStreetMap. - [`fastify-override`](https://github.com/matthyk/fastify-override) Fastify plugin to override decorators, plugins and hooks for testing purposes +- [`fastify-passkit-webservice`](https://github.com/alexandercerutti/fastify-passkit-webservice) + A set of Fastify plugins to integrate Apple Wallet Web Service specification - [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo) Fastify plugin for memoize responses by expressive settings. - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker From 47dda880645cf4f23eadc4a5eea451f9abb0e594 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Sun, 17 Nov 2024 08:07:06 -0300 Subject: [PATCH 0840/1295] test: migrate trust-proxy, type-provider, url-rewriting to node:test (#5829) --- test/trust-proxy.test.js | 85 ++++++++++++++++++-------------------- test/type-provider.test.js | 14 ++++--- test/url-rewriting.test.js | 73 ++++++++++++++++---------------- 3 files changed, 84 insertions(+), 88 deletions(-) diff --git a/test/trust-proxy.test.js b/test/trust-proxy.test.js index 480b288d88e..cf70de3d116 100644 --- a/test/trust-proxy.test.js +++ b/test/trust-proxy.test.js @@ -1,12 +1,13 @@ 'use strict' -const t = require('tap') -const { test, before } = t +const { test, before } = require('node:test') const sget = require('simple-get').concat const fastify = require('..') const helper = require('./helper') -const sgetForwardedRequest = (app, forHeader, path, protoHeader) => { +const noop = () => {} + +const sgetForwardedRequest = (app, forHeader, path, protoHeader, done) => { const headers = { 'X-Forwarded-For': forHeader, 'X-Forwarded-Host': 'example.com' @@ -18,30 +19,30 @@ const sgetForwardedRequest = (app, forHeader, path, protoHeader) => { method: 'GET', headers, url: 'http://localhost:' + app.server.address().port + path - }, () => {}) + }, done || noop) } const testRequestValues = (t, req, options) => { if (options.ip) { - t.ok(req.ip, 'ip is defined') - t.equal(req.ip, options.ip, 'gets ip from x-forwarded-for') + t.assert.ok(req.ip, 'ip is defined') + t.assert.strictEqual(req.ip, options.ip, 'gets ip from x-forwarded-for') } if (options.host) { - t.ok(req.host, 'host is defined') - t.equal(req.host, options.host, 'gets host from x-forwarded-host') - t.ok(req.hostname) - t.equal(req.hostname, options.host, 'gets hostname from x-forwarded-host') + t.assert.ok(req.host, 'host is defined') + t.assert.strictEqual(req.host, options.host, 'gets host from x-forwarded-host') + t.assert.ok(req.hostname) + t.assert.strictEqual(req.hostname, options.host, 'gets hostname from x-forwarded-host') } if (options.ips) { - t.same(req.ips, options.ips, 'gets ips from x-forwarded-for') + t.assert.deepStrictEqual(req.ips, options.ips, 'gets ips from x-forwarded-for') } if (options.protocol) { - t.ok(req.protocol, 'protocol is defined') - t.equal(req.protocol, options.protocol, 'gets protocol from x-forwarded-proto') + t.assert.ok(req.protocol, 'protocol is defined') + t.assert.strictEqual(req.protocol, options.protocol, 'gets protocol from x-forwarded-proto') } if (options.port) { - t.ok(req.port, 'port is defined') - t.equal(req.port, options.port, 'port is taken from x-forwarded-for or host') + t.assert.ok(req.port, 'port is defined') + t.assert.strictEqual(req.port, options.port, 'port is taken from x-forwarded-for or host') } } @@ -50,7 +51,7 @@ before(async function () { [localhost] = await helper.getLoopbackHost() }) -test('trust proxy, not add properties to node req', (t) => { +test('trust proxy, not add properties to node req', (t, done) => { t.plan(14) const app = fastify({ trustProxy: true @@ -65,17 +66,16 @@ test('trust proxy, not add properties to node req', (t) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - t.teardown(app.close.bind(app)) - app.listen({ port: 0 }, (err) => { app.server.unref() - t.error(err) + t.assert.ifError(err) + t.after(() => app.close()) sgetForwardedRequest(app, '1.1.1.1', '/trustproxy') - sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain') + sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined, done) }) }) -test('trust proxy chain', (t) => { +test('trust proxy chain', (t, done) => { t.plan(9) const app = fastify({ trustProxy: [localhost, '192.168.1.1'] @@ -86,16 +86,15 @@ test('trust proxy chain', (t) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - t.teardown(app.close.bind(app)) - app.listen({ port: 0 }, (err) => { app.server.unref() - t.error(err) - sgetForwardedRequest(app, '192.168.1.1, 1.1.1.1', '/trustproxychain') + t.assert.ifError(err) + t.after(() => app.close()) + sgetForwardedRequest(app, '192.168.1.1, 1.1.1.1', '/trustproxychain', undefined, done) }) }) -test('trust proxy function', (t) => { +test('trust proxy function', (t, done) => { t.plan(9) const app = fastify({ trustProxy: (address) => address === localhost @@ -105,16 +104,15 @@ test('trust proxy function', (t) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - t.teardown(app.close.bind(app)) - app.listen({ port: 0 }, (err) => { app.server.unref() - t.error(err) - sgetForwardedRequest(app, '1.1.1.1', '/trustproxyfunc') + t.assert.ifError(err) + t.after(() => app.close()) + sgetForwardedRequest(app, '1.1.1.1', '/trustproxyfunc', undefined, done) }) }) -test('trust proxy number', (t) => { +test('trust proxy number', (t, done) => { t.plan(10) const app = fastify({ trustProxy: 1 @@ -124,16 +122,15 @@ test('trust proxy number', (t) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - t.teardown(app.close.bind(app)) - app.listen({ port: 0 }, (err) => { app.server.unref() - t.error(err) - sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxynumber') + t.assert.ifError(err) + t.after(() => app.close()) + sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined, done) }) }) -test('trust proxy IP addresses', (t) => { +test('trust proxy IP addresses', (t, done) => { t.plan(10) const app = fastify({ trustProxy: `${localhost}, 2.2.2.2` @@ -143,16 +140,15 @@ test('trust proxy IP addresses', (t) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - t.teardown(app.close.bind(app)) - app.listen({ port: 0 }, (err) => { app.server.unref() - t.error(err) - sgetForwardedRequest(app, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs') + t.assert.ifError(err) + t.after(() => app.close()) + sgetForwardedRequest(app, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs', undefined, done) }) }) -test('trust proxy protocol', (t) => { +test('trust proxy protocol', (t, done) => { t.plan(31) const app = fastify({ trustProxy: true @@ -170,13 +166,14 @@ test('trust proxy protocol', (t) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - t.teardown(app.close.bind(app)) + t.after(() => app.close()) app.listen({ port: 0 }, (err) => { app.server.unref() - t.error(err) + t.assert.ifError(err) sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocol', 'lorem') sgetForwardedRequest(app, '1.1.1.1', '/trustproxynoprotocol') - sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor') + // Allow for sgetForwardedRequest requests above to finish + setTimeout(() => sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor', done)) }) }) diff --git a/test/type-provider.test.js b/test/type-provider.test.js index b691bf95bd0..80e238e7dfb 100644 --- a/test/type-provider.test.js +++ b/test/type-provider.test.js @@ -1,20 +1,22 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') -test('Should export withTypeProvider function', t => { +test('Should export withTypeProvider function', (t, done) => { t.plan(1) try { Fastify().withTypeProvider() - t.pass() + t.assert.ok('pass') + done() } catch (e) { - t.fail() + t.assert.fail(e) } }) -test('Should return same instance', t => { +test('Should return same instance', (t, done) => { t.plan(1) const fastify = Fastify() - t.equal(fastify, fastify.withTypeProvider()) + t.assert.strictEqual(fastify, fastify.withTypeProvider()) + done() }) diff --git a/test/url-rewriting.test.js b/test/url-rewriting.test.js index bc2c0bf6965..f251a4d4970 100644 --- a/test/url-rewriting.test.js +++ b/test/url-rewriting.test.js @@ -1,15 +1,14 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat -test('Should rewrite url', t => { +test('Should rewrite url', (t, done) => { t.plan(5) const fastify = Fastify({ rewriteUrl (req) { - t.equal(req.url, '/this-would-404-without-url-rewrite') + t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') this.log.info('rewriting url') return '/' } @@ -24,26 +23,26 @@ test('Should rewrite url', t => { }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' }, (err, response, body) => { - t.error(err) - t.same(JSON.parse(body), { hello: 'world' }) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - - t.teardown(() => fastify.close()) }) -test('Should not rewrite if the url is the same', t => { +test('Should not rewrite if the url is the same', (t, done) => { t.plan(4) const fastify = Fastify({ rewriteUrl (req) { - t.equal(req.url, '/this-would-404-without-url-rewrite') + t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') this.log.info('rewriting url') return req.url } @@ -58,24 +57,24 @@ test('Should not rewrite if the url is the same', t => { }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 404) + done() }) }) - - t.teardown(() => fastify.close()) }) -test('Should throw an error', t => { + +test('Should throw an error', (t, done) => { t.plan(5) const fastify = Fastify({ rewriteUrl (req) { - t.equal(req.url, '/this-would-404-without-url-rewrite') + t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') this.log.info('rewriting url') return undefined } @@ -90,27 +89,26 @@ test('Should throw an error', t => { }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' }, (err, response, body) => { - t.equal(err.code, 'ECONNRESET') - t.equal(response, undefined) - t.equal(body, undefined) + t.assert.strictEqual(err.code, 'ECONNRESET') + t.assert.strictEqual(response, undefined) + t.assert.strictEqual(body, undefined) + done() }) }) - - t.teardown(() => fastify.close()) }) -test('Should rewrite url but keep originalUrl unchanged', t => { +test('Should rewrite url but keep originalUrl unchanged', (t, done) => { t.plan(7) const fastify = Fastify({ rewriteUrl (req) { - t.equal(req.url, '/this-would-404-without-url-rewrite') - t.equal(req.originalUrl, '/this-would-404-without-url-rewrite') + t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') + t.assert.strictEqual(req.originalUrl, '/this-would-404-without-url-rewrite') return '/' } }) @@ -120,23 +118,22 @@ test('Should rewrite url but keep originalUrl unchanged', t => { url: '/', handler: (req, reply) => { reply.send({ hello: 'world', hostname: req.hostname, port: req.port }) - t.equal(req.originalUrl, '/this-would-404-without-url-rewrite') + t.assert.strictEqual(req.originalUrl, '/this-would-404-without-url-rewrite') } }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' }, (err, response, body) => { - t.error(err) + t.assert.ifError(err) const parsedBody = JSON.parse(body) - t.same(parsedBody, { hello: 'world', hostname: 'localhost', port: fastify.server.address().port }) - t.equal(response.statusCode, 200) + t.assert.deepStrictEqual(parsedBody, { hello: 'world', hostname: 'localhost', port: fastify.server.address().port }) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) - - t.teardown(() => fastify.close()) }) From 7d94ecf4b5a79e9b9a99859bca43b435eb756adf Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 17 Nov 2024 12:08:17 +0100 Subject: [PATCH 0841/1295] test: migrated fluent-schema.test.js from tap to node:test (#5832) --- test/fluent-schema.test.js | 69 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/test/fluent-schema.test.js b/test/fluent-schema.test.js index 48b95946af2..4e6d7ba46e1 100644 --- a/test/fluent-schema.test.js +++ b/test/fluent-schema.test.js @@ -1,12 +1,11 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const S = require('fluent-json-schema') -test('use fluent-json-schema object', t => { - t.plan(15) +test('use fluent-json-schema object', async (t) => { + t.plan(10) const fastify = Fastify() fastify.post('/:id', { @@ -24,73 +23,62 @@ test('use fluent-json-schema object', t => { } }) - // check params - fastify.inject({ + const res1 = await fastify.inject({ method: 'POST', url: '/1', headers: { 'x-custom': 'me@me.me' }, query: { surname: 'bar' }, payload: { name: 'foo' } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'params/id must be >= 42' }) }) + t.assert.strictEqual(res1.statusCode, 400) + t.assert.deepStrictEqual(res1.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'params/id must be >= 42' }) // check header - fastify.inject({ + const res2 = await fastify.inject({ method: 'POST', url: '/42', headers: { 'x-custom': 'invalid' }, query: { surname: 'bar' }, payload: { name: 'foo' } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'headers/x-custom must match format "email"' }) }) + t.assert.strictEqual(res2.statusCode, 400) + t.assert.deepStrictEqual(res2.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'headers/x-custom must match format "email"' }) // check query - fastify.inject({ + const res3 = await fastify.inject({ method: 'POST', url: '/42', headers: { 'x-custom': 'me@me.me' }, query: { }, payload: { name: 'foo' } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'querystring must have required property \'surname\'' }) }) + t.assert.strictEqual(res3.statusCode, 400) + t.assert.deepStrictEqual(res3.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'querystring must have required property \'surname\'' }) // check body - fastify.inject({ + const res4 = await fastify.inject({ method: 'POST', url: '/42', headers: { 'x-custom': 'me@me.me' }, query: { surname: 'bar' }, payload: { name: [1, 2, 3] } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/name must be string' }) }) + t.assert.strictEqual(res4.statusCode, 400) + t.assert.deepStrictEqual(res4.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/name must be string' }) // check response - fastify.inject({ + const res5 = await fastify.inject({ method: 'POST', url: '/42', headers: { 'x-custom': 'me@me.me' }, query: { surname: 'bar' }, payload: { name: 'foo' } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { name: 'a', surname: 'b' }) }) + t.assert.strictEqual(res5.statusCode, 200) + t.assert.deepStrictEqual(res5.json(), { name: 'a', surname: 'b' }) }) -test('use complex fluent-json-schema object', t => { +test('use complex fluent-json-schema object', (t, done) => { t.plan(1) const fastify = Fastify() @@ -113,10 +101,13 @@ test('use complex fluent-json-schema object', t => { .prop('office', S.ref('https://fastify/demo#/definitions/addressSchema')).required() fastify.post('/the/url', { schema: { body: bodyJsonSchema } }, () => { }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('use fluent schema and plain JSON schema', t => { +test('use fluent schema and plain JSON schema', (t, done) => { t.plan(1) const fastify = Fastify() @@ -154,10 +145,13 @@ test('use fluent schema and plain JSON schema', t => { .prop('office', S.ref('https://fastify/demo#/definitions/addressSchema')).required() fastify.post('/the/url', { schema: { body: bodyJsonSchema } }, () => { }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -test('Should call valueOf internally', t => { +test('Should call valueOf internally', (t, done) => { t.plan(1) const fastify = new Fastify() @@ -208,5 +202,8 @@ test('Should call valueOf internally', t => { } }) - fastify.ready(t.error) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) From c86750e02bcad8651bac63b717c26a2d46c5c440 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 17 Nov 2024 14:09:36 +0100 Subject: [PATCH 0842/1295] test: migrated from tap to node:test (#5835) --- test/header-overflow.test.js | 25 +++++++++++++------------ test/noop-set.test.js | 2 +- test/set-error-handler.test.js | 5 ++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/test/header-overflow.test.js b/test/header-overflow.test.js index 3b63830d545..a00a068b652 100644 --- a/test/header-overflow.test.js +++ b/test/header-overflow.test.js @@ -1,13 +1,12 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat const maxHeaderSize = 1024 -test('Should return 431 if request header fields are too large', t => { +test('Should return 431 if request header fields are too large', (t, done) => { t.plan(3) const fastify = Fastify({ http: { maxHeaderSize } }) @@ -20,7 +19,7 @@ test('Should return 431 if request header fields are too large', t => { }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', @@ -29,15 +28,16 @@ test('Should return 431 if request header fields are too large', t => { 'Large-Header': 'a'.repeat(maxHeaderSize) } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 431) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 431) + done() }) }) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) }) -test('Should return 431 if URI is too long', t => { +test('Should return 431 if URI is too long', (t, done) => { t.plan(3) const fastify = Fastify({ http: { maxHeaderSize } }) @@ -50,16 +50,17 @@ test('Should return 431 if URI is too long', t => { }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + `/${'a'.repeat(maxHeaderSize)}` }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 431) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 431) + done() }) }) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) }) diff --git a/test/noop-set.test.js b/test/noop-set.test.js index a004d88c0f0..8b56a14eb27 100644 --- a/test/noop-set.test.js +++ b/test/noop-set.test.js @@ -14,6 +14,6 @@ test('does a lot of nothing', async t => { t.assert.strictEqual(aSet.has(item), true) for (const i of aSet) { - t.assert.fail('should not have any items', i) + t.assert.fail('should not have any items: ' + i) } }) diff --git a/test/set-error-handler.test.js b/test/set-error-handler.test.js index 737fd02bb82..1350a139b38 100644 --- a/test/set-error-handler.test.js +++ b/test/set-error-handler.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const { FST_ERR_ERROR_HANDLER_NOT_FN } = require('../lib/errors') @@ -9,5 +8,5 @@ test('setErrorHandler should throw an error if the handler is not a function', t t.plan(1) const fastify = Fastify() - t.throws(() => fastify.setErrorHandler('not a function'), new FST_ERR_ERROR_HANDLER_NOT_FN()) + t.assert.throws(() => fastify.setErrorHandler('not a function'), new FST_ERR_ERROR_HANDLER_NOT_FN()) }) From 040cb4d98cd59cbd5fc27dcf27180ef85f8a292b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:35:46 +0000 Subject: [PATCH 0843/1295] chore: Bump secure-json-parse in the dependencies-major group (#5845) Bumps the dependencies-major group with 1 update: [secure-json-parse](https://github.com/fastify/secure-json-parse). Updates `secure-json-parse` from 2.7.0 to 3.0.1 - [Release notes](https://github.com/fastify/secure-json-parse/releases) - [Commits](https://github.com/fastify/secure-json-parse/compare/v2.7.0...v3.0.1) --- updated-dependencies: - dependency-name: secure-json-parse dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bb8ee89516..7148eb9e4de 100644 --- a/package.json +++ b/package.json @@ -201,7 +201,7 @@ "process-warning": "^4.0.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.1", - "secure-json-parse": "^2.7.0", + "secure-json-parse": "^3.0.1", "semver": "^7.6.0", "toad-cache": "^3.7.0" }, From 746795db334e468203ac33e68f99eac717da23ad Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 18 Nov 2024 19:28:06 +0100 Subject: [PATCH 0844/1295] test: migrated pretty-print.test.js from tap to node:test (#5844) --- test/pretty-print.test.js | 108 +++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/test/pretty-print.test.js b/test/pretty-print.test.js index 8c8713e798e..e02a493d3ab 100644 --- a/test/pretty-print.test.js +++ b/test/pretty-print.test.js @@ -1,10 +1,9 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') -test('pretty print - static routes', t => { +test('pretty print - static routes', (t, done) => { t.plan(2) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -22,12 +21,13 @@ test('pretty print - static routes', t => { └── hello/world (GET) ` - t.equal(typeof tree, 'string') - t.equal(tree, expected) + t.assert.strictEqual(typeof tree, 'string') + t.assert.strictEqual(tree, expected) + done() }) }) -test('pretty print - internal tree - static routes', t => { +test('pretty print - internal tree - static routes', (t, done) => { t.plan(4) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -47,8 +47,8 @@ test('pretty print - internal tree - static routes', t => { └── hello/world (GET) ` - t.equal(typeof getTree, 'string') - t.equal(getTree, expectedGetTree) + t.assert.strictEqual(typeof getTree, 'string') + t.assert.strictEqual(getTree, expectedGetTree) const putTree = fastify.printRoutes({ method: 'PUT' }) const expectedPutTree = `\ @@ -57,12 +57,13 @@ test('pretty print - internal tree - static routes', t => { └── /foo (PUT) ` - t.equal(typeof putTree, 'string') - t.equal(putTree, expectedPutTree) + t.assert.strictEqual(typeof putTree, 'string') + t.assert.strictEqual(putTree, expectedPutTree) + done() }) }) -test('pretty print - parametric routes', t => { +test('pretty print - parametric routes', (t, done) => { t.plan(2) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -82,12 +83,13 @@ test('pretty print - parametric routes', t => { └── :world (GET) ` - t.equal(typeof tree, 'string') - t.equal(tree, expected) + t.assert.strictEqual(typeof tree, 'string') + t.assert.strictEqual(tree, expected) + done() }) }) -test('pretty print - internal tree - parametric routes', t => { +test('pretty print - internal tree - parametric routes', (t, done) => { t.plan(4) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -109,8 +111,8 @@ test('pretty print - internal tree - parametric routes', t => { └── :world (GET) ` - t.equal(typeof getTree, 'string') - t.equal(getTree, expectedGetTree) + t.assert.strictEqual(typeof getTree, 'string') + t.assert.strictEqual(getTree, expectedGetTree) const putTree = fastify.printRoutes({ method: 'PUT' }) const expectedPutTree = `\ @@ -120,12 +122,13 @@ test('pretty print - internal tree - parametric routes', t => { └── :hello (PUT) ` - t.equal(typeof putTree, 'string') - t.equal(putTree, expectedPutTree) + t.assert.strictEqual(typeof putTree, 'string') + t.assert.strictEqual(putTree, expectedPutTree) + done() }) }) -test('pretty print - mixed parametric routes', t => { +test('pretty print - mixed parametric routes', (t, done) => { t.plan(2) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -145,12 +148,13 @@ test('pretty print - mixed parametric routes', t => { └── /world (GET) ` - t.equal(typeof tree, 'string') - t.equal(tree, expected) + t.assert.strictEqual(typeof tree, 'string') + t.assert.strictEqual(tree, expected) + done() }) }) -test('pretty print - wildcard routes', t => { +test('pretty print - wildcard routes', (t, done) => { t.plan(2) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -170,12 +174,13 @@ test('pretty print - wildcard routes', t => { └── * (GET) ` - t.equal(typeof tree, 'string') - t.equal(tree, expected) + t.assert.strictEqual(typeof tree, 'string') + t.assert.strictEqual(tree, expected) + done() }) }) -test('pretty print - internal tree - wildcard routes', t => { +test('pretty print - internal tree - wildcard routes', (t, done) => { t.plan(4) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -197,8 +202,8 @@ test('pretty print - internal tree - wildcard routes', t => { └── * (GET) ` - t.equal(typeof getTree, 'string') - t.equal(getTree, expectedGetTree) + t.assert.strictEqual(typeof getTree, 'string') + t.assert.strictEqual(getTree, expectedGetTree) const putTree = fastify.printRoutes({ method: 'PUT' }) const expectedPutTree = `\ @@ -208,23 +213,25 @@ test('pretty print - internal tree - wildcard routes', t => { └── * (PUT) ` - t.equal(typeof putTree, 'string') - t.equal(putTree, expectedPutTree) + t.assert.strictEqual(typeof putTree, 'string') + t.assert.strictEqual(putTree, expectedPutTree) + done() }) }) -test('pretty print - empty plugins', t => { +test('pretty print - empty plugins', (t, done) => { t.plan(2) const fastify = Fastify() fastify.ready(() => { const tree = fastify.printPlugins() - t.equal(typeof tree, 'string') - t.match(tree, /root \d+ ms\n└── bound _after \d+ ms/m) + t.assert.strictEqual(typeof tree, 'string') + t.assert.match(tree, /root \d+ ms\n└── bound _after \d+ ms/m) + done() }) }) -test('pretty print - nested plugins', t => { +test('pretty print - nested plugins', (t, done) => { t.plan(4) const fastify = Fastify() @@ -234,14 +241,15 @@ test('pretty print - nested plugins', t => { }) fastify.ready(() => { const tree = fastify.printPlugins() - t.equal(typeof tree, 'string') - t.match(tree, 'foo') - t.match(tree, 'bar') - t.match(tree, 'baz') + t.assert.strictEqual(typeof tree, 'string') + t.assert.match(tree, /foo/) + t.assert.match(tree, /bar/) + t.assert.match(tree, /baz/) + done() }) }) -test('pretty print - commonPrefix', t => { +test('pretty print - commonPrefix', (t, done) => { t.plan(4) const fastify = Fastify() @@ -263,14 +271,15 @@ test('pretty print - commonPrefix', t => { ├── /hello (GET, HEAD, PUT) └── /helicopter (GET, HEAD) ` - t.equal(typeof radixTree, 'string') - t.equal(typeof flatTree, 'string') - t.equal(radixTree, radixExpected) - t.equal(flatTree, flatExpected) + t.assert.strictEqual(typeof radixTree, 'string') + t.assert.strictEqual(typeof flatTree, 'string') + t.assert.strictEqual(radixTree, radixExpected) + t.assert.strictEqual(flatTree, flatExpected) + done() }) }) -test('pretty print - includeMeta, includeHooks', t => { +test('pretty print - includeMeta, includeHooks', (t, done) => { t.plan(6) const fastify = Fastify() @@ -346,11 +355,12 @@ test('pretty print - includeMeta, includeHooks', t => { • (onRequest) ["anonymous()"] • (onSend) ["headRouteOnSendHandler()"] ` - t.equal(typeof radixTree, 'string') - t.equal(typeof flatTree, 'string') - t.equal(typeof hooksOnlyExpected, 'string') - t.equal(radixTree, radixExpected) - t.equal(flatTree, flatExpected) - t.equal(hooksOnly, hooksOnlyExpected) + t.assert.strictEqual(typeof radixTree, 'string') + t.assert.strictEqual(typeof flatTree, 'string') + t.assert.strictEqual(typeof hooksOnlyExpected, 'string') + t.assert.strictEqual(radixTree, radixExpected) + t.assert.strictEqual(flatTree, flatExpected) + t.assert.strictEqual(hooksOnly, hooksOnlyExpected) + done() }) }) From 816f852c56e229d3af4784b1a3705a50725c3f37 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 18 Nov 2024 19:29:29 +0100 Subject: [PATCH 0845/1295] test: migrated from tap to node:test (#5837) --- test/custom-parser.5.test.js | 64 ++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/test/custom-parser.5.test.js b/test/custom-parser.5.test.js index aad8fa4b401..78f5ae4105f 100644 --- a/test/custom-parser.5.test.js +++ b/test/custom-parser.5.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../fastify') const jsonParser = require('fast-json-body') @@ -9,35 +8,34 @@ const { getServerUrl, plainTextParser } = require('./helper') process.removeAllListeners('warning') -test('cannot remove all content type parsers after binding', t => { +test('cannot remove all content type parsers after binding', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }, function (err) { - t.error(err) - - t.throws(() => fastify.removeAllContentTypeParsers()) + t.assert.ifError(err) + t.assert.throws(() => fastify.removeAllContentTypeParsers()) + fastify.close() + done() }) }) -test('cannot remove content type parsers after binding', t => { +test('cannot remove content type parsers after binding', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, function (err) { - t.error(err) - - t.throws(() => fastify.removeContentTypeParser('application/json')) + t.assert.ifError(err) + t.assert.throws(() => fastify.removeContentTypeParser('application/json')) + done() }) }) -test('should be able to override the default json parser after removeAllContentTypeParsers', t => { +test('should be able to override the default json parser after removeAllContentTypeParsers', (t, done) => { t.plan(5) const fastify = Fastify() @@ -49,14 +47,14 @@ test('should be able to override the default json parser after removeAllContentT fastify.removeAllContentTypeParsers() fastify.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') jsonParser(payload, function (err, body) { done(err, body) }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -66,15 +64,16 @@ test('should be able to override the default json parser after removeAllContentT 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) fastify.close() + done() }) }) }) -test('should be able to override the default plain text parser after removeAllContentTypeParsers', t => { +test('should be able to override the default plain text parser after removeAllContentTypeParsers', (t, done) => { t.plan(5) const fastify = Fastify() @@ -86,14 +85,14 @@ test('should be able to override the default plain text parser after removeAllCo fastify.removeAllContentTypeParsers() fastify.addContentTypeParser('text/plain', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') plainTextParser(payload, function (err, body) { done(err, body) }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -103,15 +102,16 @@ test('should be able to override the default plain text parser after removeAllCo 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello world') fastify.close() + done() }) }) }) -test('should be able to add a custom content type parser after removeAllContentTypeParsers', t => { +test('should be able to add a custom content type parser after removeAllContentTypeParsers', (t, done) => { t.plan(5) const fastify = Fastify() @@ -121,16 +121,15 @@ test('should be able to add a custom content type parser after removeAllContentT }) fastify.removeAllContentTypeParsers() - fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') jsonParser(payload, function (err, body) { done(err, body) }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -140,10 +139,11 @@ test('should be able to add a custom content type parser after removeAllContentT 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) fastify.close() + done() }) }) }) From f7f06d8f356a7d3c965f4c4142e354f2a679c81c Mon Sep 17 00:00:00 2001 From: Keith Ito Date: Mon, 25 Nov 2024 03:52:40 -0800 Subject: [PATCH 0846/1295] docs: fix example for supplying own logger instance (#5857) * Fix docs on passing pino logger directly * Fix lint issue --- docs/Reference/Logging.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index f99c2adc27e..b837a6b8b0b 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -204,15 +204,16 @@ on serializers for more information. *Any logger other than Pino will ignore this option.* You can also supply your own logger instance. Instead of passing configuration -options, pass the instance. The logger you supply must conform to the Pino -interface; that is, it must have the following methods: `info`, `error`, -`debug`, `fatal`, `warn`, `trace`, `silent`, `child` and a string property `level`. +options, pass the instance as `loggerInstance`. The logger you supply must +conform to the Pino interface; that is, it must have the following methods: +`info`, `error`, `debug`, `fatal`, `warn`, `trace`, `silent`, `child` and a +string property `level`. Example: ```js const log = require('pino')({ level: 'info' }) -const fastify = require('fastify')({ logger: log }) +const fastify = require('fastify')({ loggerInstance: log }) log.info('does not have request information') From 82b29412179e75fe1466584bdcc5fdb2ba26b505 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Tue, 26 Nov 2024 12:49:01 +0200 Subject: [PATCH 0847/1295] feat: Add hook name within timeout error message (#5851) Co-authored-by: Igor Savin --- lib/errors.js | 2 +- lib/hooks.js | 5 ++++- test/hooks.on-ready.test.js | 4 ++-- test/internals/errors.test.js | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index 9fafe866521..1a8fe5c9c26 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -189,7 +189,7 @@ const codes = { FST_ERR_HOOK_TIMEOUT: createError( 'FST_ERR_HOOK_TIMEOUT', - "A callback for '%s' hook timed out. You may have forgotten to call 'done' function or to resolve a Promise" + "A callback for '%s' hook%s timed out. You may have forgotten to call 'done' function or to resolve a Promise" ), /** diff --git a/lib/hooks.js b/lib/hooks.js index d744b8716da..1393300cf42 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -98,9 +98,12 @@ function hookRunnerApplication (hookName, boot, server, cb) { next() function exit (err) { + const hookFnName = hooks[i - 1]?.name + const hookFnFragment = hookFnName ? ` "${hookFnName}"` : '' + if (err) { if (err.code === 'AVV_ERR_READY_TIMEOUT') { - err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName)) + err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName, hookFnFragment)) } else { err = AVVIO_ERRORS_MAP[err.code] != null ? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message)) diff --git a/test/hooks.on-ready.test.js b/test/hooks.on-ready.test.js index 4e33f31fcf1..80c7a3e4590 100644 --- a/test/hooks.on-ready.test.js +++ b/test/hooks.on-ready.test.js @@ -322,14 +322,14 @@ t.test('onReady does not call done', t => { t.plan(6) const fastify = Fastify({ pluginTimeout: 500 }) - fastify.addHook('onReady', function (done) { + fastify.addHook('onReady', function someHookName (done) { t.pass('called in root') // done() // don't call done to test timeout }) fastify.ready(err => { t.ok(err) - t.equal(err.message, "A callback for 'onReady' hook timed out. You may have forgotten to call 'done' function or to resolve a Promise") + t.equal(err.message, 'A callback for \'onReady\' hook "someHookName" timed out. You may have forgotten to call \'done\' function or to resolve a Promise') t.equal(err.code, 'FST_ERR_HOOK_TIMEOUT') t.ok(err.cause) t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index ed84a4fef26..f4eb6504d6f 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -302,7 +302,7 @@ test('FST_ERR_HOOK_TIMEOUT', t => { const error = new errors.FST_ERR_HOOK_TIMEOUT() t.assert.strictEqual(error.name, 'FastifyError') t.assert.strictEqual(error.code, 'FST_ERR_HOOK_TIMEOUT') - t.assert.strictEqual(error.message, "A callback for '%s' hook timed out. You may have forgotten to call 'done' function or to resolve a Promise") + t.assert.strictEqual(error.message, "A callback for '%s' hook%s timed out. You may have forgotten to call 'done' function or to resolve a Promise") t.assert.strictEqual(error.statusCode, 500) t.assert.ok(error instanceof Error) }) From 03684b05e212fe6b252a91b06badd11a19284454 Mon Sep 17 00:00:00 2001 From: "Paul \"Joey\" Clark" Date: Wed, 27 Nov 2024 16:22:08 +0800 Subject: [PATCH 0848/1295] docs: make whitespace consistent (#5863) --- docs/Reference/Validation-and-Serialization.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 442139c2578..02445deee26 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -49,16 +49,16 @@ The shared schemas can be reused through the JSON Schema [**`$ref`**](https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8) keyword. Here is an overview of _how_ references work: -+ `myField: { $ref: '#foo'}` will search for field with `$id: '#foo'` inside the ++ `myField: { $ref: '#foo' }` will search for field with `$id: '#foo'` inside the current schema -+ `myField: { $ref: '#/definitions/foo'}` will search for field ++ `myField: { $ref: '#/definitions/foo' }` will search for field `definitions.foo` inside the current schema -+ `myField: { $ref: 'http://url.com/sh.json#'}` will search for a shared schema ++ `myField: { $ref: 'http://url.com/sh.json#' }` will search for a shared schema added with `$id: 'http://url.com/sh.json'` -+ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo'}` will search for ++ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo' }` will search for a shared schema added with `$id: 'http://url.com/sh.json'` and will use the field `definitions.foo` -+ `myField: { $ref: 'http://url.com/sh.json#foo'}` will search for a shared ++ `myField: { $ref: 'http://url.com/sh.json#foo' }` will search for a shared schema added with `$id: 'http://url.com/sh.json'` and it will look inside of it for object with `$id: '#foo'` From 24ebf0591fe5ccab6f9de1e803f38e57815fc1d1 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 28 Nov 2024 08:56:12 +0100 Subject: [PATCH 0849/1295] test: migrated fastify-instance.test.js from tap to node:test (#5859) --- test/fastify-instance.test.js | 67 +++++++++++++++++------------------ 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index e03ce822fc3..079b3502818 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const os = require('node:os') @@ -13,7 +12,7 @@ const { test('root fastify instance is an object', t => { t.plan(1) - t.type(Fastify(), 'object') + t.assert.strictEqual(typeof Fastify(), 'object') }) test('fastify instance should contains ajv options', t => { @@ -25,7 +24,7 @@ test('fastify instance should contains ajv options', t => { } } }) - t.same(fastify[kOptions].ajv, { + t.assert.deepStrictEqual(fastify[kOptions].ajv, { customOptions: { nullable: false }, @@ -43,7 +42,7 @@ test('fastify instance should contains ajv options.plugins nested arrays', t => plugins: [[]] } }) - t.same(fastify[kOptions].ajv, { + t.assert.deepStrictEqual(fastify[kOptions].ajv, { customOptions: { nullable: false }, @@ -53,7 +52,7 @@ test('fastify instance should contains ajv options.plugins nested arrays', t => test('fastify instance get invalid ajv options', t => { t.plan(1) - t.throws(() => Fastify({ + t.assert.throws(() => Fastify({ ajv: { customOptions: 8 } @@ -62,7 +61,7 @@ test('fastify instance get invalid ajv options', t => { test('fastify instance get invalid ajv options.plugins', t => { t.plan(1) - t.throws(() => Fastify({ + t.assert.throws(() => Fastify({ ajv: { customOptions: {}, plugins: 8 @@ -73,9 +72,9 @@ test('fastify instance get invalid ajv options.plugins', t => { test('fastify instance should contain default errorHandler', t => { t.plan(3) const fastify = Fastify() - t.ok(fastify[kErrorHandler].func instanceof Function) - t.same(fastify.errorHandler, fastify[kErrorHandler].func) - t.same(Object.getOwnPropertyDescriptor(fastify, 'errorHandler').set, undefined) + t.assert.ok(fastify[kErrorHandler].func instanceof Function) + t.assert.deepStrictEqual(fastify.errorHandler, fastify[kErrorHandler].func) + t.assert.deepStrictEqual(Object.getOwnPropertyDescriptor(fastify, 'errorHandler').set, undefined) }) test('errorHandler in plugin should be separate from the external one', async t => { @@ -89,24 +88,24 @@ test('errorHandler in plugin should be separate from the external one', async t instance.setErrorHandler(inPluginErrHandler) - t.notSame(instance.errorHandler, fastify.errorHandler) - t.equal(instance.errorHandler.name, 'bound inPluginErrHandler') + t.assert.notDeepStrictEqual(instance.errorHandler, fastify.errorHandler) + t.assert.strictEqual(instance.errorHandler.name, 'bound inPluginErrHandler') done() }) await fastify.ready() - t.ok(fastify[kErrorHandler].func instanceof Function) - t.same(fastify.errorHandler, fastify[kErrorHandler].func) + t.assert.ok(fastify[kErrorHandler].func instanceof Function) + t.assert.deepStrictEqual(fastify.errorHandler, fastify[kErrorHandler].func) }) test('fastify instance should contain default childLoggerFactory', t => { t.plan(3) const fastify = Fastify() - t.ok(fastify[kChildLoggerFactory] instanceof Function) - t.same(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) - t.same(Object.getOwnPropertyDescriptor(fastify, 'childLoggerFactory').set, undefined) + t.assert.ok(fastify[kChildLoggerFactory] instanceof Function) + t.assert.deepStrictEqual(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) + t.assert.deepStrictEqual(Object.getOwnPropertyDescriptor(fastify, 'childLoggerFactory').set, undefined) }) test('childLoggerFactory in plugin should be separate from the external one', async t => { @@ -120,16 +119,16 @@ test('childLoggerFactory in plugin should be separate from the external one', as instance.setChildLoggerFactory(inPluginLoggerFactory) - t.notSame(instance.childLoggerFactory, fastify.childLoggerFactory) - t.equal(instance.childLoggerFactory.name, 'inPluginLoggerFactory') + t.assert.notDeepStrictEqual(instance.childLoggerFactory, fastify.childLoggerFactory) + t.assert.strictEqual(instance.childLoggerFactory.name, 'inPluginLoggerFactory') done() }) await fastify.ready() - t.ok(fastify[kChildLoggerFactory] instanceof Function) - t.same(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) + t.assert.ok(fastify[kChildLoggerFactory] instanceof Function) + t.assert.deepStrictEqual(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) }) test('ready should resolve in order when called multiply times (promises only)', async (t) => { @@ -142,7 +141,7 @@ test('ready should resolve in order when called multiply times (promises only)', await Promise.all(promises) - t.strictSame(result, expectedOrder, 'Should resolve in order') + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') }) test('ready should reject in order when called multiply times (promises only)', async (t) => { @@ -159,7 +158,7 @@ test('ready should reject in order when called multiply times (promises only)', await Promise.all(promises) - t.strictSame(result, expectedOrder, 'Should resolve in order') + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') }) test('ready should reject in order when called multiply times (callbacks only)', async (t) => { @@ -174,10 +173,10 @@ test('ready should reject in order when called multiply times (callbacks only)', expectedOrder.map((id) => app.ready(() => result.push(id))) await app.ready().catch(err => { - t.equal(err.message, 'test') + t.assert.strictEqual(err.message, 'test') }) - t.strictSame(result, expectedOrder, 'Should resolve in order') + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') }) test('ready should resolve in order when called multiply times (callbacks only)', async (t) => { @@ -189,7 +188,7 @@ test('ready should resolve in order when called multiply times (callbacks only)' await app.ready() - t.strictSame(result, expectedOrder, 'Should resolve in order') + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') }) test('ready should resolve in order when called multiply times (mixed)', async (t) => { @@ -207,7 +206,7 @@ test('ready should resolve in order when called multiply times (mixed)', async ( await app.ready() - t.strictSame(result, expectedOrder, 'Should resolve in order') + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') }) test('ready should reject in order when called multiply times (mixed)', async (t) => { @@ -228,10 +227,10 @@ test('ready should reject in order when called multiply times (mixed)', async (t } await app.ready().catch(err => { - t.equal(err.message, 'test') + t.assert.strictEqual(err.message, 'test') }) - t.strictSame(result, expectedOrder, 'Should resolve in order') + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') }) test('ready should resolve in order when called multiply times (mixed)', async (t) => { @@ -249,7 +248,7 @@ test('ready should resolve in order when called multiply times (mixed)', async ( await app.ready() - t.strictSame(result, expectedOrder, 'Should resolve in order') + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') }) test('fastify instance should contains listeningOrigin property (with port and host)', async t => { @@ -258,7 +257,7 @@ test('fastify instance should contains listeningOrigin property (with port and h const host = '127.0.0.1' const fastify = Fastify() await fastify.listen({ port, host }) - t.same(fastify.listeningOrigin, `http://${host}:${port}`) + t.assert.deepStrictEqual(fastify.listeningOrigin, `http://${host}:${port}`) await fastify.close() }) @@ -268,7 +267,7 @@ test('fastify instance should contains listeningOrigin property (with port and h const host = '127.0.0.1' const fastify = Fastify({ https: {} }) await fastify.listen({ port, host }) - t.same(fastify.listeningOrigin, `https://${host}:${port}`) + t.assert.deepStrictEqual(fastify.listeningOrigin, `https://${host}:${port}`) await fastify.close() }) @@ -276,7 +275,7 @@ test('fastify instance should contains listeningOrigin property (unix socket)', const fastify = Fastify() const path = `fastify.${Date.now()}.sock` await fastify.listen({ path }) - t.same(fastify.listeningOrigin, path) + t.assert.deepStrictEqual(fastify.listeningOrigin, path) await fastify.close() }) @@ -286,6 +285,6 @@ test('fastify instance should contains listeningOrigin property (IPv6)', async t const host = '::1' const fastify = Fastify() await fastify.listen({ port, host }) - t.same(fastify.listeningOrigin, `http://[::1]:${port}`) + t.assert.deepStrictEqual(fastify.listeningOrigin, `http://[::1]:${port}`) await fastify.close() }) From 9705e2412e8cecdb713d520c8d08d1603f17eec7 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 28 Nov 2024 08:59:24 +0100 Subject: [PATCH 0850/1295] test: migrated request-id.test.js from tap to node:test (#5858) --- test/request-id.test.js | 56 +++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/test/request-id.test.js b/test/request-id.test.js index 0ad1bfc8b34..19b0edb279f 100644 --- a/test/request-id.test.js +++ b/test/request-id.test.js @@ -1,10 +1,10 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat -t.test('The request id header key can be customized', async (t) => { +test('The request id header key can be customized', async (t) => { t.plan(2) const REQUEST_ID = '42' @@ -13,16 +13,16 @@ t.test('The request id header key can be customized', async (t) => { }) fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) reply.send({ id: req.id }) }) const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } }) const body = await response.json() - t.equal(body.id, REQUEST_ID) + t.assert.strictEqual(body.id, REQUEST_ID) }) -t.test('The request id header key can be customized', async (t) => { +test('The request id header key can be customized', async (t) => { t.plan(2) const REQUEST_ID = '42' @@ -31,16 +31,16 @@ t.test('The request id header key can be customized', async (t) => { }) fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) reply.send({ id: req.id }) }) const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'MY-CUSTOM-REQUEST-ID': REQUEST_ID } }) const body = await response.json() - t.equal(body.id, REQUEST_ID) + t.assert.strictEqual(body.id, REQUEST_ID) }) -t.test('The request id header key can be customized', (t) => { +test('The request id header key can be customized', (t, done) => { t.plan(4) const REQUEST_ID = '42' @@ -49,13 +49,14 @@ t.test('The request id header key can be customized', (t) => { }) fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) reply.send({ id: req.id }) }) + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, (err, address) => { - t.error(err) - t.teardown(() => fastify.close()) + t.assert.ifError(err) sget({ method: 'GET', @@ -64,13 +65,14 @@ t.test('The request id header key can be customized', (t) => { 'my-custom-request-id': REQUEST_ID } }, (err, response, body) => { - t.error(err) - t.equal(body.toString(), `{"id":"${REQUEST_ID}"}`) + t.assert.ifError(err) + t.assert.strictEqual(body.toString(), `{"id":"${REQUEST_ID}"}`) + done() }) }) }) -t.test('The request id header key can be customized', (t) => { +test('The request id header key can be customized', (t, done) => { t.plan(4) const REQUEST_ID = '42' @@ -79,13 +81,14 @@ t.test('The request id header key can be customized', (t) => { }) fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) reply.send({ id: req.id }) }) + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, (err, address) => { - t.error(err) - t.teardown(() => fastify.close()) + t.assert.ifError(err) sget({ method: 'GET', @@ -94,13 +97,14 @@ t.test('The request id header key can be customized', (t) => { 'MY-CUSTOM-REQUEST-ID': REQUEST_ID } }, (err, response, body) => { - t.error(err) - t.equal(body.toString(), `{"id":"${REQUEST_ID}"}`) + t.assert.ifError(err) + t.assert.strictEqual(body.toString(), `{"id":"${REQUEST_ID}"}`) + done() }) }) }) -t.test('The request id header key can be customized', (t) => { +test('The request id header key can be customized', (t, done) => { t.plan(4) const REQUEST_ID = '42' @@ -109,13 +113,14 @@ t.test('The request id header key can be customized', (t) => { }) fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) reply.send({ id: req.id }) }) + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, (err, address) => { - t.error(err) - t.teardown(() => fastify.close()) + t.assert.ifError(err) sget({ method: 'GET', @@ -124,8 +129,9 @@ t.test('The request id header key can be customized', (t) => { 'MY-CUSTOM-REQUEST-ID': REQUEST_ID } }, (err, response, body) => { - t.error(err) - t.equal(body.toString(), `{"id":"${REQUEST_ID}"}`) + t.assert.ifError(err) + t.assert.strictEqual(body.toString(), `{"id":"${REQUEST_ID}"}`) + done() }) }) }) From a212b629735a27a524205277f2c82e57c237e477 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 30 Nov 2024 10:45:24 +0100 Subject: [PATCH 0851/1295] test: migrated tests from tap to node test (#5839) --- test/stream-serializers.test.js | 17 +++++++++------- test/sync-routes.test.js | 36 ++++++++++++++++----------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/test/stream-serializers.test.js b/test/stream-serializers.test.js index 13f9e49c97a..2df3ad5feb0 100644 --- a/test/stream-serializers.test.js +++ b/test/stream-serializers.test.js @@ -1,18 +1,19 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const Reply = require('../lib/reply') -test('should serialize reply when response stream is ended', t => { - t.plan(3) +test('should serialize reply when response stream is ended', (t, done) => { + t.plan(5) + const stream = require('node:stream') const fastify = Fastify({ logger: { serializers: { res (reply) { - t.type(reply, Reply) + t.assert.strictEqual(reply instanceof Reply, true) + t.assert.ok('passed') return reply } } @@ -27,11 +28,13 @@ test('should serialize reply when response stream is ended', t => { reply.raw.end(Buffer.from('hello\n')) }) + t.after(() => fastify.close()) + fastify.inject({ url: '/error', method: 'GET' }, (err) => { - t.error(err) - fastify.close() + t.assert.ifError(err) + done() }) }) diff --git a/test/sync-routes.test.js b/test/sync-routes.test.js index 9491a2cfd78..9f47d81c53d 100644 --- a/test/sync-routes.test.js +++ b/test/sync-routes.test.js @@ -1,32 +1,32 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') test('sync route', async t => { - const app = Fastify() - t.teardown(app.close.bind(app)) - app.get('/', () => 'hello world') - const res = await app.inject('/') - t.equal(res.statusCode, 200) - t.equal(res.body, 'hello world') + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', () => 'hello world') + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'hello world') }) test('sync route return null', async t => { - const app = Fastify() - t.teardown(app.close.bind(app)) - app.get('/', () => null) - const res = await app.inject('/') - t.equal(res.statusCode, 200) - t.equal(res.body, 'null') + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', () => null) + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'null') }) test('sync route, error', async t => { - const app = Fastify() - t.teardown(app.close.bind(app)) - app.get('/', () => { + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', () => { throw new Error('kaboom') }) - const res = await app.inject('/') - t.equal(res.statusCode, 500) + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 500) }) From 6a33e781d29be5886547000edcbd5640425a7eb9 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 30 Nov 2024 11:53:46 +0100 Subject: [PATCH 0852/1295] test: migrated router-options.test.js from tap to node:test (#5840) --- test/router-options.test.js | 157 ++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 77 deletions(-) diff --git a/test/router-options.test.js b/test/router-options.test.js index fd48fbfe78e..e575a76a994 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -1,7 +1,7 @@ 'use strict' const split = require('split2') -const test = require('tap').test +const { test } = require('node:test') const Fastify = require('../') const { FST_ERR_BAD_URL, @@ -19,12 +19,12 @@ test('Should honor ignoreTrailingSlash option', async t => { }) let res = await fastify.inject('/test') - t.equal(res.statusCode, 200) - t.equal(res.payload.toString(), 'test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') res = await fastify.inject('/test/') - t.equal(res.statusCode, 200) - t.equal(res.payload.toString(), 'test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') }) test('Should honor ignoreDuplicateSlashes option', async t => { @@ -38,12 +38,12 @@ test('Should honor ignoreDuplicateSlashes option', async t => { }) let res = await fastify.inject('/test/test/test') - t.equal(res.statusCode, 200) - t.equal(res.payload.toString(), 'test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') res = await fastify.inject('/test//test///test') - t.equal(res.statusCode, 200) - t.equal(res.payload.toString(), 'test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') }) test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', async t => { @@ -58,40 +58,35 @@ test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', asyn }) let res = await fastify.inject('/test/test/test/') - t.equal(res.statusCode, 200) - t.equal(res.payload.toString(), 'test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') res = await fastify.inject('/test//test///test//') - t.equal(res.statusCode, 200) - t.equal(res.payload.toString(), 'test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') }) -test('Should honor maxParamLength option', t => { - t.plan(4) +test('Should honor maxParamLength option', async (t) => { const fastify = Fastify({ maxParamLength: 10 }) fastify.get('/test/:id', (req, reply) => { reply.send({ hello: 'world' }) }) - fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/test/123456789' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) }) + t.assert.strictEqual(res.statusCode, 200) - fastify.inject({ + const resError = await fastify.inject({ method: 'GET', url: '/test/123456789abcd' - }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) }) + t.assert.strictEqual(resError.statusCode, 404) }) -test('Should expose router options via getters on request and reply', t => { +test('Should expose router options via getters on request and reply', (t, done) => { t.plan(9) const fastify = Fastify() const expectedSchema = { @@ -106,13 +101,13 @@ test('Should expose router options via getters on request and reply', t => { fastify.get('/test/:id', { schema: expectedSchema }, (req, reply) => { - t.equal(reply.routeOptions.config.url, '/test/:id') - t.equal(reply.routeOptions.config.method, 'GET') - t.same(req.routeOptions.schema, expectedSchema) - t.equal(typeof req.routeOptions.handler, 'function') - t.equal(req.routeOptions.config.url, '/test/:id') - t.equal(req.routeOptions.config.method, 'GET') - t.equal(req.is404, false) + t.assert.strictEqual(reply.routeOptions.config.url, '/test/:id') + t.assert.strictEqual(reply.routeOptions.config.method, 'GET') + t.assert.deepStrictEqual(req.routeOptions.schema, expectedSchema) + t.assert.strictEqual(typeof req.routeOptions.handler, 'function') + t.assert.strictEqual(req.routeOptions.config.url, '/test/:id') + t.assert.strictEqual(req.routeOptions.config.method, 'GET') + t.assert.strictEqual(req.is404, false) reply.send({ hello: 'world' }) }) @@ -120,17 +115,18 @@ test('Should expose router options via getters on request and reply', t => { method: 'GET', url: '/test/123456789' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) -test('Should set is404 flag for unmatched paths', t => { +test('Should set is404 flag for unmatched paths', (t, done) => { t.plan(3) const fastify = Fastify() fastify.setNotFoundHandler((req, reply) => { - t.equal(req.is404, true) + t.assert.strictEqual(req.is404, true) reply.code(404).send({ error: 'Not Found', message: 'Four oh for', statusCode: 404 }) }) @@ -138,19 +134,20 @@ test('Should set is404 flag for unmatched paths', t => { method: 'GET', url: '/nonexist/123456789' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 404) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 404) + done() }) }) -test('Should honor frameworkErrors option - FST_ERR_BAD_URL', t => { +test('Should honor frameworkErrors option - FST_ERR_BAD_URL', (t, done) => { t.plan(3) const fastify = Fastify({ frameworkErrors: function (err, req, res) { if (err instanceof FST_ERR_BAD_URL) { - t.ok(true) + t.assert.ok(true) } else { - t.fail() + t.assert.fail() } res.send(`${err.message} - ${err.code}`) } @@ -166,13 +163,14 @@ test('Should honor frameworkErrors option - FST_ERR_BAD_URL', t => { url: '/test/%world' }, (err, res) => { - t.error(err) - t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + done() } ) }) -test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_BAD_URL', t => { +test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => { t.plan(8) const REQ_ID = 'REQ-1234' @@ -180,15 +178,15 @@ test('Should supply Fastify request to the logger in frameworkErrors wrapper - F const fastify = Fastify({ frameworkErrors: function (err, req, res) { - t.same(req.id, REQ_ID) - t.same(req.raw.httpVersion, '1.1') + t.assert.deepStrictEqual(req.id, REQ_ID) + t.assert.deepStrictEqual(req.raw.httpVersion, '1.1') res.send(`${err.message} - ${err.code}`) }, logger: { stream: logStream, serializers: { req (request) { - t.same(request.id, REQ_ID) + t.assert.deepStrictEqual(request.id, REQ_ID) return { httpVersion: request.raw.httpVersion } } } @@ -201,9 +199,9 @@ test('Should supply Fastify request to the logger in frameworkErrors wrapper - F }) logStream.on('data', (json) => { - t.same(json.msg, 'incoming request') - t.same(json.reqId, REQ_ID) - t.same(json.req.httpVersion, '1.1') + t.assert.deepStrictEqual(json.msg, 'incoming request') + t.assert.deepStrictEqual(json.reqId, REQ_ID) + t.assert.deepStrictEqual(json.req.httpVersion, '1.1') }) fastify.inject( @@ -212,13 +210,14 @@ test('Should supply Fastify request to the logger in frameworkErrors wrapper - F url: '/test/%world' }, (err, res) => { - t.error(err) - t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + done() } ) }) -test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_BAD_URL', t => { +test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => { t.plan(2) const logStream = split(JSON.parse) @@ -232,10 +231,10 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST stream: logStream, serializers: { req () { - t.fail('should not be called') + t.assert.fail('should not be called') }, res () { - t.fail('should not be called') + t.assert.fail('should not be called') } } } @@ -246,7 +245,7 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST }) logStream.on('data', (json) => { - t.fail('should not be called') + t.assert.fail('should not be called') }) fastify.inject( @@ -255,13 +254,14 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST url: '/test/%world' }, (err, res) => { - t.error(err) - t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + done() } ) }) -test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', t => { +test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', (t, done) => { t.plan(3) const constraint = { @@ -282,9 +282,9 @@ test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', t => { const fastify = Fastify({ frameworkErrors: function (err, req, res) { if (err instanceof FST_ERR_ASYNC_CONSTRAINT) { - t.ok(true) + t.assert.ok(true) } else { - t.fail() + t.assert.fail() } res.send(`${err.message} - ${err.code}`) }, @@ -306,13 +306,14 @@ test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', t => { url: '/' }, (err, res) => { - t.error(err) - t.equal(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + t.assert.ifError(err) + t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + done() } ) }) -test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', t => { +test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => { t.plan(8) const constraint = { @@ -336,15 +337,15 @@ test('Should supply Fastify request to the logger in frameworkErrors wrapper - F const fastify = Fastify({ constraints: { secret: constraint }, frameworkErrors: function (err, req, res) { - t.same(req.id, REQ_ID) - t.same(req.raw.httpVersion, '1.1') + t.assert.deepStrictEqual(req.id, REQ_ID) + t.assert.deepStrictEqual(req.raw.httpVersion, '1.1') res.send(`${err.message} - ${err.code}`) }, logger: { stream: logStream, serializers: { req (request) { - t.same(request.id, REQ_ID) + t.assert.deepStrictEqual(request.id, REQ_ID) return { httpVersion: request.raw.httpVersion } } } @@ -362,9 +363,9 @@ test('Should supply Fastify request to the logger in frameworkErrors wrapper - F }) logStream.on('data', (json) => { - t.same(json.msg, 'incoming request') - t.same(json.reqId, REQ_ID) - t.same(json.req.httpVersion, '1.1') + t.assert.deepStrictEqual(json.msg, 'incoming request') + t.assert.deepStrictEqual(json.reqId, REQ_ID) + t.assert.deepStrictEqual(json.req.httpVersion, '1.1') }) fastify.inject( @@ -373,13 +374,14 @@ test('Should supply Fastify request to the logger in frameworkErrors wrapper - F url: '/' }, (err, res) => { - t.error(err) - t.equal(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + t.assert.ifError(err) + t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + done() } ) }) -test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', t => { +test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => { t.plan(2) const constraint = { @@ -409,10 +411,10 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST stream: logStream, serializers: { req () { - t.fail('should not be called') + t.assert.fail('should not be called') }, res () { - t.fail('should not be called') + t.assert.fail('should not be called') } } } @@ -428,7 +430,7 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST }) logStream.on('data', (json) => { - t.fail('should not be called') + t.assert.fail('should not be called') }) fastify.inject( @@ -437,8 +439,9 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST url: '/' }, (err, res) => { - t.error(err) - t.equal(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + t.assert.ifError(err) + t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + done() } ) }) From 2687c536f61d8accc9ae233125d998a15497563b Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 30 Nov 2024 12:24:51 +0100 Subject: [PATCH 0853/1295] test: migrated route.8.test.js from tap to node:test (#5864) --- test/route.8.test.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/route.8.test.js b/test/route.8.test.js index cf9e4466f1c..6289eb9d692 100644 --- a/test/route.8.test.js +++ b/test/route.8.test.js @@ -1,9 +1,8 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat -const Fastify = require('../fastify') +const Fastify = require('..') const { FST_ERR_INVALID_URL } = require('../lib/errors') @@ -24,9 +23,9 @@ test('Request and Reply share the route options', async t => { url: '/', config, handler: (req, reply) => { - t.same(req.routeOptions, reply.routeOptions) - t.same(req.routeOptions.config, reply.routeOptions.config) - t.match(req.routeOptions.config, config, 'there are url and method additional properties') + t.assert.deepStrictEqual(req.routeOptions, reply.routeOptions) + t.assert.deepStrictEqual(req.routeOptions.config, reply.routeOptions.config) + t.assert.match(req.routeOptions.config, config, 'there are url and method additional properties') reply.send({ hello: 'world' }) } @@ -62,10 +61,10 @@ test('Will not try to re-createprefixed HEAD route if it already exists and expo await fastify.ready() - t.ok(true) + t.assert.ok(true) }) -test('route with non-english characters', t => { +test('route with non-english characters', (t, done) => { t.plan(4) const fastify = Fastify() @@ -75,16 +74,17 @@ test('route with non-english characters', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: getServerUrl(fastify) + encodeURI('/föö') }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'here /föö') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'here /föö') + done() }) }) }) @@ -96,7 +96,7 @@ test('invalid url attribute - non string URL', t => { try { fastify.get(/^\/(donations|skills|blogs)/, () => { }) } catch (error) { - t.equal(error.code, FST_ERR_INVALID_URL().code) + t.assert.strictEqual(error.code, FST_ERR_INVALID_URL().code) } }) @@ -117,7 +117,7 @@ test('exposeHeadRoute should not reuse the same route option', async t => { }) fastify.addHook('onRoute', function (routeOption) { - t.equal(routeOption.onRequest.length, 1) + t.assert.strictEqual(routeOption.onRequest.length, 1) }) fastify.route({ @@ -140,7 +140,7 @@ test('using fastify.all when a catchall is defined does not degrade performance' fastify.all(`/${i}`, async (_, reply) => reply.json({ ok: true })) } - t.pass() + t.assert.ok("fastify.all doesn't degrade performance") }) test('Adding manually HEAD route after GET with the same path throws Fastify duplicated route instance error', t => { @@ -164,9 +164,9 @@ test('Adding manually HEAD route after GET with the same path throws Fastify dup reply.send({ hello: 'world' }) } }) - t.fail('Should throw fastify duplicated route declaration') + t.assert.fail('Should throw fastify duplicated route declaration') } catch (error) { - t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') + t.assert.strictEqual(error.code, 'FST_ERR_DUPLICATED_ROUTE') } }) @@ -198,7 +198,7 @@ test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /1', asyn method: 'HEAD' }) - t.equal(result.headers['x-content-type'], 'application/fastify') + t.assert.strictEqual(result.headers['x-content-type'], 'application/fastify') }) test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /2', async (t) => { @@ -229,5 +229,5 @@ test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /2', asyn method: 'HEAD' }) - t.equal(result.headers['x-content-type'], 'application/fastify') + t.assert.strictEqual(result.headers['x-content-type'], 'application/fastify') }) From db1fa005f8c22d6d1cf22f16f60a32adefd46acb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:19:34 +0000 Subject: [PATCH 0854/1295] chore: Bump lycheeverse/lychee-action from 2.0.2 to 2.1.0 (#5869) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.0.2 to 2.1.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/7cd0af4c74a61395d455af97419279d86aafaede...f81112d0d2814ded911bd23e3beaa9dda9093915) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index c3d3328472f..b36aaf4030f 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede + uses: lycheeverse/lychee-action@f81112d0d2814ded911bd23e3beaa9dda9093915 with: fail: true # As external links behavior is not predictable, we check only internal links From f1065ccaecf8de35b65fae7d450fafa6ad2eb851 Mon Sep 17 00:00:00 2001 From: Reidner <121988116+reidn3r@users.noreply.github.com> Date: Mon, 2 Dec 2024 07:56:27 -0300 Subject: [PATCH 0855/1295] test: migrated validation-error-handling from tap to node:test (#5856) * tests: migrated validation-error-handling from tap to node:test * runned lint:fix --- test/validation-error-handling.test.js | 544 ++++++++++++------------- 1 file changed, 259 insertions(+), 285 deletions(-) diff --git a/test/validation-error-handling.test.js b/test/validation-error-handling.test.js index d87793fc654..6177332dd71 100644 --- a/test/validation-error-handling.test.js +++ b/test/validation-error-handling.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Joi = require('joi') const Fastify = require('..') @@ -19,59 +19,56 @@ function echoBody (req, reply) { reply.code(200).send(req.body.name) } -test('should work with valid payload', t => { - t.plan(3) +test('should work with valid payload', async (t) => { + t.plan(2) const fastify = Fastify() fastify.post('/', { schema }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { name: 'michelangelo', work: 'sculptor, painter, architect and poet' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.payload, 'michelangelo') - t.equal(res.statusCode, 200) }) + t.assert.deepStrictEqual(response.payload, 'michelangelo') + t.assert.strictEqual(response.statusCode, 200) }) -test('should fail immediately with invalid payload', t => { - t.plan(3) +test('should fail immediately with invalid payload', async (t) => { + t.plan(2) const fastify = Fastify() fastify.post('/', { schema }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: "body must have required property 'name'" - }) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: "body must have required property 'name'" + }) + t.assert.strictEqual(response.statusCode, 400) }) -test('should be able to use setErrorHandler specify custom validation error', t => { - t.plan(3) +test('should be able to use setErrorHandler specify custom validation error', async (t) => { + t.plan(2) const fastify = Fastify() fastify.post('/', { schema }, function (req, reply) { - t.fail('should not be here') + t.assert.fail('should not be here') reply.code(200).send(req.body.name) }) @@ -81,25 +78,24 @@ test('should be able to use setErrorHandler specify custom validation error', t } }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { - statusCode: 422, - error: 'Unprocessable Entity', - message: 'validation failed' - }) - t.equal(res.statusCode, 422) }) + + t.assert.deepStrictEqual(JSON.parse(response.payload), { + statusCode: 422, + error: 'Unprocessable Entity', + message: 'validation failed' + }) + t.assert.strictEqual(response.statusCode, 422) }) -test('validation error has 400 statusCode set', t => { - t.plan(3) +test('validation error has 400 statusCode set', async (t) => { + t.plan(2) const fastify = Fastify() @@ -114,23 +110,22 @@ test('validation error has 400 statusCode set', t => { fastify.post('/', { schema }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - message: "body must have required property 'name'" - }) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + message: "body must have required property 'name'" + }) + t.assert.strictEqual(response.statusCode, 400) }) -test('error inside custom error handler should have validationContext', t => { +test('error inside custom error handler should have validationContext', async (t) => { t.plan(1) const fastify = Fastify() @@ -143,26 +138,26 @@ test('error inside custom error handler should have validationContext', t => { } } }, function (req, reply) { - t.fail('should not be here') + t.assert.fail('should not be here') reply.code(200).send(req.body.name) }) fastify.setErrorHandler(function (error, request, reply) { - t.equal(error.validationContext, 'body') + t.assert.strictEqual(error.validationContext, 'body') reply.status(500).send(error) }) - fastify.inject({ + await fastify.inject({ method: 'POST', payload: { name: 'michelangelo', work: 'artist' }, url: '/' - }, () => {}) + }) }) -test('error inside custom error handler should have validationContext if specified by custom error handler', t => { +test('error inside custom error handler should have validationContext if specified by custom error handler', async (t) => { t.plan(1) const fastify = Fastify() @@ -177,27 +172,27 @@ test('error inside custom error handler should have validationContext if specifi } } }, function (req, reply) { - t.fail('should not be here') + t.assert.fail('should not be here') reply.code(200).send(req.body.name) }) fastify.setErrorHandler(function (error, request, reply) { - t.equal(error.validationContext, 'customContext') + t.assert.strictEqual(error.validationContext, 'customContext') reply.status(500).send(error) }) - fastify.inject({ + await fastify.inject({ method: 'POST', payload: { name: 'michelangelo', work: 'artist' }, url: '/' - }, () => {}) + }) }) -test('should be able to attach validation to request', t => { - t.plan(3) +test('should be able to attach validation to request', async (t) => { + t.plan(2) const fastify = Fastify() @@ -205,56 +200,53 @@ test('should be able to attach validation to request', t => { reply.code(400).send(req.validationError.validation) }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - - t.same(res.json(), [{ - keyword: 'required', - instancePath: '', - schemaPath: '#/required', - params: { missingProperty: 'name' }, - message: 'must have required property \'name\'' - }]) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response.json(), [{ + keyword: 'required', + instancePath: '', + schemaPath: '#/required', + params: { missingProperty: 'name' }, + message: 'must have required property \'name\'' + }]) + t.assert.strictEqual(response.statusCode, 400) }) -test('should respect when attachValidation is explicitly set to false', t => { - t.plan(3) +test('should respect when attachValidation is explicitly set to false', async (t) => { + t.plan(2) const fastify = Fastify() fastify.post('/', { schema, attachValidation: false }, function (req, reply) { - t.fail('should not be here') + t.assert.fail('should not be here') reply.code(200).send(req.validationError.validation) }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: "body must have required property 'name'" - }) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(JSON.parse(response.payload), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: "body must have required property 'name'" + }) + t.assert.strictEqual(response.statusCode, 400) }) -test('Attached validation error should take precedence over setErrorHandler', t => { - t.plan(3) +test('Attached validation error should take precedence over setErrorHandler', async (t) => { + t.plan(2) const fastify = Fastify() @@ -263,26 +255,25 @@ test('Attached validation error should take precedence over setErrorHandler', t }) fastify.setErrorHandler(function (error, request, reply) { - t.fail('should not be here') + t.assert.fail('should not be here') if (error.validation) { reply.status(422).send(new Error('validation failed')) } }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.payload, "Attached: Error: body must have required property 'name'") - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response.payload, "Attached: Error: body must have required property 'name'") + t.assert.strictEqual(response.statusCode, 400) }) -test('should handle response validation error', t => { +test('should handle response validation error', async (t) => { t.plan(2) const response = { @@ -306,17 +297,17 @@ test('should handle response validation error', t => { } }) - fastify.inject({ + const injectResponse = await fastify.inject({ method: 'GET', payload: { }, url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}') }) + + t.assert.strictEqual(injectResponse.statusCode, 500) + t.assert.strictEqual(injectResponse.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}') }) -test('should handle response validation error with promises', t => { +test('should handle response validation error with promises', async (t) => { t.plan(2) const response = { @@ -336,17 +327,17 @@ test('should handle response validation error with promises', t => { return Promise.resolve({ work: 'actor' }) }) - fastify.inject({ + const injectResponse = await fastify.inject({ method: 'GET', payload: { }, url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}') }) + + t.assert.strictEqual(injectResponse.statusCode, 500) + t.assert.strictEqual(injectResponse.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}') }) -test('should return a defined output message parsing AJV errors', t => { +test('should return a defined output message parsing AJV errors', async (t) => { t.plan(2) const body = { @@ -361,20 +352,20 @@ test('should return a defined output message parsing AJV errors', t => { const fastify = Fastify() fastify.post('/', { schema: { body } }, function (req, reply) { - t.fail() + t.assert.fail() }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { }, url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body must have required property \'name\'"}') }) + + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body must have required property \'name\'"}') }) -test('should return a defined output message parsing JOI errors', t => { +test('should return a defined output message parsing JOI errors', async (t) => { t.plan(2) const body = Joi.object().keys({ @@ -391,20 +382,20 @@ test('should return a defined output message parsing JOI errors', t => { } }, function (req, reply) { - t.fail() + t.assert.fail() }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: {}, url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"\\"name\\" is required"}') }) + + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"\\"name\\" is required"}') }) -test('should return a defined output message parsing JOI error details', t => { +test('should return a defined output message parsing JOI error details', async (t) => { t.plan(2) const body = Joi.object().keys({ @@ -424,118 +415,115 @@ test('should return a defined output message parsing JOI error details', t => { } }, function (req, reply) { - t.fail() + t.assert.fail() }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: {}, url: '/' - }, (err, res) => { - t.error(err) - t.equal(res.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body \\"name\\" is required"}') }) + + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body \\"name\\" is required"}') }) -test('the custom error formatter context must be the server instance', t => { - t.plan(4) +test('the custom error formatter context must be the server instance', async (t) => { + t.plan(3) const fastify = Fastify() fastify.setSchemaErrorFormatter(function (errors, dataVar) { - t.same(this, fastify) + t.assert.deepStrictEqual(this, fastify) return new Error('my error') }) fastify.post('/', { schema }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'my error' - }) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'my error' + }) + t.assert.strictEqual(response.statusCode, 400) }) -test('the custom error formatter context must be the server instance in options', t => { - t.plan(4) +test('the custom error formatter context must be the server instance in options', async (t) => { + t.plan(3) const fastify = Fastify({ schemaErrorFormatter: function (errors, dataVar) { - t.same(this, fastify) + t.assert.deepStrictEqual(this, fastify) return new Error('my error') } }) fastify.post('/', { schema }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'my error' - }) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'my error' + }) + t.assert.strictEqual(response.statusCode, 400) }) -test('should call custom error formatter', t => { - t.plan(9) +test('should call custom error formatter', async (t) => { + t.plan(8) const fastify = Fastify({ schemaErrorFormatter: (errors, dataVar) => { - t.equal(errors.length, 1) - t.equal(errors[0].message, "must have required property 'name'") - t.equal(errors[0].keyword, 'required') - t.equal(errors[0].schemaPath, '#/required') - t.same(errors[0].params, { + t.assert.strictEqual(errors.length, 1) + t.assert.strictEqual(errors[0].message, "must have required property 'name'") + t.assert.strictEqual(errors[0].keyword, 'required') + t.assert.strictEqual(errors[0].schemaPath, '#/required') + t.assert.deepStrictEqual(errors[0].params, { missingProperty: 'name' }) - t.equal(dataVar, 'body') + t.assert.strictEqual(dataVar, 'body') return new Error('my error') } }) fastify.post('/', { schema }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'my error' - }) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'my error' + }) + t.assert.strictEqual(response.statusCode, 400) }) -test('should catch error inside formatter and return message', t => { - t.plan(3) +test('should catch error inside formatter and return message', async (t) => { + t.plan(2) const fastify = Fastify({ schemaErrorFormatter: (errors, dataVar) => { @@ -545,25 +533,23 @@ test('should catch error inside formatter and return message', t => { fastify.post('/', { schema }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 500, - error: 'Internal Server Error', - message: 'abc' - }) - t.equal(res.statusCode, 500) - t.end() }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 500, + error: 'Internal Server Error', + message: 'abc' + }) + t.assert.strictEqual(response.statusCode, 500) }) -test('cannot create a fastify instance with wrong type of errorFormatter', t => { +test('cannot create a fastify instance with wrong type of errorFormatter', async (t) => { t.plan(3) try { @@ -573,7 +559,7 @@ test('cannot create a fastify instance with wrong type of errorFormatter', t => } }) } catch (err) { - t.equal(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') } try { @@ -581,19 +567,19 @@ test('cannot create a fastify instance with wrong type of errorFormatter', t => schemaErrorFormatter: 500 }) } catch (err) { - t.equal(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') } try { const fastify = Fastify() fastify.setSchemaErrorFormatter(500) } catch (err) { - t.equal(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') } }) -test('should register a route based schema error formatter', t => { - t.plan(3) +test('should register a route based schema error formatter', async (t) => { + t.plan(2) const fastify = Fastify() @@ -604,27 +590,25 @@ test('should register a route based schema error formatter', t => { } }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'abc' - }) - t.equal(res.statusCode, 400) - t.end() }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'abc' + }) + t.assert.strictEqual(response.statusCode, 400) }) -test('prefer route based error formatter over global one', t => { - t.plan(9) +test('prefer route based error formatter over global one', async (t) => { + t.plan(6) const fastify = Fastify({ schemaErrorFormatter: (errors, dataVar) => { @@ -648,60 +632,57 @@ test('prefer route based error formatter over global one', t => { fastify.post('/test', { schema }, echoBody) - fastify.inject({ + const response1 = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: '123' - }) - t.equal(res.statusCode, 400) }) - fastify.inject({ + t.assert.deepStrictEqual(response1.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: '123' + }) + t.assert.strictEqual(response1.statusCode, 400) + + const response2 = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/abc' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'abc' - }) - t.equal(res.statusCode, 400) }) - fastify.inject({ + t.assert.deepStrictEqual(response2.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'abc' + }) + t.assert.strictEqual(response2.statusCode, 400) + + const response3 = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/test' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'abc123' - }) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response3.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'abc123' + }) + t.assert.strictEqual(response3.statusCode, 400) }) -test('adding schemaErrorFormatter', t => { - t.plan(3) +test('adding schemaErrorFormatter', async (t) => { + t.plan(2) const fastify = Fastify() @@ -711,27 +692,25 @@ test('adding schemaErrorFormatter', t => { fastify.post('/', { schema }, echoBody) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'abc' - }) - t.equal(res.statusCode, 400) - t.end() }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'abc' + }) + t.assert.strictEqual(response.statusCode, 400) }) -test('plugin override', t => { - t.plan(15) +test('plugin override', async (t) => { + t.plan(10) const fastify = Fastify({ schemaErrorFormatter: (errors, dataVar) => { @@ -772,88 +751,83 @@ test('plugin override', t => { } }, echoBody) - fastify.inject({ + const response1 = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'A' - }) - t.equal(res.statusCode, 400) }) - fastify.inject({ + t.assert.deepStrictEqual(response1.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'A' + }) + t.assert.strictEqual(response1.statusCode, 400) + + const response2 = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/b' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'B' - }) - t.equal(res.statusCode, 400) }) - fastify.inject({ + t.assert.deepStrictEqual(response2.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'B' + }) + t.assert.strictEqual(response2.statusCode, 400) + + const response3 = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/c' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'C' - }) - t.equal(res.statusCode, 400) }) - fastify.inject({ + t.assert.deepStrictEqual(response3.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'C' + }) + t.assert.strictEqual(response3.statusCode, 400) + + const response4 = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/d' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'D' - }) - t.equal(res.statusCode, 400) }) - fastify.inject({ + t.assert.deepStrictEqual(response4.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'D' + }) + t.assert.strictEqual(response4.statusCode, 400) + + const response5 = await fastify.inject({ method: 'POST', payload: { hello: 'michelangelo' }, url: '/stillC' - }, (err, res) => { - t.error(err) - t.same(res.json(), { - statusCode: 400, - code: 'FST_ERR_VALIDATION', - error: 'Bad Request', - message: 'C' - }) - t.equal(res.statusCode, 400) }) + + t.assert.deepStrictEqual(response5.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'C' + }) + t.assert.strictEqual(response5.statusCode, 400) }) From 05464388df27797de8e7dcb8911c9d15cec2eef0 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Tue, 3 Dec 2024 04:50:28 +0100 Subject: [PATCH 0856/1295] test: migrated handler-context.test.js from tap to node:test (#5868) --- test/handler-context.test.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/handler-context.test.js b/test/handler-context.test.js index 048fe92d3b5..868377341cf 100644 --- a/test/handler-context.test.js +++ b/test/handler-context.test.js @@ -1,7 +1,7 @@ 'use strict' -const test = require('tap').test +const { test } = require('node:test') const { kRouteContext } = require('../lib/symbols') -const fastify = require('../') +const fastify = require('..') test('handlers receive correct `this` context', async (t) => { t.plan(4) @@ -17,15 +17,15 @@ test('handlers receive correct `this` context', async (t) => { instance.register(plugin) instance.get('/', function (req, reply) { - t.ok(this.foo) - t.equal(this.foo, 'foo') + t.assert.ok(this.foo) + t.assert.strictEqual(this.foo, 'foo') reply.send() }) await instance.inject('/') - t.ok(instance.foo) - t.equal(instance.foo, 'foo') + t.assert.ok(instance.foo) + t.assert.strictEqual(instance.foo, 'foo') }) test('handlers have access to the internal context', async (t) => { @@ -33,11 +33,11 @@ test('handlers have access to the internal context', async (t) => { const instance = fastify() instance.get('/', { config: { foo: 'bar' } }, function (req, reply) { - t.ok(reply[kRouteContext]) - t.ok(reply[kRouteContext].config) - t.type(reply[kRouteContext].config, Object) - t.ok(reply[kRouteContext].config.foo) - t.equal(reply[kRouteContext].config.foo, 'bar') + t.assert.ok(reply[kRouteContext]) + t.assert.ok(reply[kRouteContext].config) + t.assert.ok(typeof reply[kRouteContext].config, Object) + t.assert.ok(reply[kRouteContext].config.foo) + t.assert.strictEqual(reply[kRouteContext].config.foo, 'bar') reply.send() }) From 87f9f20687c938828f1138f91682d568d2a31e53 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Tue, 3 Dec 2024 17:17:57 +0100 Subject: [PATCH 0857/1295] test: migrated find-route.test.js from tap to node:test (#5867) --- .../{findRoute.test.js => find-route.test.js} | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) rename test/{findRoute.test.js => find-route.test.js} (88%) diff --git a/test/findRoute.test.js b/test/find-route.test.js similarity index 88% rename from test/findRoute.test.js rename to test/find-route.test.js index 48746d96c66..9e39ef49055 100644 --- a/test/findRoute.test.js +++ b/test/find-route.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') const fastifyPlugin = require('fastify-plugin') @@ -15,7 +15,7 @@ test('findRoute should return null when route cannot be found due to a different handler: (req, reply) => reply.send(typeof req.params.artistId) }) - t.equal(fastify.findRoute({ + t.assert.strictEqual(fastify.findRoute({ method: 'POST', url: '/artists/:artistId' }), null) @@ -46,7 +46,8 @@ test('findRoute should return an immutable route to avoid leaking and runtime ro method: 'GET', url: '/artists/:artistId' }) - t.same(route.params, { artistId: ':artistId' }) + + t.assert.strictEqual(route.params.artistId, ':artistId') }) test('findRoute should return null when when url is not passed', t => { @@ -60,7 +61,7 @@ test('findRoute should return null when when url is not passed', t => { handler: (req, reply) => reply.send(typeof req.params.artistId) }) - t.equal(fastify.findRoute({ + t.assert.strictEqual(fastify.findRoute({ method: 'POST' }), null) }) @@ -76,7 +77,7 @@ test('findRoute should return null when route cannot be found due to a different handler: (req, reply) => reply.send(typeof req.params.artistId) }) - t.equal(fastify.findRoute({ + t.assert.strictEqual(fastify.findRoute({ method: 'GET', url: '/books/:bookId' }), null) @@ -99,11 +100,10 @@ test('findRoute should return the route when found', t => { method: 'GET', url: '/artists/:artistId' }) - - t.same(route.params, { artistId: ':artistId' }) + t.assert.strictEqual(route.params.artistId, ':artistId') }) -test('findRoute should work correctly when used within plugins', t => { +test('findRoute should work correctly when used within plugins', (t, done) => { t.plan(1) const fastify = Fastify() const handler = (req, reply) => reply.send(typeof req.params.artistId) @@ -128,7 +128,8 @@ test('findRoute should work correctly when used within plugins', t => { fastify.register(fastifyPlugin(validateRoutePlugin)) fastify.ready(() => { - t.equal(fastify.validateRoutes.validateParams(), true) + t.assert.strictEqual(fastify.validateRoutes.validateParams(), true) + done() }) }) @@ -147,5 +148,5 @@ test('findRoute should not expose store', t => { method: 'GET', url: '/artists/:artistId' }) - t.equal(route.store, undefined) + t.assert.strictEqual(route.store, undefined) }) From 8f3eab6dd7d3524a684f83f5c49d5795f2babe83 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Wed, 4 Dec 2024 14:46:38 +0100 Subject: [PATCH 0858/1295] chore: sponsor link (#5871) Signed-off-by: Manuel Spigolon --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 644be93df64..ab7c3990983 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ downloads](https://img.shields.io/npm/dm/fastify.svg?style=flat)](https://www.np Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yellow.svg)](https://github.com/fastify/fastify/blob/main/SECURITY.md) [![Discord](https://img.shields.io/discord/725613461949906985)](https://discord.gg/fastify) [![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=blue)](https://gitpod.io/#https://github.com/fastify/fastify) -![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/fastify) +[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/fastify)](https://github.com/sponsors/fastify#sponsors) @@ -423,4 +423,4 @@ dependencies: - BSD-2-Clause [hd-link]: https://www.herodevs.com/support/fastify-nes?utm_source=fastify&utm_medium=link&utm_campaign=github_readme -[lts-link]: https://fastify.dev/docs/latest/Reference/LTS/ \ No newline at end of file +[lts-link]: https://fastify.dev/docs/latest/Reference/LTS/ From 67fe1418210fc4defb799cf308942bfea44ae18e Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 5 Dec 2024 09:37:42 +0000 Subject: [PATCH 0859/1295] docs(readme): point ci badge at main branch (#5873) Signed-off-by: Frazer Smith --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ab7c3990983..4a550c1b7d3 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@
-[![CI](https://github.com/fastify/fastify/actions/workflows/ci.yml/badge.svg)](https://github.com/fastify/fastify/actions/workflows/ci.yml) -[![Package Manager -CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml) +![CI](https://github.com/fastify/fastify/actions/workflows/ci.yml/badge.svg?branch=main) +![Package Manager +CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main) [![Web SIte](https://github.com/fastify/fastify/workflows/website/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml) [![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) From 3d1bb6830229a57a5de29d757d88232aae386a12 Mon Sep 17 00:00:00 2001 From: Ran Toledo Date: Thu, 5 Dec 2024 17:07:40 +0200 Subject: [PATCH 0860/1295] test: migrate content-parser test to use node:test (#5847) --- test/content-parser.test.js | 345 +++++++++++++++++++----------------- 1 file changed, 178 insertions(+), 167 deletions(-) diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 228be10818c..83a757765ac 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const keys = require('../lib/symbols') const { FST_ERR_CTP_ALREADY_PRESENT, FST_ERR_CTP_INVALID_TYPE, FST_ERR_CTP_INVALID_MEDIA_TYPE } = require('../lib/errors') @@ -10,21 +9,22 @@ const first = function (req, payload, done) {} const second = function (req, payload, done) {} const third = function (req, payload, done) {} -test('hasContentTypeParser', t => { - test('should know about internal parsers', t => { +test('hasContentTypeParser', async t => { + await t.test('should know about internal parsers', (t, done) => { t.plan(5) const fastify = Fastify() fastify.ready(err => { - t.error(err) - t.ok(fastify.hasContentTypeParser('application/json')) - t.ok(fastify.hasContentTypeParser('text/plain')) - t.ok(fastify.hasContentTypeParser(' text/plain ')) - t.notOk(fastify.hasContentTypeParser('application/jsoff')) + t.assert.ifError(err) + t.assert.ok(fastify.hasContentTypeParser('application/json')) + t.assert.ok(fastify.hasContentTypeParser('text/plain')) + t.assert.ok(fastify.hasContentTypeParser(' text/plain ')) + t.assert.ok(!fastify.hasContentTypeParser('application/jsoff')) + done() }) }) - test('should only work with string and RegExp', t => { + await t.test('should only work with string and RegExp', t => { t.plan(8) const fastify = Fastify() @@ -32,21 +32,22 @@ test('hasContentTypeParser', t => { fastify.addContentTypeParser(/^application\/.+\+xml/, first) fastify.addContentTypeParser('image/gif', first) - t.ok(fastify.hasContentTypeParser('application/json')) - t.ok(fastify.hasContentTypeParser(/^image\/.*/)) - t.ok(fastify.hasContentTypeParser(/^application\/.+\+xml/)) - t.ok(fastify.hasContentTypeParser('image/gif')) - t.notOk(fastify.hasContentTypeParser(/^image\/.+\+xml/)) - t.notOk(fastify.hasContentTypeParser('image/png')) - t.notOk(fastify.hasContentTypeParser('*')) - t.throws(() => fastify.hasContentTypeParser(123), FST_ERR_CTP_INVALID_TYPE) + t.assert.ok(fastify.hasContentTypeParser('application/json')) + t.assert.ok(fastify.hasContentTypeParser(/^image\/.*/)) + t.assert.ok(fastify.hasContentTypeParser(/^application\/.+\+xml/)) + t.assert.ok(fastify.hasContentTypeParser('image/gif')) + t.assert.ok(!fastify.hasContentTypeParser(/^image\/.+\+xml/)) + t.assert.ok(!fastify.hasContentTypeParser('image/png')) + t.assert.ok(!fastify.hasContentTypeParser('*')) + t.assert.throws( + () => fastify.hasContentTypeParser(123), + FST_ERR_CTP_INVALID_TYPE + ) }) - - t.end() }) -test('getParser', t => { - test('should return matching parser', t => { +test('getParser', async t => { + await t.test('should return matching parser', t => { t.plan(6) const fastify = Fastify() @@ -55,62 +56,62 @@ test('getParser', t => { fastify.addContentTypeParser(/^application\/.+\+xml/, second) fastify.addContentTypeParser('text/html', third) - t.equal(fastify[keys.kContentTypeParser].getParser('application/t+xml').fn, second) - t.equal(fastify[keys.kContentTypeParser].getParser('image/png').fn, first) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, third) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html; charset=utf-8').fn, third) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html ; charset=utf-8').fn, third) - t.equal(fastify[keys.kContentTypeParser].getParser('text/htmlINVALID')?.fn, undefined) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('application/t+xml').fn, second) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/png').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, third) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html; charset=utf-8').fn, third) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ; charset=utf-8').fn, third) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/htmlINVALID')?.fn, undefined) }) - test('should return matching parser with caching /1', t => { + await t.test('should return matching parser with caching /1', t => { t.plan(6) const fastify = Fastify() fastify.addContentTypeParser('text/html', first) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 0) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 1) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 1) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 0) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) }) - test('should return matching parser with caching /2', t => { + await t.test('should return matching parser with caching /2', t => { t.plan(8) const fastify = Fastify() fastify.addContentTypeParser('text/html', first) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 0) - t.equal(fastify[keys.kContentTypeParser].getParser('text/HTML').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 1) - t.equal(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 2) - t.equal(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 2) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 0) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/HTML').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2) }) - test('should return matching parser with caching /3', t => { + await t.test('should return matching parser with caching /3', t => { t.plan(6) const fastify = Fastify() fastify.addContentTypeParser(/^text\/html(;\s*charset=[^;]+)?$/, first) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 1) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html;charset=utf-8').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 2) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html;charset=utf-8').fn, first) - t.equal(fastify[keys.kContentTypeParser].cache.size, 2) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html;charset=utf-8').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html;charset=utf-8').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2) }) - test('should prefer content type parser with string value', t => { + await t.test('should prefer content type parser with string value', t => { t.plan(2) const fastify = Fastify() @@ -118,11 +119,11 @@ test('getParser', t => { fastify.addContentTypeParser(/^image\/.*/, first) fastify.addContentTypeParser('image/gif', second) - t.equal(fastify[keys.kContentTypeParser].getParser('image/gif').fn, second) - t.equal(fastify[keys.kContentTypeParser].getParser('image/png').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/gif').fn, second) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/png').fn, first) }) - test('should return parser that catches all if no other is set', t => { + await t.test('should return parser that catches all if no other is set', t => { t.plan(3) const fastify = Fastify() @@ -130,12 +131,12 @@ test('getParser', t => { fastify.addContentTypeParser('*', first) fastify.addContentTypeParser(/^text\/.*/, second) - t.equal(fastify[keys.kContentTypeParser].getParser('image/gif').fn, first) - t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, second) - t.equal(fastify[keys.kContentTypeParser].getParser('text').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/gif').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, second) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text').fn, first) }) - test('should return undefined if no matching parser exist', t => { + await t.test('should return undefined if no matching parser exist', t => { t.plan(2) const fastify = Fastify() @@ -143,15 +144,13 @@ test('getParser', t => { fastify.addContentTypeParser(/^weirdType\/.+/, first) fastify.addContentTypeParser('application/javascript', first) - t.notOk(fastify[keys.kContentTypeParser].getParser('application/xml')) - t.notOk(fastify[keys.kContentTypeParser].getParser('weirdType/')) + t.assert.ok(!fastify[keys.kContentTypeParser].getParser('application/xml')) + t.assert.ok(!fastify[keys.kContentTypeParser].getParser('weirdType/')) }) - - t.end() }) -test('existingParser', t => { - test('returns always false for "*"', t => { +test('existingParser', async t => { + await t.test('returns always false for "*"', t => { t.plan(2) const fastify = Fastify() @@ -160,14 +159,14 @@ test('existingParser', t => { fastify.addContentTypeParser(/^application\/.+\+xml/, first) fastify.addContentTypeParser('text/html', first) - t.notOk(fastify[keys.kContentTypeParser].existingParser('*')) + t.assert.ok(!fastify[keys.kContentTypeParser].existingParser('*')) fastify.addContentTypeParser('*', first) - t.notOk(fastify[keys.kContentTypeParser].existingParser('*')) + t.assert.ok(!fastify[keys.kContentTypeParser].existingParser('*')) }) - test('let you override the default parser once', t => { + await t.test('let you override the default parser once', t => { t.plan(2) const fastify = Fastify() @@ -175,15 +174,13 @@ test('existingParser', t => { fastify.addContentTypeParser('application/json', first) fastify.addContentTypeParser('text/plain', first) - t.throws( + t.assert.throws( () => fastify.addContentTypeParser('application/json', first), - FST_ERR_CTP_ALREADY_PRESENT, - "Content type parser 'application/json' already present" + FST_ERR_CTP_ALREADY_PRESENT ) - t.throws( + t.assert.throws( () => fastify.addContentTypeParser('text/plain', first), - FST_ERR_CTP_ALREADY_PRESENT, - "Content type parser 'text/plain' already present" + FST_ERR_CTP_ALREADY_PRESENT ) }) @@ -194,49 +191,47 @@ test('existingParser', t => { fastify.addContentTypeParser(/^application\/.+\+xml/, first) fastify.addContentTypeParser('text/html', first) - t.ok(contentTypeParser.existingParser(/^image\/.*/)) - t.ok(contentTypeParser.existingParser('text/html')) - t.ok(contentTypeParser.existingParser(/^application\/.+\+xml/)) - t.notOk(contentTypeParser.existingParser('application/json')) - t.notOk(contentTypeParser.existingParser('text/plain')) - t.notOk(contentTypeParser.existingParser('image/png')) - t.notOk(contentTypeParser.existingParser(/^application\/.+\+json/)) - - t.end() + t.assert.ok(contentTypeParser.existingParser(/^image\/.*/)) + t.assert.ok(contentTypeParser.existingParser('text/html')) + t.assert.ok(contentTypeParser.existingParser(/^application\/.+\+xml/)) + t.assert.ok(!contentTypeParser.existingParser('application/json')) + t.assert.ok(!contentTypeParser.existingParser('text/plain')) + t.assert.ok(!contentTypeParser.existingParser('image/png')) + t.assert.ok(!contentTypeParser.existingParser(/^application\/.+\+json/)) }) -test('add', t => { - test('should only accept string and RegExp', t => { +test('add', async t => { + await t.test('should only accept string and RegExp', t => { t.plan(4) const fastify = Fastify() const contentTypeParser = fastify[keys.kContentTypeParser] - t.error(contentTypeParser.add('test', {}, first)) - t.error(contentTypeParser.add(/test/, {}, first)) - t.throws( + t.assert.ifError(contentTypeParser.add('test', {}, first)) + t.assert.ifError(contentTypeParser.add(/test/, {}, first)) + t.assert.throws( () => contentTypeParser.add({}, {}, first), FST_ERR_CTP_INVALID_TYPE, 'The content type should be a string or a RegExp' ) - t.throws( + t.assert.throws( () => contentTypeParser.add(1, {}, first), FST_ERR_CTP_INVALID_TYPE, 'The content type should be a string or a RegExp' ) }) - test('should set "*" as parser that catches all', t => { + await t.test('should set "*" as parser that catches all', t => { t.plan(1) const fastify = Fastify() const contentTypeParser = fastify[keys.kContentTypeParser] contentTypeParser.add('*', {}, first) - t.equal(contentTypeParser.customParsers.get('').fn, first) + t.assert.strictEqual(contentTypeParser.customParsers.get('').fn, first) }) - test('should lowercase contentTypeParser name', async t => { + await t.test('should lowercase contentTypeParser name', async t => { t.plan(1) const fastify = Fastify() fastify.addContentTypeParser('text/html', function (req, done) { @@ -247,11 +242,11 @@ test('add', t => { done() }) } catch (err) { - t.same(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) + t.assert.strictEqual(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) } }) - test('should trim contentTypeParser name', async t => { + await t.test('should trim contentTypeParser name', async t => { t.plan(1) const fastify = Fastify() fastify.addContentTypeParser('text/html', function (req, done) { @@ -262,14 +257,12 @@ test('add', t => { done() }) } catch (err) { - t.same(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) + t.assert.strictEqual(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message) } }) - - t.end() }) -test('non-Error thrown from content parser is properly handled', t => { +test('non-Error thrown from content parser is properly handled', (t, done) => { t.plan(3) const fastify = Fastify() @@ -285,7 +278,7 @@ test('non-Error thrown from content parser is properly handled', t => { }) fastify.setErrorHandler((err, req, res) => { - t.equal(err, throwable) + t.assert.strictEqual(err, throwable) res.send(payload) }) @@ -296,12 +289,13 @@ test('non-Error thrown from content parser is properly handled', t => { headers: { 'Content-Type': 'text/test' }, body: 'some text' }, (err, res) => { - t.error(err) - t.equal(res.payload, payload) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, payload) + done() }) }) -test('Error thrown 415 from content type is null and make post request to server', t => { +test('Error thrown 415 from content type is null and make post request to server', (t, done) => { t.plan(3) const fastify = Fastify() @@ -315,28 +309,29 @@ test('Error thrown 415 from content type is null and make post request to server url: '/', body: 'some text' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 415) - t.equal(JSON.parse(res.body).message, errMsg) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 415) + t.assert.strictEqual(JSON.parse(res.body).message, errMsg) + done() }) }) -test('remove', t => { - test('should remove default parser', t => { +test('remove', async t => { + await t.test('should remove default parser', t => { t.plan(6) const fastify = Fastify() const contentTypeParser = fastify[keys.kContentTypeParser] - t.ok(contentTypeParser.remove('application/json')) - t.notOk(contentTypeParser.customParsers['application/json']) - t.notOk(contentTypeParser.parserList.find(parser => parser === 'application/json')) - t.ok(contentTypeParser.remove(' text/plain ')) - t.notOk(contentTypeParser.customParsers['text/plain']) - t.notOk(contentTypeParser.parserList.find(parser => parser === 'text/plain')) + t.assert.ok(contentTypeParser.remove('application/json')) + t.assert.ok(!contentTypeParser.customParsers['application/json']) + t.assert.ok(!contentTypeParser.parserList.find(parser => parser === 'application/json')) + t.assert.ok(contentTypeParser.remove(' text/plain ')) + t.assert.ok(!contentTypeParser.customParsers['text/plain']) + t.assert.ok(!contentTypeParser.parserList.find(parser => parser === 'text/plain')) }) - test('should remove RegExp parser', t => { + await t.test('should remove RegExp parser', t => { t.plan(3) const fastify = Fastify() @@ -344,39 +339,37 @@ test('remove', t => { const contentTypeParser = fastify[keys.kContentTypeParser] - t.ok(contentTypeParser.remove(/^text\/*/)) - t.notOk(contentTypeParser.customParsers[/^text\/*/]) - t.notOk(contentTypeParser.parserRegExpList.find(parser => parser.toString() === /^text\/*/.toString())) + t.assert.ok(contentTypeParser.remove(/^text\/*/)) + t.assert.ok(!contentTypeParser.customParsers[/^text\/*/]) + t.assert.ok(!contentTypeParser.parserRegExpList.find(parser => parser.toString() === /^text\/*/.toString())) }) - test('should throw an error if content type is neither string nor RegExp', t => { + await t.test('should throw an error if content type is neither string nor RegExp', t => { t.plan(1) const fastify = Fastify() - t.throws(() => fastify[keys.kContentTypeParser].remove(12), FST_ERR_CTP_INVALID_TYPE) + t.assert.throws(() => fastify[keys.kContentTypeParser].remove(12), FST_ERR_CTP_INVALID_TYPE) }) - test('should return false if content type does not exist', t => { + await t.test('should return false if content type does not exist', t => { t.plan(1) const fastify = Fastify() - t.notOk(fastify[keys.kContentTypeParser].remove('image/png')) + t.assert.ok(!fastify[keys.kContentTypeParser].remove('image/png')) }) - test('should not remove any content type parser if content type does not exist', t => { + await t.test('should not remove any content type parser if content type does not exist', t => { t.plan(2) const fastify = Fastify() const contentTypeParser = fastify[keys.kContentTypeParser] - t.notOk(contentTypeParser.remove('image/png')) - t.same(contentTypeParser.customParsers.size, 2) + t.assert.ok(!contentTypeParser.remove('image/png')) + t.assert.strictEqual(contentTypeParser.customParsers.size, 2) }) - - t.end() }) test('remove all should remove all existing parsers and reset cache', t => { @@ -391,10 +384,10 @@ test('remove all should remove all existing parsers and reset cache', t => { contentTypeParser.getParser('application/xml') // fill cache with one entry contentTypeParser.removeAll() - t.same(contentTypeParser.cache.size, 0) - t.same(contentTypeParser.parserList.length, 0) - t.same(contentTypeParser.parserRegExpList.length, 0) - t.same(Object.keys(contentTypeParser.customParsers).length, 0) + t.assert.strictEqual(contentTypeParser.cache.size, 0) + t.assert.strictEqual(contentTypeParser.parserList.length, 0) + t.assert.strictEqual(contentTypeParser.parserRegExpList.length, 0) + t.assert.strictEqual(Object.keys(contentTypeParser.customParsers).length, 0) }) test('Safeguard against malicious content-type / 1', async t => { @@ -417,7 +410,7 @@ test('Safeguard against malicious content-type / 1', async t => { body: '' }) - t.same(response.statusCode, 415) + t.assert.strictEqual(response.statusCode, 415) } }) @@ -439,7 +432,7 @@ test('Safeguard against malicious content-type / 2', async t => { body: '' }) - t.same(response.statusCode, 415) + t.assert.strictEqual(response.statusCode, 415) }) test('Safeguard against malicious content-type / 3', async t => { @@ -460,7 +453,7 @@ test('Safeguard against malicious content-type / 3', async t => { body: '' }) - t.same(response.statusCode, 415) + t.assert.strictEqual(response.statusCode, 415) }) test('Safeguard against content-type spoofing - string', async t => { @@ -469,11 +462,11 @@ test('Safeguard against content-type spoofing - string', async t => { const fastify = Fastify() fastify.removeAllContentTypeParsers() fastify.addContentTypeParser('text/plain', function (request, body, done) { - t.pass('should be called') + t.assert.ok('should be called') done(null, body) }) fastify.addContentTypeParser('application/json', function (request, body, done) { - t.fail('shouldn\'t be called') + t.assert.fail('shouldn\'t be called') done(null, body) }) @@ -491,24 +484,42 @@ test('Safeguard against content-type spoofing - string', async t => { }) }) -test('Warning against improper content-type - regexp', t => { - t.plan(2) - - const fastify = Fastify() +test('Warning against improper content-type - regexp', async t => { + await t.test('improper regex - text plain', (t, done) => { + t.plan(2) + const fastify = Fastify() - process.on('warning', onWarning) - function onWarning (warning) { - t.equal(warning.name, 'FastifySecurity') - t.equal(warning.code, 'FSTSEC001') - } - t.teardown(() => process.removeListener('warning', onWarning)) + process.on('warning', onWarning) + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifySecurity') + t.assert.strictEqual(warning.code, 'FSTSEC001') + done() + } + t.after(() => process.removeListener('warning', onWarning)) - fastify.removeAllContentTypeParsers() - fastify.addContentTypeParser(/text\/plain/, function (request, body, done) { - done(null, body) + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser(/text\/plain/, function (request, body, done) { + done(null, body) + }) }) - fastify.addContentTypeParser(/application\/json/, function (request, body, done) { - done(null, body) + + await t.test('improper regex - application json', (t, done) => { + t.plan(2) + const fastify = Fastify() + + process.on('warning', onWarning) + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifySecurity') + t.assert.strictEqual(warning.code, 'FSTSEC001') + done() + } + t.after(() => process.removeListener('warning', onWarning)) + + fastify.removeAllContentTypeParsers() + + fastify.addContentTypeParser(/application\/json/, function (request, body, done) { + done(null, body) + }) }) }) @@ -518,11 +529,11 @@ test('content-type match parameters - string 1', async t => { const fastify = Fastify() fastify.removeAllContentTypeParsers() fastify.addContentTypeParser('text/plain; charset=utf8', function (request, body, done) { - t.fail('shouldn\'t be called') + t.assert.fail('shouldn\'t be called') done(null, body) }) fastify.addContentTypeParser('application/json; charset=utf8', function (request, body, done) { - t.pass('should be called') + t.assert.ok('should be called') done(null, body) }) @@ -546,7 +557,7 @@ test('content-type match parameters - regexp', async t => { const fastify = Fastify() fastify.removeAllContentTypeParsers() fastify.addContentTypeParser(/application\/json; charset=utf8/, function (request, body, done) { - t.pass('should be called') + t.assert.ok('should be called') done(null, body) }) @@ -570,7 +581,7 @@ test('content-type fail when parameters not match - string 1', async t => { const fastify = Fastify() fastify.removeAllContentTypeParsers() fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { - t.fail('shouldn\'t be called') + t.assert.fail('shouldn\'t be called') done(null, body) }) @@ -587,7 +598,7 @@ test('content-type fail when parameters not match - string 1', async t => { body: '' }) - t.same(response.statusCode, 415) + t.assert.strictEqual(response.statusCode, 415) }) test('content-type fail when parameters not match - string 2', async t => { @@ -596,7 +607,7 @@ test('content-type fail when parameters not match - string 2', async t => { const fastify = Fastify() fastify.removeAllContentTypeParsers() fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { - t.fail('shouldn\'t be called') + t.assert.fail('shouldn\'t be called') done(null, body) }) @@ -613,7 +624,7 @@ test('content-type fail when parameters not match - string 2', async t => { body: '' }) - t.same(response.statusCode, 415) + t.assert.strictEqual(response.statusCode, 415) }) test('content-type fail when parameters not match - regexp', async t => { @@ -622,7 +633,7 @@ test('content-type fail when parameters not match - regexp', async t => { const fastify = Fastify() fastify.removeAllContentTypeParsers() fastify.addContentTypeParser(/application\/json; charset=utf8; foo=bar/, function (request, body, done) { - t.fail('shouldn\'t be called') + t.assert.fail('shouldn\'t be called') done(null, body) }) @@ -639,7 +650,7 @@ test('content-type fail when parameters not match - regexp', async t => { body: '' }) - t.same(response.statusCode, 415) + t.assert.strictEqual(response.statusCode, 415) }) // Refs: https://github.com/fastify/fastify/issues/4495 @@ -667,9 +678,9 @@ test('content-type regexp list should be cloned when plugin override', async t = payload: 'jpeg', headers: { 'content-type': 'image/jpeg' } }) - t.same(statusCode, 200) - t.same(headers['content-type'], 'image/jpeg') - t.same(payload, 'jpeg') + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(headers['content-type'], 'image/jpeg') + t.assert.strictEqual(payload, 'jpeg') } { @@ -679,9 +690,9 @@ test('content-type regexp list should be cloned when plugin override', async t = payload: 'png', headers: { 'content-type': 'image/png' } }) - t.same(statusCode, 200) - t.same(headers['content-type'], 'image/png') - t.same(payload, 'png') + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(headers['content-type'], 'image/png') + t.assert.strictEqual(payload, 'png') } }) @@ -691,7 +702,7 @@ test('edge case content-type - ;', async t => { const fastify = Fastify() fastify.removeAllContentTypeParsers() fastify.addContentTypeParser(';', function (request, body, done) { - t.fail('should not be called') + t.assert.fail('should not be called') done(null, body) }) @@ -717,5 +728,5 @@ test('edge case content-type - ;', async t => { body: '' }) - t.pass('end') + t.assert.ok('end') }) From 4c443fdc49441a775032d0e90cbe7e40378bf40f Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 5 Dec 2024 21:55:06 +0000 Subject: [PATCH 0861/1295] docs(readme): revert from deprecated workflow badge syntax (#5877) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4a550c1b7d3..2b22c2ccec6 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@
-![CI](https://github.com/fastify/fastify/actions/workflows/ci.yml/badge.svg?branch=main) -![Package Manager -CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main) +[![CI](https://github.com/fastify/fastify/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/ci.yml) +[![Package Manager +CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml) [![Web SIte](https://github.com/fastify/fastify/workflows/website/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml) [![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) From 95fe302fc0fbe95faa50e96e12bb278b11026457 Mon Sep 17 00:00:00 2001 From: Giuliano Kranevitter Date: Sat, 7 Dec 2024 05:47:58 -0500 Subject: [PATCH 0862/1295] chore: readme lighter install script (#5872) --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 2b22c2ccec6..592002252bf 100644 --- a/README.md +++ b/README.md @@ -106,14 +106,9 @@ generate functionality of [Fastify CLI](https://github.com/fastify/fastify-cli). To install Fastify in an existing project as a dependency: -Install with npm: ```sh npm i fastify ``` -Install with yarn: -```sh -yarn add fastify -``` ### Example From 98af7898dd60bcae4c4a9bbda422fdb66e56dbfc Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Tue, 10 Dec 2024 12:42:46 +0100 Subject: [PATCH 0863/1295] docs(CONTRIBUTING.md): added v4 to version branches (#5886) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be2de589e7a..2b28f5bde22 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,7 @@ the organization efforts for each Fastify's version. **v1.x**|[branch 1.x](https://github.com/fastify/fastify/tree/1.x)| **v2.x**|[branch 2.x](https://github.com/fastify/fastify/tree/2.x)| **v3.x**|[branch 3.x](https://github.com/fastify/fastify/tree/3.x)| +**v4.x**|[branch 4.x](https://github.com/fastify/fastify/tree/4.x)| ## Releases From 0fe688e4c9e8c2d78c8e6d8178b65a8304483bbf Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Wed, 11 Dec 2024 23:53:21 +0100 Subject: [PATCH 0864/1295] fix(errorHandler.js): linting problems in pipeline (#5885) --- lib/error-handler.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/error-handler.js b/lib/error-handler.js index cde82f8baef..f53cfa557b5 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -112,16 +112,16 @@ function fallbackErrorHandler (error, reply, cb) { const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type']) payload = (serializerFn === false) ? serializeError({ - error: statusCodes[statusCode + ''], - code: error.code, - message: error.message, - statusCode - }) + error: statusCodes[statusCode + ''], + code: error.code, + message: error.message, + statusCode + }) : serializerFn(Object.create(error, { - error: { value: statusCodes[statusCode + ''] }, - message: { value: error.message }, - statusCode: { value: statusCode } - })) + error: { value: statusCodes[statusCode + ''] }, + message: { value: error.message }, + statusCode: { value: statusCode } + })) } catch (err) { if (!reply.log[kDisableRequestLogging]) { // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization From 3ffba7efb5e1a90bb4c86bbcae4b3b506d8b1270 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:36:09 +0800 Subject: [PATCH 0865/1295] ci: pin node version 22.11 (#5889) --- .github/workflows/ci.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99602d1bfac..bd18425aa84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,7 +113,7 @@ jobs: strategy: matrix: - node-version: [20, 22] + node-version: [20, 22.11] os: [macos-latest, ubuntu-latest, windows-latest] steps: diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index a963067baab..d7c836b28d5 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 'lts/*' + node-version: 22.11 cache: 'npm' cache-dependency-path: package.json check-latest: true From 15819067756b4ca46dd68b359b5e6d986d1881d1 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Fri, 13 Dec 2024 18:34:16 +1000 Subject: [PATCH 0866/1295] docs(ecosystem): update apitally description (#5891) --- docs/Guides/Ecosystem.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index e78b690bed4..043c1a85f3d 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -218,9 +218,9 @@ section. Beautiful OpenAPI/Swagger API references for Fastify - [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs) SeaweedFS for Fastify -- [`apitally`](https://github.com/apitally/nodejs-client) Fastify plugin to - integrate with [Apitally](https://apitally.io), a simple API monitoring & - API key management solution. +- [`apitally`](https://github.com/apitally/apitally-js) Fastify plugin to + integrate with [Apitally](https://apitally.io/fastify), an API analytics, + logging and monitoring tool. - [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for Kubernetes Liveness and Readiness Probes. - [`aws-xray-sdk-fastify`](https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/fastify) From f8c1d70d33eeb5838f86b1389977eef049a3bf1a Mon Sep 17 00:00:00 2001 From: Giulio Davide Carparelli Date: Fri, 13 Dec 2024 09:50:06 +0100 Subject: [PATCH 0867/1295] types: remove connection property in FastifyRequest (#5884) Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --- types/request.d.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/types/request.d.ts b/types/request.d.ts index fcfdcd7a0b4..c63e386ab2c 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -87,8 +87,4 @@ export interface FastifyRequest Date: Fri, 13 Dec 2024 17:02:06 +0800 Subject: [PATCH 0868/1295] fix: crash when host header is missing by various of reason (#5892) --- docs/Reference/Request.md | 14 ++- lib/request.js | 21 ++--- test/request-header-host.test.js | 142 +++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 14 deletions(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index a1fdcf149fc..156d3d80c0f 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -23,10 +23,13 @@ Request is a core Fastify object containing the following fields: - `host` - the host of the incoming request (derived from `X-Forwarded-Host` header when the [`trustProxy`](./Server.md#factory-trust-proxy) option is enabled). For HTTP/2 compatibility it returns `:authority` if no host header - exists. When you use `requireHostHeader = false` in the server options, it - will fallback as empty when the host header is missing. -- `hostname` - the host of the incoming request without the port -- `port` - the port that the server is listening on + exists. The host header may return empty string when using + `requireHostHeader = false`, not suppied when connected with `HTTP/1.0` or + using schema validation that remove the extra headers. +- `hostname` - the host of the `host` property, it may refers the incoming + request hostname +- `port` - the port of the `host` property, it may refers the port thats + the server is listening on - `protocol` - the protocol of the incoming request (`https` or `http`) - `method` - the method of the incoming request - `url` - the URL of the incoming request @@ -85,6 +88,9 @@ request's headers with the `request.raw.headers` property. > Note: For performance reason on `not found` route, you may see that we will add an extra property `Symbol('fastify.RequestAcceptVersion')` on the headers. +> Note: When using schema, it may mutate the `request.headers` and +`request.raw.headers` object. So, you may found the headers becomes empty. + ```js fastify.post('/:params', options, function (request, reply) { console.log(request.body) diff --git a/lib/request.js b/lib/request.js index 289f736afe5..9c6525368a2 100644 --- a/lib/request.js +++ b/lib/request.js @@ -117,10 +117,11 @@ function buildRequestWithTrustProxy (R, trustProxy) { if (this.ip !== undefined && this.headers['x-forwarded-host']) { return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host']) } - let host = this.headers.host ?? this.headers[':authority'] - // support http.requireHostHeader === false - if (this.server.server.requireHostHeader === false) host ??= '' - return host + // the last fallback is used to support the following cases: + // 1. support http.requireHostHeader === false + // 2. support HTTP/1.0 without Host Header + // 3. support headers schema which may remove the Host Header + return this.headers.host ?? this.headers[':authority'] ?? '' } }, protocol: { @@ -212,10 +213,11 @@ Object.defineProperties(Request.prototype, { }, host: { get () { - let host = this.raw.headers.host ?? this.raw.headers[':authority'] - // support http.requireHostHeader === false - if (this.server.server.requireHostHeader === false) host ??= '' - return host + // the last fallback is used to support the following cases: + // 1. support http.requireHostHeader === false + // 2. support HTTP/1.0 without Host Header + // 3. support headers schema which may remove the Host Header + return this.raw.headers.host ?? this.raw.headers[':authority'] ?? '' } }, hostname: { @@ -231,8 +233,7 @@ Object.defineProperties(Request.prototype, { return portFromHost } // now fall back to port from host/:authority header - let host = (this.headers.host ?? this.headers[':authority']) - if (this.server.server.requireHostHeader === false) host ??= '' + const host = (this.headers.host ?? this.headers[':authority'] ?? '') const portFromHeader = parseInt(host.split(':').slice(-1)[0]) if (!isNaN(portFromHeader)) { return portFromHeader diff --git a/test/request-header-host.test.js b/test/request-header-host.test.js index b6f259d5487..26c77584ac8 100644 --- a/test/request-header-host.test.js +++ b/test/request-header-host.test.js @@ -195,3 +195,145 @@ test('Return 200 when Host header is missing and http.requireHostHeader = false }) }) }) + +test('Return 200 when Host header is missing using HTTP/1.0', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.0\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is missing with trust proxy using HTTP/1.0', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + trustProxy: true, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.0\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is removed by schema', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', { + schema: { + headers: { + type: 'object', + properties: {}, + additionalProperties: false + } + } + }, async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is removed by schema with trust proxy', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + trustProxy: true, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', { + schema: { + headers: { + type: 'object', + properties: {}, + additionalProperties: false + } + } + }, async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) From c751d33b781456e4059c242aac9fe9eb8ebc8130 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 13 Dec 2024 10:02:36 +0100 Subject: [PATCH 0869/1295] chore: fix flaky test (#5881) * chore: fix flaky test * chore: win is hard * chore: win is hard * chore: flaky * typo * ci: no more artifacts to upload * cleaning * Update close-pipelining.test.js Signed-off-by: Manuel Spigolon --------- Signed-off-by: Manuel Spigolon --- .github/workflows/coverage-nix.yml | 12 ---------- .github/workflows/coverage-win.yml | 12 ---------- package.json | 1 - test/close-pipelining.test.js | 1 - test/hooks.test.js | 38 +++++++++++++++--------------- test/post-empty-body.test.js | 10 ++++---- test/trust-proxy.test.js | 19 +++++++++++---- 7 files changed, 39 insertions(+), 54 deletions(-) diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 3d19bde7902..000306ca515 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -24,18 +24,6 @@ jobs: run: | npm install --ignore-scripts - # We do not check coverage requirements here because the goal is to - # generate a report to upload as an artifact. - - name: Generate coverage report - run: | - npm run coverage:ci - - - uses: actions/upload-artifact@v4 - if: ${{ success() }} - with: - name: coverage-report-nix - path: ./.tap/report/ - # Here, we verify the coverage thresholds so that this workflow can pass # or fail and stop further workflows if this one fails. - name: Verify coverage meets thresholds diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index d7c836b28d5..9af08e4ea85 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -24,18 +24,6 @@ jobs: run: | npm install --ignore-scripts - # We do not check coverage requirements here because the goal is to - # generate a report to upload as an artifact. - - name: Generate coverage report - run: | - npm run coverage:ci - - - uses: actions/upload-artifact@v4 - if: ${{ success() }} - with: - name: coverage-report-win - path: ./.tap/report/ - # Here, we verify the coverage thresholds so that this workflow can pass # or fail and stop further workflows if this one fails. - name: Verify coverage meets thresholds diff --git a/package.json b/package.json index 7148eb9e4de..3beb9c8812b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "coverage": "c8 --reporter html borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --lines 100 ", - "coverage:ci": "c8 --reporter lcov --reporter html borp --reporter=./test/test-reporter.mjs", "coverage:ci-check-coverage": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --lines 100", "lint": "npm run lint:eslint", "lint:fix": "eslint --fix", diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index c347318dfa2..bbadbb02de7 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -28,7 +28,6 @@ test('Should return 503 while closing - pipelining', async t => { instance.request({ path: '/', method: 'GET' }) ]) const actual = responses.map(r => r.statusCode) - t.assert.deepStrictEqual(actual, codes) await instance.close() diff --git a/test/hooks.test.js b/test/hooks.test.js index 2e92b753f3a..c753cff4160 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -212,7 +212,7 @@ test('onRequest hook should support encapsulation / 1', t => { }) }) -test('onRequest hook should support encapsulation / 2', t => { +test('onRequest hook should support encapsulation / 2', (t) => { t.plan(3) const fastify = Fastify() let pluginInstance @@ -300,6 +300,7 @@ test('onRequest hook should support encapsulation / 3', t => { test('preHandler hook should support encapsulation / 5', t => { t.plan(17) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('preHandler', function (req, res, done) { @@ -334,7 +335,6 @@ test('preHandler hook should support encapsulation / 5', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -729,6 +729,7 @@ test('onRoute hook should able to change the route url', t => { t.plan(5) const fastify = Fastify({ exposeHeadRoutes: false }) + t.teardown(() => { fastify.close() }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', (route) => { @@ -745,7 +746,6 @@ test('onRoute hook should able to change the route url', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -921,6 +921,7 @@ test('onResponse hook should support encapsulation / 2', t => { test('onResponse hook should support encapsulation / 3', t => { t.plan(16) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('onResponse', function (request, reply, done) { @@ -951,7 +952,6 @@ test('onResponse hook should support encapsulation / 3', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -998,6 +998,7 @@ test('onSend hook should support encapsulation / 1', t => { test('onSend hook should support encapsulation / 2', t => { t.plan(16) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('onSend', function (request, reply, thePayload, done) { @@ -1028,7 +1029,6 @@ test('onSend hook should support encapsulation / 2', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -1272,6 +1272,7 @@ test('onSend hook throws', t => { } }) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('onSend', function (request, reply, payload, done) { if (request.raw.method === 'DELETE') { done(new Error('some error')) @@ -1323,7 +1324,6 @@ test('onSend hook throws', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -1446,6 +1446,7 @@ test('Content-Length header should be updated if onSend hook modifies the payloa test('cannot add hook after binding', t => { t.plan(2) const instance = Fastify() + t.teardown(() => instance.close()) instance.get('/', function (request, reply) { reply.send({ hello: 'world' }) @@ -1453,7 +1454,6 @@ test('cannot add hook after binding', t => { instance.listen({ port: 0 }, err => { t.error(err) - t.teardown(instance.server.close.bind(instance.server)) try { instance.addHook('onRequest', () => {}) @@ -2423,6 +2423,7 @@ test('preValidation hook should support encapsulation / 2', t => { test('preValidation hook should support encapsulation / 3', t => { t.plan(20) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('preValidation', function (req, reply, done) { @@ -2461,7 +2462,6 @@ test('preValidation hook should support encapsulation / 3', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -2589,6 +2589,7 @@ test('onError hook with setErrorHandler', t => { test('preParsing hook should run before parsing and be able to modify the payload', t => { t.plan(5) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('preParsing', function (req, reply, payload, done) { const modified = new stream.Readable() @@ -2608,7 +2609,6 @@ test('preParsing hook should run before parsing and be able to modify the payloa fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'POST', @@ -2627,6 +2627,7 @@ test('preParsing hook should run before parsing and be able to modify the payloa test('preParsing hooks should run in the order in which they are defined', t => { t.plan(5) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('preParsing', function (req, reply, payload, done) { const modified = new stream.Readable() @@ -2651,7 +2652,6 @@ test('preParsing hooks should run in the order in which they are defined', t => fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'POST', @@ -2670,6 +2670,7 @@ test('preParsing hooks should run in the order in which they are defined', t => test('preParsing hooks should support encapsulation', t => { t.plan(9) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('preParsing', function (req, reply, payload, done) { const modified = new stream.Readable() @@ -2701,7 +2702,6 @@ test('preParsing hooks should support encapsulation', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'POST', @@ -2784,6 +2784,7 @@ test('preParsing hook should support encapsulation / 2', t => { test('preParsing hook should support encapsulation / 3', t => { t.plan(20) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('preParsing', function (req, reply, payload, done) { @@ -2822,7 +2823,6 @@ test('preParsing hook should support encapsulation / 3', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -2849,6 +2849,7 @@ test('preParsing hook should support encapsulation / 3', t => { test('preSerialization hook should run before serialization and be able to modify the payload', t => { t.plan(5) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { payload.hello += '1' @@ -2884,7 +2885,6 @@ test('preSerialization hook should run before serialization and be able to modif fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -2900,6 +2900,7 @@ test('preSerialization hook should run before serialization and be able to modif test('preSerialization hook should be able to throw errors which are validated against schema response', t => { const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { done(new Error('preSerialization aborted')) @@ -2935,7 +2936,6 @@ test('preSerialization hook should be able to throw errors which are validated a fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -2953,6 +2953,7 @@ test('preSerialization hook should be able to throw errors which are validated a test('preSerialization hook which returned error should still run onError hooks', t => { t.plan(4) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { done(new Error('preSerialization aborted')) @@ -2969,7 +2970,6 @@ test('preSerialization hook which returned error should still run onError hooks' fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -2984,6 +2984,7 @@ test('preSerialization hook which returned error should still run onError hooks' test('preSerialization hooks should run in the order in which they are defined', t => { t.plan(5) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { payload.hello += '2' @@ -3003,7 +3004,6 @@ test('preSerialization hooks should run in the order in which they are defined', fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -3020,6 +3020,7 @@ test('preSerialization hooks should run in the order in which they are defined', test('preSerialization hooks should support encapsulation', t => { t.plan(9) const fastify = Fastify() + t.teardown(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { payload.hello += '1' @@ -3047,7 +3048,6 @@ test('preSerialization hooks should support encapsulation', t => { fastify.listen({ port: 0 }, err => { t.error(err) - t.teardown(() => { fastify.close() }) sget({ method: 'GET', @@ -3273,6 +3273,7 @@ test('reply.send should throw if undefined error is thrown at onSend hook', t => test('onTimeout should be triggered', t => { t.plan(6) const fastify = Fastify({ connectionTimeout: 500 }) + t.teardown(() => { fastify.close() }) fastify.addHook('onTimeout', function (req, res, done) { t.ok('called', 'onTimeout') @@ -3289,7 +3290,6 @@ test('onTimeout should be triggered', t => { fastify.listen({ port: 0 }, (err, address) => { t.error(err) - t.teardown(() => fastify.close()) sget({ method: 'GET', @@ -3311,6 +3311,7 @@ test('onTimeout should be triggered', t => { test('onTimeout should be triggered and socket _meta is set', t => { t.plan(6) const fastify = Fastify({ connectionTimeout: 500 }) + t.teardown(() => { fastify.close() }) fastify.addHook('onTimeout', function (req, res, done) { t.ok('called', 'onTimeout') @@ -3328,7 +3329,6 @@ test('onTimeout should be triggered and socket _meta is set', t => { fastify.listen({ port: 0 }, (err, address) => { t.error(err) - t.teardown(() => fastify.close()) sget({ method: 'GET', diff --git a/test/post-empty-body.test.js b/test/post-empty-body.test.js index fdd23141bb8..8389e39da84 100644 --- a/test/post-empty-body.test.js +++ b/test/post-empty-body.test.js @@ -9,8 +9,8 @@ setGlobalDispatcher(new Agent({ keepAliveMaxTimeout: 10 })) -test('post empty body', async t => { - const fastify = Fastify() +test('post empty body', { timeout: 3_000 }, async t => { + const fastify = Fastify({ forceCloseConnections: true }) const abortController = new AbortController() const { signal } = abortController t.after(() => { @@ -18,11 +18,13 @@ test('post empty body', async t => { abortController.abort() }) - fastify.post('/bug', async (request, reply) => {}) + fastify.post('/bug', async () => { + // This function must be async and return nothing + }) await fastify.listen({ port: 0 }) - const res = await request(`http://127.0.0.1:${fastify.server.address().port}/bug`, { + const res = await request(`http://localhost:${fastify.server.address().port}/bug`, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/test/trust-proxy.test.js b/test/trust-proxy.test.js index cf70de3d116..7d108596065 100644 --- a/test/trust-proxy.test.js +++ b/test/trust-proxy.test.js @@ -7,7 +7,7 @@ const helper = require('./helper') const noop = () => {} -const sgetForwardedRequest = (app, forHeader, path, protoHeader, done) => { +const sgetForwardedRequest = (app, forHeader, path, protoHeader, testCaseDone) => { const headers = { 'X-Forwarded-For': forHeader, 'X-Forwarded-Host': 'example.com' @@ -19,7 +19,7 @@ const sgetForwardedRequest = (app, forHeader, path, protoHeader, done) => { method: 'GET', headers, url: 'http://localhost:' + app.server.address().port + path - }, done || noop) + }, testCaseDone || noop) } const testRequestValues = (t, req, options) => { @@ -56,6 +56,8 @@ test('trust proxy, not add properties to node req', (t, done) => { const app = fastify({ trustProxy: true }) + t.after(() => app.close()) + app.get('/trustproxy', function (req, reply) { testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) @@ -69,9 +71,16 @@ test('trust proxy, not add properties to node req', (t, done) => { app.listen({ port: 0 }, (err) => { app.server.unref() t.assert.ifError(err) - t.after(() => app.close()) - sgetForwardedRequest(app, '1.1.1.1', '/trustproxy') - sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined, done) + + sgetForwardedRequest(app, '1.1.1.1', '/trustproxy', undefined, completed) + sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined, completed) + + let pending = 2 + function completed () { + if (--pending === 0) { + done() + } + } }) }) From 54096663654b30403ab5d16436a349e2dc3c997b Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Fri, 13 Dec 2024 13:19:48 +0000 Subject: [PATCH 0870/1295] perf: use `node:` prefix to bypass require.cache call for builtins (#5894) --- test/build-certificate.js | 2 +- test/reply-code.test.js | 2 +- test/reply-early-hints.test.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/build-certificate.js b/test/build-certificate.js index 114cd254e84..c4dc54a9171 100644 --- a/test/build-certificate.js +++ b/test/build-certificate.js @@ -1,6 +1,6 @@ 'use strict' -const os = require('os') +const os = require('node:os') const forge = require('node-forge') // from self-cert module diff --git a/test/reply-code.test.js b/test/reply-code.test.js index b12e42d64b9..700f6f41f0c 100644 --- a/test/reply-code.test.js +++ b/test/reply-code.test.js @@ -1,7 +1,7 @@ 'use strict' const { test } = require('node:test') -const { Readable } = require('stream') +const { Readable } = require('node:stream') const Fastify = require('..') test('code should handle null/undefined/float', (t, done) => { diff --git a/test/reply-early-hints.test.js b/test/reply-early-hints.test.js index f5c54419bf7..a6042e5a36b 100644 --- a/test/reply-early-hints.test.js +++ b/test/reply-early-hints.test.js @@ -2,8 +2,8 @@ const Fastify = require('..') const { test } = require('node:test') -const http = require('http') -const http2 = require('http2') +const http = require('node:http') +const http2 = require('node:http2') const testResBody = 'Hello, world!' From 8e27e22d7cbef2c648c5d80c71b6f4ad950a808d Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 13 Dec 2024 17:56:32 +0100 Subject: [PATCH 0871/1295] chore: bump dev fastify-plugin (#5879) Signed-off-by: Manuel Spigolon --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3beb9c8812b..636256656a4 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,7 @@ "cross-env": "^7.0.3", "eslint": "^9.0.0", "fast-json-body": "^1.1.0", - "fastify-plugin": "^4.5.1", + "fastify-plugin": "^5.0.0", "fluent-json-schema": "^5.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", From b5954c16eca2d68b1ab8e8915feb98cec311a921 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 14 Dec 2024 06:42:21 +0000 Subject: [PATCH 0872/1295] types: use `node:` prefix for builtins (#5896) --- docs/Reference/TypeScript.md | 14 +++++++------- examples/typescript-server.ts | 2 +- fastify.d.ts | 8 ++++---- test/types/content-type-parser.test-d.ts | 2 +- test/types/fastify.test-d.ts | 8 ++++---- test/types/instance.test-d.ts | 2 +- test/types/logger.test-d.ts | 4 ++-- test/types/plugin.test-d.ts | 4 ++-- test/types/register.test-d.ts | 4 ++-- test/types/reply.test-d.ts | 2 +- test/types/route.test-d.ts | 2 +- test/types/serverFactory.test-d.ts | 2 +- test/types/type-provider.test-d.ts | 2 +- types/hooks.d.ts | 2 +- types/instance.d.ts | 4 ++-- types/reply.d.ts | 2 +- types/serverFactory.d.ts | 6 +++--- types/utils.d.ts | 6 +++--- 18 files changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 7176192bc6d..7b59ee594f2 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -873,8 +873,8 @@ a more detailed http server walkthrough. 1. Create the following imports from `@types/node` and `fastify` ```typescript - import fs from 'fs' - import path from 'path' + import fs from 'node:fs' + import path from 'node:path' import fastify from 'fastify' ``` 2. Perform the following steps before setting up a Fastify HTTPS server @@ -935,7 +935,7 @@ specified at server instantiation, the custom type becomes available on all further instances of the custom type. ```typescript import fastify from 'fastify' -import http from 'http' +import http from 'node:http' interface customRequest extends http.IncomingMessage { mySpecialProp: string @@ -1123,8 +1123,8 @@ returns `http.IncomingMessage`, otherwise, it returns `http2.Http2ServerRequest`. ```typescript -import http from 'http' -import http2 from 'http2' +import http from 'node:http' +import http2 from 'node:http2' import { RawRequestDefaultExpression } from 'fastify' RawRequestDefaultExpression // -> http.IncomingMessage @@ -1183,8 +1183,8 @@ returns `http.ServerResponse`, otherwise, it returns `http2.Http2ServerResponse`. ```typescript -import http from 'http' -import http2 from 'http2' +import http from 'node:http' +import http2 from 'node:http2' import { RawReplyDefaultExpression } from 'fastify' RawReplyDefaultExpression // -> http.ServerResponse diff --git a/examples/typescript-server.ts b/examples/typescript-server.ts index 8e976ea6bd4..d1e24247d31 100644 --- a/examples/typescript-server.ts +++ b/examples/typescript-server.ts @@ -11,7 +11,7 @@ */ import fastify, { FastifyInstance, RouteShorthandOptions } from '../fastify' -import { Server, IncomingMessage, ServerResponse } from 'http' +import { Server, IncomingMessage, ServerResponse } from 'node:http' // Create an http server. We pass the relevant typings for our http version used. // By passing types we get correctly typed access to the underlying http objects in routes. diff --git a/fastify.d.ts b/fastify.d.ts index 6c46fd9144b..2f8e2111fea 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -1,7 +1,7 @@ -import * as http from 'http' -import * as http2 from 'http2' -import * as https from 'https' -import { Socket } from 'net' +import * as http from 'node:http' +import * as http2 from 'node:http2' +import * as https from 'node:https' +import { Socket } from 'node:net' import { Options as AjvOptions, ValidatorFactory } from '@fastify/ajv-compiler' import { FastifyError } from '@fastify/error' diff --git a/test/types/content-type-parser.test-d.ts b/test/types/content-type-parser.test-d.ts index a3f388bc2b2..f75fc01ae79 100644 --- a/test/types/content-type-parser.test-d.ts +++ b/test/types/content-type-parser.test-d.ts @@ -1,6 +1,6 @@ import fastify, { FastifyBodyParser } from '../../fastify' import { expectError, expectType } from 'tsd' -import { IncomingMessage } from 'http' +import { IncomingMessage } from 'node:http' import { FastifyRequest } from '../../types/request' expectType(fastify().addContentTypeParser('contentType', function (request, payload, done) { diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 4dc7ac74fac..d482acaf5ee 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -1,8 +1,8 @@ import { ErrorObject as AjvErrorObject } from 'ajv' -import * as http from 'http' -import * as http2 from 'http2' -import * as https from 'https' -import { Socket } from 'net' +import * as http from 'node:http' +import * as http2 from 'node:http2' +import * as https from 'node:https' +import { Socket } from 'node:net' import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd' import fastify, { ConnectionError, diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index c797ba21f05..7040d20b170 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -13,7 +13,7 @@ import { HookHandlerDoneFunction } from '../../types/hooks' import { FastifyReply } from '../../types/reply' import { FastifyRequest } from '../../types/request' import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema' -import { AddressInfo } from 'net' +import { AddressInfo } from 'node:net' import { Bindings, ChildLoggerOptions } from '../../types/logger' const server = fastify() diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index 9165698611f..a5e2203b4d0 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -7,8 +7,8 @@ import fastify, { FastifyReply, FastifyBaseLogger } from '../../fastify' -import { Server, IncomingMessage, ServerResponse } from 'http' -import * as fs from 'fs' +import { Server, IncomingMessage, ServerResponse } from 'node:http' +import * as fs from 'node:fs' import P from 'pino' import { ResSerializerReply } from '../../types/logger' diff --git a/test/types/plugin.test-d.ts b/test/types/plugin.test-d.ts index e53c944f4fd..b1680151ef4 100644 --- a/test/types/plugin.test-d.ts +++ b/test/types/plugin.test-d.ts @@ -1,6 +1,6 @@ import fastify, { FastifyInstance, FastifyPluginOptions, SafePromiseLike } from '../../fastify' -import * as http from 'http' -import * as https from 'https' +import * as http from 'node:http' +import * as https from 'node:https' import { expectType, expectError, expectAssignable } from 'tsd' import { FastifyPluginCallback, FastifyPluginAsync } from '../../types/plugin' import { FastifyError } from '@fastify/error' diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index ab115fae314..82f13fa1f1a 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -1,6 +1,6 @@ import { expectAssignable, expectError, expectType } from 'tsd' -import { IncomingMessage, Server, ServerResponse } from 'http' -import { Http2Server, Http2ServerRequest, Http2ServerResponse } from 'http2' +import { IncomingMessage, Server, ServerResponse } from 'node:http' +import { Http2Server, Http2ServerRequest, Http2ServerResponse } from 'node:http2' import fastify, { FastifyInstance, FastifyError, FastifyLoggerInstance, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions, RawServerDefault } from '../../fastify' const testPluginCallback: FastifyPluginCallback = function (instance, opts, done) { } diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 9a96d1bd10e..3cc88ed1cbb 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -1,4 +1,4 @@ -import { Buffer } from 'buffer' +import { Buffer } from 'node:buffer' import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { FastifyContextConfig, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' import { FastifyInstance } from '../../types/instance' diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 244b4935a1d..bc9f58d31e6 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -1,5 +1,5 @@ import { FastifyError } from '@fastify/error' -import * as http from 'http' +import * as http from 'node:http' import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from '../../fastify' import { RequestPayload } from '../../types/hooks' diff --git a/test/types/serverFactory.test-d.ts b/test/types/serverFactory.test-d.ts index 1ad1354414a..ecb04b5f61c 100644 --- a/test/types/serverFactory.test-d.ts +++ b/test/types/serverFactory.test-d.ts @@ -1,5 +1,5 @@ import fastify, { FastifyServerFactory } from '../../fastify' -import * as http from 'http' +import * as http from 'node:http' import { expectType } from 'tsd' // Custom Server diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index e4c7f14fa7b..b885f478581 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -8,7 +8,7 @@ import fastify, { SafePromiseLike } from '../../fastify' import { expectAssignable, expectError, expectType } from 'tsd' -import { IncomingHttpHeaders } from 'http' +import { IncomingHttpHeaders } from 'node:http' import { Type, TSchema, Static } from '@sinclair/typebox' import { FromSchema, JSONSchema } from 'json-schema-to-ts' diff --git a/types/hooks.d.ts b/types/hooks.d.ts index 7b939698a32..cbcda11dc49 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -1,4 +1,4 @@ -import { Readable } from 'stream' +import { Readable } from 'node:stream' import { FastifyInstance } from './instance' import { RouteOptions, RouteGenericInterface } from './route' import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' diff --git a/types/instance.d.ts b/types/instance.d.ts index 00ad258080c..10b559fb6ed 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -1,8 +1,8 @@ import { FastifyError } from '@fastify/error' import { ConstraintStrategy, FindResult, HTTPVersion } from 'find-my-way' -import * as http from 'http' +import * as http from 'node:http' import { InjectOptions, CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request' -import { AddressInfo } from 'net' +import { AddressInfo } from 'node:net' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, ProtoAction, getDefaultJsonParser, hasContentTypeParser, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' import { ApplicationHook, HookAsyncLookup, HookLookup, LifecycleHook, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onListenAsyncHookHandler, onListenHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' import { FastifyBaseLogger, FastifyChildLoggerFactory } from './logger' diff --git a/types/reply.d.ts b/types/reply.d.ts index 75edf70ca6c..8c43cc74921 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -1,4 +1,4 @@ -import { Buffer } from 'buffer' +import { Buffer } from 'node:buffer' import { FastifyInstance } from './instance' import { FastifyBaseLogger } from './logger' import { FastifyRequest, RequestRouteOptions } from './request' diff --git a/types/serverFactory.d.ts b/types/serverFactory.d.ts index c4d72b82526..05e743a0566 100644 --- a/types/serverFactory.d.ts +++ b/types/serverFactory.d.ts @@ -1,7 +1,7 @@ import { RawServerBase, RawServerDefault, RawReplyDefaultExpression, RawRequestDefaultExpression } from './utils' -import * as http from 'http' -import * as https from 'https' -import * as http2 from 'http2' +import * as http from 'node:http' +import * as https from 'node:https' +import * as http2 from 'node:http2' export type FastifyServerFactoryHandler< RawServer extends RawServerBase = RawServerDefault, diff --git a/types/utils.d.ts b/types/utils.d.ts index d65e1e91ef2..c569e15526d 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -1,6 +1,6 @@ -import * as http from 'http' -import * as http2 from 'http2' -import * as https from 'https' +import * as http from 'node:http' +import * as http2 from 'node:http2' +import * as https from 'node:https' type AutocompletePrimitiveBaseType = T extends string ? string : From c85e0bf6f0bc26569ca8ebd1ba613db63459fb46 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 14 Dec 2024 07:57:32 +0100 Subject: [PATCH 0873/1295] test: migrated content-length.test.js from tap to node:test (#5878) --- test/content-length.test.js | 121 ++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 68 deletions(-) diff --git a/test/content-length.test.js b/test/content-length.test.js index c59293495d6..0f62138af89 100644 --- a/test/content-length.test.js +++ b/test/content-length.test.js @@ -1,11 +1,10 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') -test('default 413 with bodyLimit option', t => { - t.plan(4) +test('default 413 with bodyLimit option', async (t) => { + t.plan(3) const fastify = Fastify({ bodyLimit: 10 @@ -15,27 +14,25 @@ test('default 413 with bodyLimit option', t => { reply.send({ hello: 'world' }) }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', url: '/', body: { text: '12345678901234567890123456789012345678901234567890' } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 413) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { - error: 'Payload Too Large', - code: 'FST_ERR_CTP_BODY_TOO_LARGE', - message: 'Request body is too large', - statusCode: 413 - }) + }) + t.assert.strictEqual(response.statusCode, 413) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(response.payload), { + error: 'Payload Too Large', + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + message: 'Request body is too large', + statusCode: 413 }) }) -test('default 400 with wrong content-length', t => { - t.plan(4) +test('default 400 with wrong content-length', async (t) => { + t.plan(3) const fastify = Fastify() @@ -43,7 +40,7 @@ test('default 400 with wrong content-length', t => { reply.send({ hello: 'world' }) }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', url: '/', headers: { @@ -52,21 +49,19 @@ test('default 400 with wrong content-length', t => { body: { text: '12345678901234567890123456789012345678901234567890' } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { - error: 'Bad Request', - code: 'FST_ERR_CTP_INVALID_CONTENT_LENGTH', - message: 'Request body size did not match Content-Length', - statusCode: 400 - }) + }) + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(response.payload), { + error: 'Bad Request', + code: 'FST_ERR_CTP_INVALID_CONTENT_LENGTH', + message: 'Request body size did not match Content-Length', + statusCode: 400 }) }) -test('custom 413 with bodyLimit option', t => { - t.plan(4) +test('custom 413 with bodyLimit option', async (t) => { + t.plan(3) const fastify = Fastify({ bodyLimit: 10 @@ -83,27 +78,25 @@ test('custom 413 with bodyLimit option', t => { .send(err) }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', url: '/', body: { text: '12345678901234567890123456789012345678901234567890' } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 413) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { - error: 'Payload Too Large', - code: 'FST_ERR_CTP_BODY_TOO_LARGE', - message: 'Request body is too large', - statusCode: 413 - }) + }) + t.assert.strictEqual(response.statusCode, 413) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(response.payload), { + error: 'Payload Too Large', + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + message: 'Request body is too large', + statusCode: 413 }) }) -test('custom 400 with wrong content-length', t => { - t.plan(4) +test('custom 400 with wrong content-length', async (t) => { + t.plan(3) const fastify = Fastify() @@ -118,7 +111,7 @@ test('custom 400 with wrong content-length', t => { .send(err) }) - fastify.inject({ + const response = await fastify.inject({ method: 'POST', url: '/', headers: { @@ -127,20 +120,18 @@ test('custom 400 with wrong content-length', t => { body: { text: '12345678901234567890123456789012345678901234567890' } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { - error: 'Bad Request', - code: 'FST_ERR_CTP_INVALID_CONTENT_LENGTH', - message: 'Request body size did not match Content-Length', - statusCode: 400 - }) + }) + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(response.payload), { + error: 'Bad Request', + code: 'FST_ERR_CTP_INVALID_CONTENT_LENGTH', + message: 'Request body size did not match Content-Length', + statusCode: 400 }) }) -test('#2214 - wrong content-length', t => { +test('#2214 - wrong content-length', async (t) => { const fastify = Fastify() fastify.get('/', async () => { @@ -151,17 +142,14 @@ test('#2214 - wrong content-length', t => { throw error }) - fastify.inject({ + const response = await fastify.inject({ method: 'GET', path: '/' }) - .then(response => { - t.equal(response.headers['content-length'], '' + response.rawPayload.length) - t.end() - }) + t.assert.strictEqual(response.headers['content-length'], '' + response.rawPayload.length) }) -test('#2543 - wrong content-length with errorHandler', t => { +test('#2543 - wrong content-length with errorHandler', async (t) => { const fastify = Fastify() fastify.setErrorHandler((_error, _request, reply) => { @@ -176,14 +164,11 @@ test('#2543 - wrong content-length with errorHandler', t => { throw error }) - fastify.inject({ + const response = await fastify.inject({ method: 'GET', path: '/' }) - .then(res => { - t.equal(res.statusCode, 500) - t.equal(res.headers['content-length'], '' + res.rawPayload.length) - t.same(JSON.parse(res.payload), { message: 'longer than 2 bytes' }) - t.end() - }) + t.assert.strictEqual(response.statusCode, 500) + t.assert.strictEqual(response.headers['content-length'], '' + response.rawPayload.length) + t.assert.deepStrictEqual(JSON.parse(response.payload), { message: 'longer than 2 bytes' }) }) From 9177df07b091f440ddec9f76ee7625b466b37e8d Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 14 Dec 2024 08:52:59 +0100 Subject: [PATCH 0874/1295] Bumped v5.2.0 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 162111f4d5d..73c17818882 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.1.0' +const VERSION = '5.2.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 636256656a4..28e5b802549 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.1.0", + "version": "5.2.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 0cb427346a566e87c94e1c30b6e85201f2b74e32 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 14 Dec 2024 11:48:53 +0100 Subject: [PATCH 0875/1295] chore: org members reorder (#5898) --- CONTRIBUTING.md | 4 +-- README.md | 85 +++++++++++++++++++++++-------------------------- 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b28f5bde22..6cfd85bbcff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,8 +97,8 @@ the following tasks: [`fastify/fastify:HEAD`](https://github.com/fastify/fastify/pulls) that adds your name, username, and email to the team you have chosen in the [README.md](./README.md) and [package.json](./package.json) *(if you are part - of the core team)* files. The members lists are sorted alphabetically; make - sure to add your name in the proper order. + of the core team)* files. The members lists are sorted alphabetically by last + name; make sure to add your name in the proper order. 4. Open a pull request to [`fastify/website:HEAD`](https://github.com/fastify/website/pulls) adding yourself to the diff --git a/README.md b/README.md index 592002252bf..3c376f8b67c 100644 --- a/README.md +++ b/README.md @@ -290,23 +290,16 @@ listed in alphabetical order. , ### Fastify Core team -* [__Tommaso Allevi__](https://github.com/allevo), - , +* [__Aras Abbasi__](https://github.com/uzlopak), + * [__Harry Brundage__](https://github.com/airhorns/), , -* [__David Mark Clements__](https://github.com/davidmarkclements), - , - * [__Matteo Collina__](https://github.com/mcollina), , +* [__Gürgün Dayıoğlu__](https://github.com/gurgunday), + * [__Tomas Della Vedova__](https://github.com/delvedor), , -* [__Dustin Deus__](https://github.com/StarpTech), - , -* [__Ayoub El Khattabi__](https://github.com/AyoubElk), - , -* [__Denis Fäcke__](https://github.com/SerayaEryn), - , * [__Carlos Fuentes__](https://github.com/metcoder95), , * [__Vincent Le Goff__](https://github.com/zekth) @@ -320,63 +313,63 @@ listed in alphabetical order. , * [__James Sumners__](https://github.com/jsumners), , -* [__Aras Abbasi__](https://github.com/uzlopak), - -* [__Gürgün Dayıoğlu__](https://github.com/gurgunday), - ### Fastify Plugins team -* [__Matteo Collina__](https://github.com/mcollina), - , * [__Harry Brundage__](https://github.com/airhorns/), , +* [__Simone Busoli__](https://github.com/simoneb), + , +* [__Dan Castillo__](https://github.com/dancastillo), + +* [__Matteo Collina__](https://github.com/mcollina), + , +* [__Gürgün Dayıoğlu__](https://github.com/gurgunday), + * [__Tomas Della Vedova__](https://github.com/delvedor), , -* [__Ayoub El Khattabi__](https://github.com/AyoubElk), - , * [__Carlos Fuentes__](https://github.com/metcoder95), , * [__Vincent Le Goff__](https://github.com/zekth) -* [__Salman Mitha__](https://github.com/salmanm), - +* [__Jean Michelet__](https://github.com/jean-michelet), + * [__Maksim Sinik__](https://github.com/fox1t), , -* [__Frazer Smith__](https://github.com/Fdawgs), * [__Manuel Spigolon__](https://github.com/eomm), , -* [__Simone Busoli__](https://github.com/simoneb), - , -* [__Gürgün Dayıoğlu__](https://github.com/gurgunday), - -* [__Dan Castillo__](https://github.com/dancastillo), - -* [__Jean Michelet__](https://github.com/jean-michelet), - +* [__Frazer Smith__](https://github.com/Fdawgs), -### Great Contributors +### Emeritus Contributors Great contributors on a specific area in the Fastify ecosystem will be invited -to join this group by Lead Maintainers. +to join this group by Lead Maintainers when they decide to step down from the +active contributors group. -* [__dalisoft__](https://github.com/dalisoft), , - -* [__Luciano Mammino__](https://github.com/lmammino), - , -* [__Evan Shortiss__](https://github.com/evanshortiss), - , - -**Past Collaborators** +* [__Tommaso Allevi__](https://github.com/allevo), + , +* [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/), + , * [__Çağatay Çalı__](https://github.com/cagataycali), , -* [__Trivikram Kamat__](https://github.com/trivikr), - , +* [__David Mark Clements__](https://github.com/davidmarkclements), + , + +* [__dalisoft__](https://github.com/dalisoft), , + +* [__Dustin Deus__](https://github.com/StarpTech), + , +* [__Denis Fäcke__](https://github.com/SerayaEryn), + , +* [__Rafael Gonzaga__](https://github.com/rafaelgss), + , +* [__Ayoub El Khattabi__](https://github.com/AyoubElk), + , * [__Cemre Mengu__](https://github.com/cemremengu), , +* [__Salman Mitha__](https://github.com/salmanm), + +* [__Trivikram Kamat__](https://github.com/trivikr), + , * [__Nathan Woltman__](https://github.com/nwoltman), , -* [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/), - , -* [__Rafael Gonzaga__](https://github.com/rafaelgss), - , ## Hosted by From 48ceb9d35f6317ebf9c93a14568e99a5cf0718e7 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 17 Dec 2024 18:39:21 +0000 Subject: [PATCH 0876/1295] docs(request): clarify request host functionality (#5904) --- README.md | 6 +++--- docs/Reference/Request.md | 17 ++++++++--------- lib/request.js | 20 ++++++++++++-------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3c376f8b67c..3dd57adb042 100644 --- a/README.md +++ b/README.md @@ -334,9 +334,9 @@ listed in alphabetical order. * [__Maksim Sinik__](https://github.com/fox1t), , +* [__Frazer Smith__](https://github.com/Fdawgs), * [__Manuel Spigolon__](https://github.com/eomm), , -* [__Frazer Smith__](https://github.com/Fdawgs), ### Emeritus Contributors Great contributors on a specific area in the Fastify ecosystem will be invited @@ -360,14 +360,14 @@ active contributors group. , * [__Rafael Gonzaga__](https://github.com/rafaelgss), , +* [__Trivikram Kamat__](https://github.com/trivikr), + , * [__Ayoub El Khattabi__](https://github.com/AyoubElk), , * [__Cemre Mengu__](https://github.com/cemremengu), , * [__Salman Mitha__](https://github.com/salmanm), -* [__Trivikram Kamat__](https://github.com/trivikr), - , * [__Nathan Woltman__](https://github.com/nwoltman), , diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 156d3d80c0f..cc82b65bb8b 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -23,13 +23,12 @@ Request is a core Fastify object containing the following fields: - `host` - the host of the incoming request (derived from `X-Forwarded-Host` header when the [`trustProxy`](./Server.md#factory-trust-proxy) option is enabled). For HTTP/2 compatibility it returns `:authority` if no host header - exists. The host header may return empty string when using - `requireHostHeader = false`, not suppied when connected with `HTTP/1.0` or - using schema validation that remove the extra headers. -- `hostname` - the host of the `host` property, it may refers the incoming - request hostname -- `port` - the port of the `host` property, it may refers the port thats - the server is listening on + exists. The host header may return an empty string if `requireHostHeader` + is false, not provided with HTTP/1.0, or removed by schema validation. +- `hostname` - the hostname derived from the `host` property of + the incoming request +- `port` - the port from the `host` property, which may refer to + the port the server is listening on - `protocol` - the protocol of the incoming request (`https` or `http`) - `method` - the method of the incoming request - `url` - the URL of the incoming request @@ -88,8 +87,8 @@ request's headers with the `request.raw.headers` property. > Note: For performance reason on `not found` route, you may see that we will add an extra property `Symbol('fastify.RequestAcceptVersion')` on the headers. -> Note: When using schema, it may mutate the `request.headers` and -`request.raw.headers` object. So, you may found the headers becomes empty. +> Note: Using schema validation may mutate the `request.headers` and +`request.raw.headers` objects, causing the headers to become empty. ```js fastify.post('/:params', options, function (request, reply) { diff --git a/lib/request.js b/lib/request.js index 9c6525368a2..4f50e6467e4 100644 --- a/lib/request.js +++ b/lib/request.js @@ -117,10 +117,12 @@ function buildRequestWithTrustProxy (R, trustProxy) { if (this.ip !== undefined && this.headers['x-forwarded-host']) { return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host']) } - // the last fallback is used to support the following cases: - // 1. support http.requireHostHeader === false - // 2. support HTTP/1.0 without Host Header - // 3. support headers schema which may remove the Host Header + /** + * The last fallback supports the following cases: + * 1. http.requireHostHeader === false + * 2. HTTP/1.0 without a Host Header + * 3. Headers schema that may remove the Host Header + */ return this.headers.host ?? this.headers[':authority'] ?? '' } }, @@ -213,10 +215,12 @@ Object.defineProperties(Request.prototype, { }, host: { get () { - // the last fallback is used to support the following cases: - // 1. support http.requireHostHeader === false - // 2. support HTTP/1.0 without Host Header - // 3. support headers schema which may remove the Host Header + /** + * The last fallback supports the following cases: + * 1. http.requireHostHeader === false + * 2. HTTP/1.0 without a Host Header + * 3. Headers schema that may remove the Host Header + */ return this.raw.headers.host ?? this.raw.headers[':authority'] ?? '' } }, From 323bc2f686fe013af3be2c815abfe77db783bcf7 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 18 Dec 2024 08:14:08 +0000 Subject: [PATCH 0877/1295] chore(package): add fdawgs to contributors array (#5905) Signed-off-by: Frazer Smith --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 28e5b802549..39ffb0aa15f 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,11 @@ { "name": "Aras Abbasi", "email": "aras.abbasi@gmail.com" + }, + { + "name": "Frazer Smith", + "email": "frazer.dev@icloud.com", + "url": "https://github.com/fdawgs" } ], "license": "MIT", From a9d62d38b519e267671719d890cbdaa0da4ad182 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:26:49 -0500 Subject: [PATCH 0878/1295] chore: updated test reporter (#5907) --- package.json | 13 ++++---- test/test-reporter.mjs | 68 ------------------------------------------ 2 files changed, 7 insertions(+), 74 deletions(-) delete mode 100644 test/test-reporter.mjs diff --git a/package.json b/package.json index 39ffb0aa15f..8d818d4ed8a 100644 --- a/package.json +++ b/package.json @@ -10,22 +10,22 @@ "benchmark": "concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"autocannon -c 100 -d 30 -p 10 localhost:3000/\"", "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", - "coverage": "c8 --reporter html borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --lines 100 ", - "coverage:ci-check-coverage": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --lines 100", + "coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100 ", + "coverage:ci-check-coverage": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100", "lint": "npm run lint:eslint", "lint:fix": "eslint --fix", "lint:markdown": "markdownlint-cli2", "lint:eslint": "eslint", - "prepublishOnly": "cross-env PREPUBLISH=true borp --reporter=./test/test-reporter.mjs && npm run test:validator:integrity", + "prepublishOnly": "cross-env PREPUBLISH=true borp --reporter=@jsumners/line-reporter && npm run test:validator:integrity", "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run unit && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", "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", "test:typescript": "tsc test/types/import.ts --target es2022 --moduleResolution node16 --module node16 --noEmit && tsd", "test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse", - "unit": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage", - "unit:report": "c8 --reporter html borp --reporter=./test/test-reporter.mjs", - "citgm": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --concurrency=1" + "unit": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage", + "unit:report": "c8 --reporter html borp --reporter=@jsumners/line-reporter", + "citgm": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage --concurrency=1" }, "repository": { "type": "git", @@ -155,6 +155,7 @@ ], "devDependencies": { "@fastify/pre-commit": "^2.1.0", + "@jsumners/line-reporter": "^1.0.1", "@sinclair/typebox": "^0.33.4", "@sinonjs/fake-timers": "^11.2.2", "@stylistic/eslint-plugin": "^2.1.0", diff --git a/test/test-reporter.mjs b/test/test-reporter.mjs deleted file mode 100644 index bf7087ec81f..00000000000 --- a/test/test-reporter.mjs +++ /dev/null @@ -1,68 +0,0 @@ -function colorize (type, text) { - if (type === 'pass') { - const blackText = `\x1b[30m${text}` - const boldblackText = `\x1b[1m${blackText}` - // Green background with black text - return `\x1b[42m${boldblackText}\x1b[0m` - } - - if (type === 'fail') { - const whiteText = `\x1b[37m${text}` - const boldWhiteText = `\x1b[1m${whiteText}` - // Red background with white text - return `\x1b[41m${boldWhiteText}\x1b[0m` - } - - return text -} - -function formatDiagnosticStr (str) { - return str.replace(/^(\w+)(\s*\d*)/i, (_, firstWord, rest) => { - return firstWord.charAt(0).toUpperCase() + firstWord.slice(1).toLowerCase() + ':' + rest - }) -} - -async function * reporter (source) { - const failed = new Set() - const diagnostics = new Set() - - for await (const event of source) { - switch (event.type) { - case 'test:pass': { - yield `${colorize('pass', 'PASSED')}: ${event.data.file || event.data.name}\n` - break - } - - case 'test:fail': { - failed.add(event.data.name || event.data.file) - yield `${colorize('fail', 'FAILED')}: ${event.data.file || event.data.name}\n` - break - } - - case 'test:diagnostic': { - diagnostics.add(`${formatDiagnosticStr(event.data.message)}\n`) - break - } - - default: { - yield '' - } - } - } - - if (failed.size > 0) { - yield `\n\n${colorize('fail', 'Failed tests:')}\n` - for (const file of failed) { - yield `${file}\n` - } - } - - yield '\n' - - for (const diagnostic of diagnostics) { - yield `${diagnostic}` - } - yield '\n' -} - -export default reporter From fa95e84a53a6d8676b441d64b1bfe9ec902e408d Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Fri, 20 Dec 2024 13:08:59 +0100 Subject: [PATCH 0879/1295] test: migrated custom-parser.2.test.js from tap to node:test (#5902) --- test/custom-parser.2.test.js | 39 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/test/custom-parser.2.test.js b/test/custom-parser.2.test.js index 0cc8f92953f..30ff87b884f 100644 --- a/test/custom-parser.2.test.js +++ b/test/custom-parser.2.test.js @@ -1,9 +1,8 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat -const Fastify = require('../fastify') +const Fastify = require('..') const { getServerUrl } = require('./helper') process.removeAllListeners('warning') @@ -14,17 +13,17 @@ test('Wrong parseAs parameter', t => { try { fastify.addContentTypeParser('application/json', { parseAs: 'fireworks' }, () => {}) - t.fail('should throw') + t.assert.fail('should throw') } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') - t.equal(err.message, "The body parser can only parse your data as 'string' or 'buffer', you asked 'fireworks' which is not supported.") + t.assert.strictEqual(err.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') + t.assert.strictEqual(err.message, "The body parser can only parse your data as 'string' or 'buffer', you asked 'fireworks' which is not supported.") } }) -test('Should allow defining the bodyLimit per parser', t => { +test('Should allow defining the bodyLimit per parser', (t, done) => { t.plan(3) const fastify = Fastify() - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) fastify.post('/', (req, reply) => { reply.send(req.body) @@ -34,13 +33,13 @@ test('Should allow defining the bodyLimit per parser', t => { 'x/foo', { parseAs: 'string', bodyLimit: 5 }, function (req, body, done) { - t.fail('should not be invoked') + t.assert.fail('should not be invoked') done() } ) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -50,22 +49,22 @@ test('Should allow defining the bodyLimit per parser', t => { 'Content-Type': 'x/foo' } }, (err, response, body) => { - t.error(err) - t.strictSame(JSON.parse(body.toString()), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body.toString()), { statusCode: 413, code: 'FST_ERR_CTP_BODY_TOO_LARGE', error: 'Payload Too Large', message: 'Request body is too large' }) - fastify.close() + done() }) }) }) -test('route bodyLimit should take precedence over a custom parser bodyLimit', t => { +test('route bodyLimit should take precedence over a custom parser bodyLimit', (t, done) => { t.plan(3) const fastify = Fastify() - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) fastify.post('/', { bodyLimit: 5 }, (request, reply) => { reply.send(request.body) @@ -75,13 +74,13 @@ test('route bodyLimit should take precedence over a custom parser bodyLimit', t 'x/foo', { parseAs: 'string', bodyLimit: 100 }, function (req, body, done) { - t.fail('should not be invoked') + t.assert.fail('should not be invoked') done() } ) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -89,14 +88,14 @@ test('route bodyLimit should take precedence over a custom parser bodyLimit', t body: '1234567890', headers: { 'Content-Type': 'x/foo' } }, (err, response, body) => { - t.error(err) - t.strictSame(JSON.parse(body.toString()), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body.toString()), { statusCode: 413, code: 'FST_ERR_CTP_BODY_TOO_LARGE', error: 'Payload Too Large', message: 'Request body is too large' }) - fastify.close() + done() }) }) }) From 221ec4532b9a8b856d217d0b2f5a867ab4c579eb Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 21 Dec 2024 12:19:36 +0100 Subject: [PATCH 0880/1295] test: migrated nullable-validation.test.js from tap to node:test (#5880) --- test/nullable-validation.test.js | 57 +++++++++++++++++--------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/test/nullable-validation.test.js b/test/nullable-validation.test.js index 2c9da1094ff..02ffa0c6859 100644 --- a/test/nullable-validation.test.js +++ b/test/nullable-validation.test.js @@ -1,18 +1,17 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('..') -test('nullable string', t => { +test('nullable string', (t, done) => { t.plan(3) const fastify = Fastify() fastify.route({ method: 'POST', url: '/', handler: (req, reply) => { - t.same(req.body.hello, null) + t.assert.strictEqual(req.body.hello, null) reply.code(200).send(req.body) }, schema: { @@ -47,12 +46,13 @@ test('nullable string', t => { hello: null } }, (err, res) => { - t.error(err) - t.same(res.payload.hello, null) + t.assert.ifError(err) + t.assert.strictEqual(res.json().hello, null) + done() }) }) -test('object or null body', t => { +test('object or null body', (t, done) => { t.plan(5) const fastify = Fastify() @@ -61,7 +61,7 @@ test('object or null body', t => { method: 'POST', url: '/', handler: (req, reply) => { - t.equal(req.body, undefined) + t.assert.strictEqual(req.body, undefined) reply.code(200).send({ isUndefinedBody: req.body === undefined }) }, schema: { @@ -89,21 +89,22 @@ test('object or null body', t => { }) fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'POST', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { isUndefinedBody: true }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { isUndefinedBody: true }) + done() }) }) }) -test('nullable body', t => { +test('nullable body', (t, done) => { t.plan(5) const fastify = Fastify() @@ -112,7 +113,7 @@ test('nullable body', t => { method: 'POST', url: '/', handler: (req, reply) => { - t.equal(req.body, undefined) + t.assert.strictEqual(req.body, undefined) reply.code(200).send({ isUndefinedBody: req.body === undefined }) }, schema: { @@ -141,21 +142,22 @@ test('nullable body', t => { }) fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'POST', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { isUndefinedBody: true }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { isUndefinedBody: true }) + done() }) }) }) -test('Nullable body with 204', t => { +test('Nullable body with 204', (t, done) => { t.plan(5) const fastify = Fastify() @@ -164,7 +166,7 @@ test('Nullable body with 204', t => { method: 'POST', url: '/', handler: (req, reply) => { - t.equal(req.body, undefined) + t.assert.strictEqual(req.body, undefined) reply.code(204).send() }, schema: { @@ -182,16 +184,17 @@ test('Nullable body with 204', t => { }) fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'POST', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 204) - t.equal(body.length, 0) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 204) + t.assert.strictEqual(body.length, 0) + done() }) }) }) From a8353251dc0d17fcf4e2bcd3ccaad755e5a83109 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 22 Dec 2024 10:32:10 +0100 Subject: [PATCH 0881/1295] test: migrated custom-parser.3.test.js from tap to node:test (#5903) --- test/custom-parser.3.test.js | 101 +++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/test/custom-parser.3.test.js b/test/custom-parser.3.test.js index ec8ac2b03dc..b81110a2536 100644 --- a/test/custom-parser.3.test.js +++ b/test/custom-parser.3.test.js @@ -1,18 +1,17 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat -const Fastify = require('../fastify') +const Fastify = require('..') const jsonParser = require('fast-json-body') const { getServerUrl } = require('./helper') process.removeAllListeners('warning') -test('should be able to use default parser for extra content type', t => { +test('should be able to use default parser for extra content type', (t, done) => { t.plan(4) const fastify = Fastify() - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) fastify.post('/', (request, reply) => { reply.send(request.body) @@ -21,7 +20,7 @@ test('should be able to use default parser for extra content type', t => { fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore')) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -31,18 +30,17 @@ test('should be able to use default parser for extra content type', t => { 'Content-Type': 'text/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.strictSame(JSON.parse(body.toString()), { hello: 'world' }) - fastify.close() + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body.toString()), { hello: 'world' }) + done() }) }) }) -test('contentTypeParser should add a custom parser with RegExp value', t => { - t.plan(3) - +test('contentTypeParser should add a custom parser with RegExp value', async (t) => { const fastify = Fastify() + t.after(() => fastify.close()) fastify.post('/', (req, reply) => { reply.send(req.body) @@ -58,13 +56,12 @@ test('contentTypeParser should add a custom parser with RegExp value', t => { }) }) - fastify.listen({ port: 0 }, err => { - t.error(err) - - t.teardown(() => fastify.close()) + fastify.listen({ port: 0 }, async err => { + t.assert.ifError(err) - t.test('in POST', t => { + await t.test('in POST', (t, done) => { t.plan(3) + t.after(() => fastify.close()) sget({ method: 'POST', @@ -74,14 +71,16 @@ test('contentTypeParser should add a custom parser with RegExp value', t => { 'Content-Type': 'application/vnd.test+json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + done() }) }) - t.test('in OPTIONS', t => { + await t.test('in OPTIONS', (t, done) => { t.plan(3) + t.after(() => fastify.close()) sget({ method: 'OPTIONS', @@ -91,9 +90,10 @@ test('contentTypeParser should add a custom parser with RegExp value', t => { 'Content-Type': 'weird/content-type+json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + done() }) }) }) @@ -102,7 +102,7 @@ test('contentTypeParser should add a custom parser with RegExp value', t => { test('contentTypeParser should add multiple custom parsers with RegExp values', async t => { t.plan(6) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.post('/', (req, reply) => { reply.send(req.body) @@ -137,8 +137,8 @@ test('contentTypeParser should add multiple custom parsers with RegExp values', 'Content-Type': 'application/vnd.hello+json' } }) - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), '{"hello":"world"}') + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.payload.toString(), '{"hello":"world"}') } { @@ -150,8 +150,8 @@ test('contentTypeParser should add multiple custom parsers with RegExp values', 'Content-Type': 'application/test+xml' } }) - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), 'xml') + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.payload.toString(), 'xml') } await fastify.inject({ @@ -162,17 +162,17 @@ test('contentTypeParser should add multiple custom parsers with RegExp values', 'Content-Type': 'application/+myExtension' } }).then((response) => { - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), 'abcdefgmyExtension') + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.payload.toString(), 'abcdefgmyExtension') }).catch((err) => { - t.error(err) + t.assert.ifError(err) }) }) -test('catch all content type parser should not interfere with content type parser', t => { +test('catch all content type parser should not interfere with content type parser', (t, done) => { t.plan(10) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.post('/', (req, reply) => { reply.send(req.body) @@ -201,7 +201,15 @@ test('catch all content type parser should not interfere with content type parse }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + + let pending = 3 + + function completed () { + if (--pending === 0) { + done() + } + } sget({ method: 'POST', @@ -211,9 +219,10 @@ test('catch all content type parser should not interfere with content type parse 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ myKey: 'myValue' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ myKey: 'myValue' })) + completed() }) sget({ @@ -224,9 +233,10 @@ test('catch all content type parser should not interfere with content type parse 'Content-Type': 'very-weird-content-type' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'body') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), 'body') + completed() }) sget({ @@ -237,9 +247,10 @@ test('catch all content type parser should not interfere with content type parse 'Content-Type': 'text/html' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'my texthtml') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), 'my texthtml') + completed() }) }) }) From 3c2ffae72d391f3ea0f661b850c346b86a1cc713 Mon Sep 17 00:00:00 2001 From: Kalpana Upadhyay <51444674+Kalpana98@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:35:04 +0530 Subject: [PATCH 0882/1295] test: migrate delete test from tap to node test runner (#5906) * test: migrate delete test from tap to node test runner * test: fix test case for delete * test: convert t.assert.equal to t.assert.strictEqual and t.assert.deepEqual to t.assert.deepStrictEqual * test: update import on suggestion * test: update import for node:assert --------- Co-authored-by: Frazer Smith --- test/delete.test.js | 146 ++++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 67 deletions(-) diff --git a/test/delete.test.js b/test/delete.test.js index 1a296fbe8f6..e35725c249a 100644 --- a/test/delete.test.js +++ b/test/delete.test.js @@ -1,7 +1,7 @@ 'use strict' -const t = require('tap') -const test = t.test +const assert = require('node:assert') +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('..')() @@ -85,15 +85,17 @@ const bodySchema = { } } -test('shorthand - delete', t => { +test('shorthand - delete', (t, done) => { t.plan(1) try { fastify.delete('/', schema, function (req, reply) { reply.code(200).send({ hello: 'world' }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() + } finally { + done() } }) @@ -103,9 +105,9 @@ test('shorthand - delete params', t => { fastify.delete('/params/:foo/:test', paramsSchema, function (req, reply) { reply.code(200).send(req.params) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -115,9 +117,9 @@ test('shorthand - delete, querystring schema', t => { fastify.delete('/query', querySchema, function (req, reply) { reply.send(req.query) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -127,9 +129,9 @@ test('shorthand - get, headers schema', t => { fastify.delete('/headers', headersSchema, function (req, reply) { reply.code(200).send(req.headers) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -139,9 +141,9 @@ test('missing schema - delete', t => { fastify.delete('/missing', function (req, reply) { reply.code(200).send({ hello: 'world' }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -151,60 +153,63 @@ test('body - delete', t => { fastify.delete('/body', bodySchema, function (req, reply) { reply.send(req.body) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + assert.ifError(err) + test.after(() => { fastify.close() }) - test('shorthand - request delete', t => { + test('shorthand - request delete', (t, done) => { t.plan(4) sget({ method: 'DELETE', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) - test('shorthand - request delete params schema', t => { + test('shorthand - request delete params schema', (t, done) => { t.plan(4) sget({ method: 'DELETE', url: 'http://localhost:' + fastify.server.address().port + '/params/world/123' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { foo: 'world', test: 123 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) + done() }) }) - test('shorthand - request delete params schema error', t => { + test('shorthand - request delete params schema error', (t, done) => { t.plan(3) sget({ method: 'DELETE', url: 'http://localhost:' + fastify.server.address().port + '/params/world/string' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'params/test must be integer', statusCode: 400 }) + done() }) }) - test('shorthand - request delete headers schema', t => { + test('shorthand - request delete headers schema', (t, done) => { t.plan(4) sget({ method: 'DELETE', @@ -213,14 +218,15 @@ fastify.listen({ port: 0 }, err => { }, url: 'http://localhost:' + fastify.server.address().port + '/headers' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.equal(JSON.parse(body)['x-test'], 1) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.strictEqual(JSON.parse(body)['x-test'], 1) + done() }) }) - test('shorthand - request delete headers schema error', t => { + test('shorthand - request delete headers schema error', (t, done) => { t.plan(3) sget({ method: 'DELETE', @@ -229,61 +235,65 @@ fastify.listen({ port: 0 }, err => { }, url: 'http://localhost:' + fastify.server.address().port + '/headers' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'headers/x-test must be number', statusCode: 400 }) + done() }) }) - test('shorthand - request delete querystring schema', t => { + test('shorthand - request delete querystring schema', (t, done) => { t.plan(4) sget({ method: 'DELETE', url: 'http://localhost:' + fastify.server.address().port + '/query?hello=123' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 123 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) + done() }) }) - test('shorthand - request delete querystring schema error', t => { + test('shorthand - request delete querystring schema error', (t, done) => { t.plan(3) sget({ method: 'DELETE', url: 'http://localhost:' + fastify.server.address().port + '/query?hello=world' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(JSON.parse(body), { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', code: 'FST_ERR_VALIDATION', message: 'querystring/hello must be integer', statusCode: 400 }) + done() }) }) - test('shorthand - request delete missing schema', t => { + test('shorthand - request delete missing schema', (t, done) => { t.plan(4) sget({ method: 'DELETE', url: 'http://localhost:' + fastify.server.address().port + '/missing' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) - test('shorthand - delete with body', t => { + test('shorthand - delete with body', (t, done) => { t.plan(3) sget({ method: 'DELETE', @@ -293,18 +303,19 @@ fastify.listen({ port: 0 }, err => { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: 'world' }) + done() }) }) }) -test('shorthand - delete with application/json Content-Type header and null body', t => { +test('shorthand - delete with application/json Content-Type header and null body', (t, done) => { t.plan(4) const fastify = require('..')() fastify.delete('/', {}, (req, reply) => { - t.equal(req.body, null) + t.assert.strictEqual(req.body, null) reply.send(req.body) }) fastify.inject({ @@ -313,9 +324,10 @@ test('shorthand - delete with application/json Content-Type header and null body headers: { 'Content-Type': 'application/json' }, body: 'null' }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), 'null') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.payload.toString(), 'null') + done() }) }) @@ -325,7 +337,7 @@ test('shorthand - delete with application/json Content-Type header and without b t.plan(4) const fastify = require('..')() fastify.delete('/', {}, (req, reply) => { - t.equal(req.body, undefined) + t.assert.strictEqual(req.body, undefined) reply.send(req.body) }) fastify.inject({ @@ -334,8 +346,8 @@ test('shorthand - delete with application/json Content-Type header and without b headers: { 'Content-Type': 'application/json' }, body: null }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(response.payload.toString(), '') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.payload.toString(), '') }) }) From f1b0174fbc4269026f3d20ca887ddd2aeadf76ff Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:05:03 +0800 Subject: [PATCH 0883/1295] docs: add climba03003 to team (#5910) --- README.md | 6 ++++++ package.json | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 3dd57adb042..f9bd4a2fab4 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,8 @@ listed in alphabetical order. , * [__Tomas Della Vedova__](https://github.com/delvedor), , +* [__KaKa Ng__](https://github.com/climba03003), + * [__Manuel Spigolon__](https://github.com/eomm), , * [__James Sumners__](https://github.com/jsumners), @@ -305,6 +307,8 @@ listed in alphabetical order. * [__Vincent Le Goff__](https://github.com/zekth) * [__Luciano Mammino__](https://github.com/lmammino), , +* [__KaKa Ng__](https://github.com/climba03003), + * [__Luis Orbaiceta__](https://github.com/luisorbaiceta), , * [__Maksim Sinik__](https://github.com/fox1t), @@ -332,6 +336,8 @@ listed in alphabetical order. * [__Vincent Le Goff__](https://github.com/zekth) * [__Jean Michelet__](https://github.com/jean-michelet), +* [__KaKa Ng__](https://github.com/climba03003), + * [__Maksim Sinik__](https://github.com/fox1t), , * [__Frazer Smith__](https://github.com/Fdawgs), diff --git a/package.json b/package.json index 8d818d4ed8a..7354f384ebb 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,11 @@ "name": "Frazer Smith", "email": "frazer.dev@icloud.com", "url": "https://github.com/fdawgs" + }, + { + "name": "KaKa Ng", + "email": "kaka@kakang.dev", + "url": "https://github.com/climba03003" } ], "license": "MIT", From c5f4a17b1342470e4de80414164723f812d095a1 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 28 Dec 2024 18:07:57 +0000 Subject: [PATCH 0884/1295] build(deps): replace `proxy-addr` with `@fastify/proxy-addr` (#5913) --- docs/Reference/Server.md | 2 +- lib/request.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index d9438197c73..26e52900a59 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -567,7 +567,7 @@ const fastify = Fastify({ trustProxy: true }) ``` For more examples, refer to the -[`proxy-addr`](https://www.npmjs.com/package/proxy-addr) package. +[`@fastify/proxy-addr`](https://www.npmjs.com/package/@fastify/proxy-addr) package. You may access the `ip`, `ips`, `host` and `protocol` values on the [`request`](./Request.md) object. diff --git a/lib/request.js b/lib/request.js index 4f50e6467e4..d47c87cfa99 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,6 +1,6 @@ 'use strict' -const proxyAddr = require('proxy-addr') +const proxyAddr = require('@fastify/proxy-addr') const { kHasBeenDecorated, kSchemaBody, diff --git a/package.json b/package.json index 7354f384ebb..22c36e49649 100644 --- a/package.json +++ b/package.json @@ -202,6 +202,7 @@ "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", @@ -209,7 +210,6 @@ "light-my-request": "^6.0.0", "pino": "^9.0.0", "process-warning": "^4.0.0", - "proxy-addr": "^2.0.7", "rfdc": "^1.3.1", "secure-json-parse": "^3.0.1", "semver": "^7.6.0", From 3bcb8cfb2e5c635a5ce60fd00f1d78ef150b91a5 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 3 Jan 2025 18:05:55 +0800 Subject: [PATCH 0885/1295] fix: ReadableStream.locked crashes application (#5920) * fix: ReadableStream.locked crashes application * docs: update readme --- docs/Reference/Errors.md | 2 ++ lib/errors.js | 4 ++++ lib/reply.js | 4 ++++ test/internals/errors.test.js | 26 +++++++++++++++------ test/web-api.test.js | 44 +++++++++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index ad19abafd81..97151d567a0 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -49,6 +49,7 @@ - [FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED](#fst_err_log_logger_and_logger_instance_provided) - [FST_ERR_REP_INVALID_PAYLOAD_TYPE](#fst_err_rep_invalid_payload_type) - [FST_ERR_REP_RESPONSE_BODY_CONSUMED](#fst_err_rep_response_body_consumed) + - [FST_ERR_REP_READABLE_STREAM_LOCKED](#fst_err_rep_readable_stream_locked) - [FST_ERR_REP_ALREADY_SENT](#fst_err_rep_already_sent) - [FST_ERR_REP_SENT_VALUE](#fst_err_rep_sent_value) - [FST_ERR_SEND_INSIDE_ONERR](#fst_err_send_inside_onerr) @@ -321,6 +322,7 @@ Below is a table with all the error codes that Fastify uses. | FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED | You cannot provide both `'logger'` and `'loggerInstance'`. | Please provide only one option. | [#5020](https://github.com/fastify/fastify/pull/5020) | | FST_ERR_REP_INVALID_PAYLOAD_TYPE | Reply payload can be either a `string` or a `Buffer`. | Use a `string` or a `Buffer` for the payload. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_REP_RESPONSE_BODY_CONSUMED | Using `Response` as reply payload, but the body is being consumed. | Make sure you don't consume the `Response.body` | [#5286](https://github.com/fastify/fastify/pull/5286) | +| FST_ERR_REP_READABLE_STREAM_LOCKED | Using `ReadableStream` as reply payload, but locked with another reader. | Make sure you don't call the `Readable.getReader` before sending or release lock with `reader.releaseLock()` before sending. | [#5920](https://github.com/fastify/fastify/pull/5920) | | FST_ERR_REP_ALREADY_SENT | A response was already sent. | - | [#1336](https://github.com/fastify/fastify/pull/1336) | | FST_ERR_REP_SENT_VALUE | The only possible value for `reply.sent` is `true`. | - | [#1336](https://github.com/fastify/fastify/pull/1336) | | FST_ERR_SEND_INSIDE_ONERR | You cannot use `send` inside the `onError` hook. | - | [#1348](https://github.com/fastify/fastify/pull/1348) | diff --git a/lib/errors.js b/lib/errors.js index 1a8fe5c9c26..06bb7c9741c 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -241,6 +241,10 @@ const codes = { 'FST_ERR_REP_RESPONSE_BODY_CONSUMED', 'Response.body is already consumed.' ), + FST_ERR_REP_READABLE_STREAM_LOCKED: createError( + 'FST_ERR_REP_READABLE_STREAM_LOCKED', + 'ReadableStream was locked. You should call releaseLock() method on reader before sending.' + ), FST_ERR_REP_ALREADY_SENT: createError( 'FST_ERR_REP_ALREADY_SENT', 'Reply was already sent, did you forget to "return reply" in "%s" (%s)?' diff --git a/lib/reply.js b/lib/reply.js index 1148a09c5a2..777972ab8e9 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -45,6 +45,7 @@ const CONTENT_TYPE = { const { FST_ERR_REP_INVALID_PAYLOAD_TYPE, FST_ERR_REP_RESPONSE_BODY_CONSUMED, + FST_ERR_REP_READABLE_STREAM_LOCKED, FST_ERR_REP_ALREADY_SENT, FST_ERR_SEND_INSIDE_ONERR, FST_ERR_BAD_STATUS_CODE, @@ -670,6 +671,9 @@ function logStreamError (logger, err, res) { } function sendWebStream (payload, res, reply) { + if (payload.locked) { + throw FST_ERR_REP_READABLE_STREAM_LOCKED() + } const nodeStream = Readable.fromWeb(payload) sendStream(nodeStream, res, reply) } diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index f4eb6504d6f..06f4041c1f7 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,9 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -test('should expose 83 errors', t => { +const expectedErrors = 84 + +test(`should expose ${expectedErrors} errors`, t => { t.plan(1) const exportedKeys = Object.keys(errors) let counter = 0 @@ -14,11 +16,11 @@ test('should expose 83 errors', t => { counter++ } } - t.assert.strictEqual(counter, 83) + t.assert.strictEqual(counter, expectedErrors) }) test('ensure name and codes of Errors are identical', t => { - t.plan(83) + t.plan(expectedErrors) const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -377,6 +379,16 @@ test('FST_ERR_REP_RESPONSE_BODY_CONSUMED', t => { t.assert.ok(error instanceof Error) }) +test('FST_ERR_REP_READABLE_STREAM_LOCKED', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_READABLE_STREAM_LOCKED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_READABLE_STREAM_LOCKED') + t.assert.strictEqual(error.message, 'ReadableStream was locked. You should call releaseLock() method on reader before sending.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + test('FST_ERR_REP_ALREADY_SENT', t => { t.plan(5) const error = new errors.FST_ERR_REP_ALREADY_SENT('/hello', 'GET') @@ -868,7 +880,7 @@ test('FST_ERR_ERROR_HANDLER_NOT_FN', t => { }) test('Ensure that all errors are in Errors.md TOC', t => { - t.plan(83) + t.plan(expectedErrors) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -880,7 +892,7 @@ test('Ensure that all errors are in Errors.md TOC', t => { }) test('Ensure that non-existing errors are not in Errors.md TOC', t => { - t.plan(83) + t.plan(expectedErrors) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = / {4}- \[([A-Z0-9_]+)\]\(#[a-z0-9_]+\)/g @@ -893,7 +905,7 @@ test('Ensure that non-existing errors are not in Errors.md TOC', t => { }) test('Ensure that all errors are in Errors.md documented', t => { - t.plan(83) + t.plan(expectedErrors) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -905,7 +917,7 @@ test('Ensure that all errors are in Errors.md documented', t => { }) test('Ensure that non-existing errors are not in Errors.md documented', t => { - t.plan(83) + t.plan(expectedErrors) const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /([0-9a-zA-Z_]+)<\/a>/g diff --git a/test/web-api.test.js b/test/web-api.test.js index d8828696af8..fb8476d7810 100644 --- a/test/web-api.test.js +++ b/test/web-api.test.js @@ -227,6 +227,50 @@ test('Error when Response.bodyUsed', async (t) => { t.assert.strictEqual(body.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') }) +test('Error when Response.body.locked', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', async function (request, reply) { + const stream = Readable.toWeb(fs.createReadStream(__filename)) + const response = new Response(stream, { + status: 200, + headers: { + hello: 'world' + } + }) + stream.getReader() + t.assert.strictEqual(stream.locked, true) + return reply.send(response) + }) + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 500) + const body = response.json() + t.assert.strictEqual(body.code, 'FST_ERR_REP_READABLE_STREAM_LOCKED') +}) + +test('Error when ReadableStream.locked', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', async function (request, reply) { + const stream = Readable.toWeb(fs.createReadStream(__filename)) + stream.getReader() + t.assert.strictEqual(stream.locked, true) + return reply.send(stream) + }) + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 500) + const body = response.json() + t.assert.strictEqual(body.code, 'FST_ERR_REP_READABLE_STREAM_LOCKED') +}) + test('allow to pipe with fetch', async (t) => { t.plan(2) const abortController = new AbortController() From 05ba10dcf44f94bf377b22dcdf76a64d414cf814 Mon Sep 17 00:00:00 2001 From: FKPSC <99185575+FKPSC@users.noreply.github.com> Date: Sat, 4 Jan 2025 19:39:36 +0100 Subject: [PATCH 0886/1295] docs: fix typo (#5921) Signed-off-by: FKPSC <99185575+FKPSC@users.noreply.github.com> --- docs/Reference/Request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index cc82b65bb8b..eb62cf32ac0 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -161,7 +161,7 @@ This function will compile a validation schema and return a function that can be used to validate data. The function returned (a.k.a. _validation function_) is compiled by using the provided [`SchemaController#ValidationCompiler`](./Server.md#schema-controller). -A `WeakMap` is used to cached this, reducing compilation calls. +A `WeakMap` is used to cache this, reducing compilation calls. The optional parameter `httpPart`, if provided, is forwarded directly the `ValidationCompiler`, so it can be used to compile the validation From eafe0aa25ced21033310ae708b33e645ffb0d532 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 3 Jan 2025 18:07:50 +0800 Subject: [PATCH 0887/1295] Bumped v5.2.1 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 73c17818882..6894d36a85d 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.2.0' +const VERSION = '5.2.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 22c36e49649..a2a184ec7e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.2.0", + "version": "5.2.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 6c89e6b92facdcff7990086cc05ccb91fb0bbcbf Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:40:44 +0800 Subject: [PATCH 0888/1295] build: use static path instead of __filename (#5922) The path delimiter is different between Windows and Linux, which may cause problems when release. --- build/build-validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build-validation.js b/build/build-validation.js index 9e85d3f04d6..b82f74d421c 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -8,7 +8,7 @@ const path = require('node:path') const factory = AjvStandaloneCompiler({ readMode: false, storeFunction (routeOpts, schemaValidationCode) { - const moduleCode = `// This file is autogenerated by ${__filename.replace(__dirname, 'build')}, do not edit + const moduleCode = `// This file is autogenerated by build/build-validation.js, do not edit /* c8 ignore start */ ${schemaValidationCode} From 718ecbfad51fa00d165f02b77e709b8297d28dfc Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 7 Jan 2025 16:34:47 +0100 Subject: [PATCH 0889/1295] lint: fix linting error in error-handler.js (#5926) --- lib/error-handler.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/error-handler.js b/lib/error-handler.js index f53cfa557b5..3904779fa0c 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -110,18 +110,20 @@ function fallbackErrorHandler (error, reply, cb) { let payload try { const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type']) - payload = (serializerFn === false) - ? serializeError({ - error: statusCodes[statusCode + ''], - code: error.code, - message: error.message, - statusCode - }) - : serializerFn(Object.create(error, { - error: { value: statusCodes[statusCode + ''] }, - message: { value: error.message }, - statusCode: { value: statusCode } - })) + if (serializerFn === false) { + payload = serializeError({ + error: statusCodes[statusCode + ''], + code: error.code, + message: error.message, + statusCode + }) + } else { + payload = serializerFn(Object.create(error, { + error: { value: statusCodes[statusCode + ''] }, + message: { value: error.message }, + statusCode: { value: statusCode } + })) + } } catch (err) { if (!reply.log[kDisableRequestLogging]) { // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization From 787b7a7c2e00f2fa211a3e52d134ca1e8ce81530 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 09:41:07 +0000 Subject: [PATCH 0890/1295] chore: Bump the dev-dependencies group across 1 directory with 6 updates (#5930) Bumps the dev-dependencies group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@sinclair/typebox](https://github.com/sinclairzx81/typebox) | `0.33.22` | `0.34.13` | | [autocannon](https://github.com/mcollina/autocannon) | `7.15.0` | `8.0.0` | | [concurrently](https://github.com/open-cli-tools/concurrently) | `8.2.2` | `9.1.2` | | [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) | `0.13.0` | `0.17.1` | | [neostandard](https://github.com/neostandard/neostandard) | `0.11.9` | `0.12.0` | | [typescript](https://github.com/microsoft/TypeScript) | `5.4.5` | `5.7.2` | Updates `@sinclair/typebox` from 0.33.22 to 0.34.13 - [Commits](https://github.com/sinclairzx81/typebox/compare/0.33.22...0.34.13) Updates `autocannon` from 7.15.0 to 8.0.0 - [Release notes](https://github.com/mcollina/autocannon/releases) - [Commits](https://github.com/mcollina/autocannon/compare/v7.15.0...v8.0.0) Updates `concurrently` from 8.2.2 to 9.1.2 - [Release notes](https://github.com/open-cli-tools/concurrently/releases) - [Commits](https://github.com/open-cli-tools/concurrently/compare/v8.2.2...v9.1.2) Updates `markdownlint-cli2` from 0.13.0 to 0.17.1 - [Changelog](https://github.com/DavidAnson/markdownlint-cli2/blob/main/CHANGELOG.md) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.13.0...v0.17.1) Updates `neostandard` from 0.11.9 to 0.12.0 - [Release notes](https://github.com/neostandard/neostandard/releases) - [Changelog](https://github.com/neostandard/neostandard/blob/main/CHANGELOG.md) - [Commits](https://github.com/neostandard/neostandard/compare/v0.11.9...v0.12.0) Updates `typescript` from 5.4.5 to 5.7.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.4.5...v5.7.2) --- updated-dependencies: - dependency-name: "@sinclair/typebox" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: autocannon dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies - dependency-name: concurrently dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies - dependency-name: markdownlint-cli2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: neostandard dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index a2a184ec7e8..1f2099c9347 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "devDependencies": { "@fastify/pre-commit": "^2.1.0", "@jsumners/line-reporter": "^1.0.1", - "@sinclair/typebox": "^0.33.4", + "@sinclair/typebox": "^0.34.13", "@sinonjs/fake-timers": "^11.2.2", "@stylistic/eslint-plugin": "^2.1.0", "@stylistic/eslint-plugin-js": "^2.1.0", @@ -171,10 +171,10 @@ "ajv-formats": "^3.0.1", "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", - "autocannon": "^7.15.0", + "autocannon": "^8.0.0", "borp": "^0.18.0", "branch-comparer": "^1.1.0", - "concurrently": "^8.2.2", + "concurrently": "^9.1.2", "cross-env": "^7.0.3", "eslint": "^9.0.0", "fast-json-body": "^1.1.0", @@ -185,15 +185,15 @@ "joi": "^17.12.3", "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", - "markdownlint-cli2": "^0.13.0", - "neostandard": "^0.11.3", + "markdownlint-cli2": "^0.17.1", + "neostandard": "^0.12.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", "simple-get": "^4.0.1", "split2": "^4.2.0", "tap": "^21.0.0", "tsd": "^0.31.0", - "typescript": "~5.4.5", + "typescript": "~5.7.2", "undici": "^6.13.0", "vary": "^1.1.2", "yup": "^1.4.0" From 37c7ca89d88f55880e6fe23c6d5d8b26c048538f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Thu, 9 Jan 2025 09:42:41 +0100 Subject: [PATCH 0891/1295] fix: don't check for payload type in default json parser (#5933) * fix: don't check for buffer in default json parser * fix: don't check for buffer in default json parser * fix: don't check for buffer in default json parser --- lib/contentTypeParser.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 281cde71828..ec6ed1b9ca0 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -2,7 +2,7 @@ const { AsyncResource } = require('node:async_hooks') const { FifoMap: Fifo } = require('toad-cache') -const secureJson = require('secure-json-parse') +const { parse: secureJsonParse } = require('secure-json-parse') const { kDefaultJsonParse, kContentTypeParser, @@ -304,17 +304,15 @@ function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) { return defaultJsonParser function defaultJsonParser (req, body, done) { - if (body === '' || body == null || (Buffer.isBuffer(body) && body.length === 0)) { + if (body.length === 0) { return done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined) } - let json try { - json = secureJson.parse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning }) + done(null, secureJsonParse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning })) } catch (err) { err.statusCode = 400 - return done(err, undefined) + done(err, undefined) } - done(null, json) } } From 25fd197a5c8a122cf468ada8bd4873e3c1aa9225 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 10 Jan 2025 05:50:57 -0500 Subject: [PATCH 0892/1295] docs: Include req.hostname change in upgrade guide (#5935) * docs: Include req.hostname change in upgrade guide This wasn't included in the guide, but is a fairly important breaking change for applications that were using `req. hostname`. Signed-off-by: Tom MacWright Update Migration-Guide-V5.md Signed-off-by: Tom MacWright * Update Migration-Guide-V5.md Signed-off-by: Frazer Smith * Update Migration-Guide-V5.md Signed-off-by: Frazer Smith --------- Signed-off-by: Tom MacWright Signed-off-by: Frazer Smith Co-authored-by: Frazer Smith --- docs/Guides/Migration-Guide-V5.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index a288215e1c7..9c6884b4e37 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -524,6 +524,17 @@ fastify.register(function (instance, opts, done) { }); ``` +### Requests now have `host`, `hostname`, and `port`, and `hostname` no longer includes the port number + +In Fastify v4, `req.hostname` would include both the hostname and the +server’s port, so locally it might have the value `localhost:1234`. +With v5, we aligned to the Node.js URL object and now include `host`, `hostname`, +and `port` properties. `req.host` has the same value as `req.hostname` did in v4, +while `req.hostname` includes the hostname _without_ a port if a port is present, +and `req.port` contains just the port number. +See [#4766](https://github.com/fastify/fastify/pull/4766) +and [#4682](https://github.com/fastify/fastify/issues/4682) for more information. + ### Removes `getDefaultRoute` and `setDefaultRoute` methods The `getDefaultRoute` and `setDefaultRoute` methods have been removed in v5. From 951d50560561f959469e00eadccab7e5b7379d87 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Fri, 10 Jan 2025 16:35:40 +0000 Subject: [PATCH 0893/1295] build(dependabot): regroup dev dependencies (#5931) * build(dependabot): ungroup dev dependencies Signed-off-by: Frazer Smith * ci(dependabot): group dev-dependencies where necessary --------- Signed-off-by: Frazer Smith Co-authored-by: KaKa --- .github/dependabot.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7582bcdc022..0290237b7ab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -29,9 +29,23 @@ updates: dependency-type: "production" update-types: - "major" - # Development dependencies - dev-dependencies: - dependency-type: "development" + # ESLint related dependencies + dev-dependencies-eslint: + patterns: + - "eslint" + - "neostandard" + - "@stylistic/*" + # TypeScript related dependencies + dev-dependencies-typescript: + patterns: + - "@types/*" + - "tsd" + - "typescript" + # Ajv related dependencies + dev-dependencies-ajv: + patterns: + - "ajv" + - "ajv-*" ignore: - dependency-name: tap update-types: ["version-update:semver-major"] From fb8cf45d15753264329c3d232b05de81cbf5696c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:46:14 +0000 Subject: [PATCH 0894/1295] chore: Bump borp from 0.18.0 to 0.19.0 (#5936) Bumps [borp](https://github.com/mcollina/borp) from 0.18.0 to 0.19.0. - [Release notes](https://github.com/mcollina/borp/releases) - [Commits](https://github.com/mcollina/borp/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: borp dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f2099c9347..c3f0c685557 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "autocannon": "^8.0.0", - "borp": "^0.18.0", + "borp": "^0.19.0", "branch-comparer": "^1.1.0", "concurrently": "^9.1.2", "cross-env": "^7.0.3", From 677adbf0f3d027a4dff7091ed4b6575c00061757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sat, 11 Jan 2025 09:51:17 +0100 Subject: [PATCH 0895/1295] chore: don't return the `done` function (#5937) * chore: explicitly return undefined instead of the done function * remove unrelated change --- lib/contentTypeParser.js | 3 ++- lib/headRoute.js | 6 ++++-- lib/pluginUtils.js | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index ec6ed1b9ca0..e01a25899be 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -305,7 +305,8 @@ function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) { function defaultJsonParser (req, body, done) { if (body.length === 0) { - return done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined) + done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined) + return } try { done(null, secureJsonParse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning })) diff --git a/lib/headRoute.js b/lib/headRoute.js index 798624835ea..0cc4e5a4a56 100644 --- a/lib/headRoute.js +++ b/lib/headRoute.js @@ -3,7 +3,8 @@ function headRouteOnSendHandler (req, reply, payload, done) { // If payload is undefined if (payload === undefined) { reply.header('content-length', '0') - return done(null, null) + done(null, null) + return } if (typeof payload.resume === 'function') { @@ -11,7 +12,8 @@ function headRouteOnSendHandler (req, reply, payload, done) { reply.log.error({ err }, 'Error on Stream found for HEAD route') }) payload.resume() - return done(null, null) + done(null, null) + return } const size = '' + Buffer.byteLength(payload) diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index bac8664b4a1..8dc81184714 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -106,7 +106,7 @@ function _checkDecorators (that, instance, decorators, name) { function checkVersion (fn) { const meta = getMeta(fn) - if (meta == null || meta?.fastify == null) return + if (meta?.fastify == null) return const requiredVersion = meta.fastify From 45100c9c31ff4eb63e65de7c7ff228b7b6e873d5 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 11 Jan 2025 16:16:14 +0000 Subject: [PATCH 0896/1295] ci(workflows): unpin node 22 version (#5941) --- .github/workflows/ci.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd18425aa84..99602d1bfac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,7 +113,7 @@ jobs: strategy: matrix: - node-version: [20, 22.11] + node-version: [20, 22] os: [macos-latest, ubuntu-latest, windows-latest] steps: diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 9af08e4ea85..df96691ea1d 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 22.11 + node-version: 'lts/*' cache: 'npm' cache-dependency-path: package.json check-latest: true From 24c4da28e61c286609c2cc4d2f48dcf0cf9efd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sat, 11 Jan 2025 23:10:01 +0100 Subject: [PATCH 0897/1295] perf: don't use optional chaining for typeof .then checks (#5942) --- lib/contentTypeParser.js | 2 +- lib/validation.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index e01a25899be..57c29844ca2 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -197,7 +197,7 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply } else { const result = parser.fn(request, request[kRequestPayloadStream], done) - if (typeof result?.then === 'function') { + if (result && typeof result.then === 'function') { result.then(body => done(null, body), done) } } diff --git a/lib/validation.js b/lib/validation.js index af9ea4bdb78..1e490cf137b 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -119,7 +119,7 @@ function validateParam (validatorFunction, request, paramName) { const isUndefined = request[paramName] === undefined const ret = validatorFunction && validatorFunction(isUndefined ? null : request[paramName]) - if (ret?.then) { + if (ret && typeof ret.then === 'function') { return ret .then((res) => { return answer(res) }) .catch(err => { return err }) // return as simple error (not throw) From c74c85c9a776dd471482c5ffd67ad050c6979b8d Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 14 Jan 2025 12:35:57 +0100 Subject: [PATCH 0898/1295] docs: no floating promise guide is not needed anymore (#5946) Signed-off-by: Matteo Collina --- docs/Reference/TypeScript.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 7b59ee594f2..1d84e6ccf03 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -659,23 +659,6 @@ However, there are a couple of suggestions to help improve this experience: - Make sure the `no-unused-vars` rule is enabled in [ESLint](https://eslint.org/docs/rules/no-unused-vars) and any imported plugin are actually being loaded. -- In case you've the `@typescript-eslint/no-floating-promises` enabled, -please double-check that your ESLint configuration includes a `allowForKnownSafePromises` -property as described on the [`typescript-eslint no-floating-promises allowForKnownSafePromises -documentation`](https://typescript-eslint.io/rules/no-floating-promises/#allowforknownsafepromises): -``` -{ - "rules": { - "@typescript-eslint/no-floating-promises": ["error", { - "allowForKnownSafePromises": [ - { "from": "package", "name": "FastifyInstance", "package": "fastify" }, - { "from": "package", "name": "FastifyReply", "package": "fastify" }, - { "from": "package", "name": "SafePromiseLike", "package": "fastify" }, - ] - }] - } -} -``` - Use a module such as [depcheck](https://www.npmjs.com/package/depcheck) or [npm-check](https://www.npmjs.com/package/npm-check) to verify plugin dependencies are being used somewhere in your project. From 9db7990935caafcc72fbc7c64bf92af152552686 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 14 Jan 2025 17:07:55 +0000 Subject: [PATCH 0899/1295] docs: grammar and spelling fixes (#5944) --- CONTRIBUTING.md | 40 ++++++++++++++++++++-------------------- PROJECT_CHARTER.md | 14 +++++++------- README.md | 44 +++++++++++++++++++++----------------------- 3 files changed, 48 insertions(+), 50 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6cfd85bbcff..6f71f53e595 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,28 +12,28 @@ details on contributing to this project. ### I want to be a collaborator! If you think you meet the above criteria and we have not invited you yet, we are -sorry! Feel free reach out to a [Lead +sorry! Feel free to reach out to a [Lead Maintainer](https://github.com/fastify/fastify#team) privately with a few links to your valuable contributions. Read the [GOVERNANCE](GOVERNANCE.md) to get more information. ## Rules -There are a few basic ground-rules for contributors: +There are a few basic ground rules for contributors: 1. **No `--force` pushes** on `main` or modifying the Git history in any way after a PR has been merged. 1. **Non-main branches** ought to be used for ongoing work. 1. **External API changes and significant modifications** ought to be subject to - an **internal pull-request** to solicit feedback from other contributors. -1. Internal pull-requests to solicit feedback are *encouraged* for any other - non-trivial contribution but left to the discretion of the contributor. -1. Contributors should attempt to adhere to the prevailing code-style. -1. At least two contributors, or one core member, must approve pull-requests - prior to merging. -1. All integrated CI services must be green before a pull-request can be merged. + an **internal pull request** to solicit feedback from other contributors. +1. Internal pull requests to solicit feedback are *encouraged* for any other + non-trivial contribution but are left to the discretion of the contributor. +1. Contributors should attempt to adhere to the prevailing code style. +1. At least two contributors, or one core member, must approve pull requests + before merging. +1. All integrated CI services must be green before a pull request can be merged. 1. A lead maintainer must merge SemVer-major changes in this repository. -1. In case it is not possible to reach consensus in a pull-request, the decision +1. If it is not possible to reach a consensus in a pull request, the decision is left to the lead maintainer's team. ### Fastify previous versions @@ -42,7 +42,7 @@ Every Fastify's version is on its own branch. All Fastify related changes should be based on the corresponding branch. We have a [Long Term Support](./docs/Reference/LTS.md) policy that defines -the organization efforts for each Fastify's version. +the organization's efforts for each Fastify's version. |Version|Branch| |-------|------| @@ -64,17 +64,17 @@ Fastify repository with a few adjustments: 1. Any member can publish a release. 1. The plugin version must follow the [semver](https://semver.org/) specification. -1. The Node.js compatibility must match with the Fastify's main branch. +1. The Node.js compatibility must match with Fastify's main branch. 1. The new release must have the changelog information stored in the GitHub - release. For this scope we suggest to adopt a tool like + release. For this we suggest adopting a tool like [`releasify`](https://github.com/fastify/releasify) to archive this. 1. PR opened by bots (like Dependabot) can be merged if the CI is green and the - Node.js versions supported are the same of the plugin. + Node.js versions supported are the same as the plugin. ## Changes to this arrangement This is an experiment and feedback is welcome! This document may also be subject -to pull-requests or changes by contributors where you believe you have something +to pull requests or changes by contributors where you believe you have something valuable to add or change. # Fastify Organization Structure @@ -91,8 +91,8 @@ the following tasks: - [NPM 2FA](https://docs.npmjs.com/about-two-factor-authentication) 2. Choose which team to join *(more than one is ok!)* based on how you want to help. - - Core team: maintains the Fastify core and its documentation - - Plugins team: maintains the Fastify's plugins and its ecosystem + - Core team: maintains core Fastify and its documentation + - Plugins team: maintains Fastify's plugins and its ecosystem 3. Open a pull request to [`fastify/fastify:HEAD`](https://github.com/fastify/fastify/pulls) that adds your name, username, and email to the team you have chosen in the @@ -106,9 +106,9 @@ the following tasks: file. This list is also sorted alphabetically so make sure to add your name in the proper order. Use your GitHub profile icon for the `picture:` field. 5. Read the [pinned announcements](https://github.com/orgs/fastify/discussions/categories/announcements) - to be updated with the organisation’s news. + to be updated with the organization’s news. 6. The person that does the onboarding must add you to the [npm - org](https://www.npmjs.com/org/fastify), so that you can help maintaining the + org](https://www.npmjs.com/org/fastify), so that you can help maintain the official plugins. 7. Optionally, the person can be added as an Open Collective member by the lead team. @@ -130,7 +130,7 @@ person that did the onboarding must: file. The person that did the onboarding must: -1. If the collaborator doesn't reply to the ping in reasonable time, open the +1. If the collaborator does not reply to the ping in a reasonable time, open the pull requests described above. 2. Remove the collaborator from the Fastify teams on GitHub. 3. Remove the collaborator from the [npm diff --git a/PROJECT_CHARTER.md b/PROJECT_CHARTER.md index 79262c8d1d7..a841a165ba1 100644 --- a/PROJECT_CHARTER.md +++ b/PROJECT_CHARTER.md @@ -1,6 +1,6 @@ # Fastify Charter -The Fastify project aims to build a fast and low overhead web framework for +The Fastify project aims to build a fast and low-overhead web framework for Node.js. @@ -20,7 +20,7 @@ experience with the least overhead and a plugin architecture. ### 1.1: In-scope + Develop a web framework for Node.js with a focus on developer experience, - performance and extensibility. + performance, and extensibility. + Plugin Architecture + Support web protocols + Official plugins for common user requirements @@ -43,7 +43,7 @@ experience with the least overhead and a plugin architecture. + Support versions of Node.js at EOL (end of life) stage + Support serverless architecture -+ Contributions that violates the [Code of Conduct](CODE_OF_CONDUCT.md) ++ Contributions that violate the [Code of Conduct](CODE_OF_CONDUCT.md) ## Section 2: Relationship with OpenJS Foundation CPC. @@ -58,10 +58,10 @@ the Board of Directors (the "Board"). This Fastify Charter reflects a carefully constructed balanced role for the Collaborators and the CPC in the governance of the OpenJS Foundation. The charter amendment process is for the Fastify Collaborators to propose change -using simple majority of the full Fastify Organization, the proposed changes +using a majority of the full Fastify Organization, the proposed changes being subject to review and approval by the CPC. The CPC may additionally make amendments to the Collaborators charter at any time, though the CPC will not -interfere with day-to-day discussions, votes or meetings of the Fastify +interfere with day-to-day discussions, votes, or meetings of the Fastify Organization. @@ -92,7 +92,7 @@ Section Intentionally Left Blank Fastify's features can be discussed in GitHub issues and/or projects. Consensus on a discussion is reached when there is no objection by any collaborators. -Whenever there is not consensus, Lead Maintainers will have final say on the +When there is no consensus, Lead Maintainers will have the final say on the topic. **Voting, and/or Elections** @@ -112,7 +112,7 @@ Section Intentionally Left Blank Foundation Board. + *Collaborators*: contribute code and other artifacts, have the right to commit - to the code base and release plugins projects. Collaborators follow the + to the code base, and release plugin projects. Collaborators follow the [CONTRIBUTING](CONTRIBUTING.md) guidelines to manage the project. A Collaborator could be encumbered by other Fastify Collaborators and never by the CPC or OpenJS Foundation Board. diff --git a/README.md b/README.md index f9bd4a2fab4..8760df156ce 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,10 @@ Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yello
-An efficient server implies a lower cost of the infrastructure, a better -responsiveness under load and happy users. How can you efficiently handle the +An efficient server implies a lower cost of the infrastructure, better +responsiveness under load, and happy users. How can you efficiently handle the resources of your server, knowing that you are serving the highest number of -requests as possible, without sacrificing security validations and handy +requests possible, without sacrificing security validations and handy development? Enter Fastify. Fastify is a web framework highly focused on providing the best @@ -46,7 +46,7 @@ developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town. -The `main` branch refers to the Fastify `v5` release, which is not released/LTS yet. +The `main` branch refers to the Fastify `v5` release. Check out the [`4.x` branch](https://github.com/fastify/fastify/tree/4.x) for `v4`. ### Table of Contents @@ -138,7 +138,7 @@ fastify.listen({ port: 3000 }, (err, address) => { }) ``` -with async-await: +With async-await: ```js // ESM @@ -171,7 +171,7 @@ href="./docs/Guides/Getting-Started.md">Getting Started
. > (`127.0.0.1` or `::1`, depending on the operating system configuration). If > you are running Fastify in a container (Docker, > [GCP](https://cloud.google.com/), etc.), you may need to bind to `0.0.0.0`. Be -> careful when deciding to listen on all interfaces; it comes with inherent +> careful when listening on all interfaces; it comes with inherent > [security > risks](https://web.archive.org/web/20170711105010/https://snyk.io/blog/mongodb-hack-and-secure-defaults/). > See [the documentation](./docs/Reference/Server.md#listen) for more @@ -182,16 +182,16 @@ href="./docs/Guides/Getting-Started.md">Getting Started. - **Highly performant:** as far as we know, Fastify is one of the fastest web frameworks in town, depending on the code complexity we can serve up to 76+ thousand requests per second. -- **Extensible:** Fastify is fully extensible via its hooks, plugins and +- **Extensible:** Fastify is fully extensible via its hooks, plugins, and decorators. -- **Schema based:** even if it is not mandatory we recommend to use [JSON +- **Schema-based:** even if it is not mandatory we recommend using [JSON Schema](https://json-schema.org/) to validate your routes and serialize your - outputs, internally Fastify compiles the schema in a highly performant + outputs. Internally Fastify compiles the schema in a highly performant function. - **Logging:** logs are extremely important but are costly; we chose the best logger to almost remove this cost, [Pino](https://github.com/pinojs/pino)! - **Developer friendly:** the framework is built to be very expressive and help - the developer in their daily use, without sacrificing performance and + developers in their daily use without sacrificing performance and security. ### Benchmarks @@ -211,10 +211,10 @@ second average | - | | | | | `http.Server` | 16.14.2 | ✗ | 74,513 | -Benchmarks taken using https://github.com/fastify/benchmarks. This is a -synthetic, "hello world" benchmark that aims to evaluate the framework overhead. +These benchmarks taken using https://github.com/fastify/benchmarks. This is a +synthetic "hello world" benchmark that aims to evaluate the framework overhead. The overhead that each framework has on your application depends on your -application, you should __always__ benchmark if performance matters to you. +application. You should __always__ benchmark if performance matters to you. ## Documentation * [__`Getting Started`__](./docs/Guides/Getting-Started.md) @@ -244,13 +244,11 @@ application, you should __always__ benchmark if performance matters to you. * [__`Serverless`__](./docs/Guides/Serverless.md) * [__`Recommendations`__](./docs/Guides/Recommendations.md) -中文文档[地址](https://github.com/fastify/docs-chinese/blob/HEAD/README.md) - ## Ecosystem - [Core](./docs/Guides/Ecosystem.md#core) - Core plugins maintained by the _Fastify_ [team](#team). -- [Community](./docs/Guides/Ecosystem.md#community) - Community supported +- [Community](./docs/Guides/Ecosystem.md#community) - Community-supported plugins. - [Live Examples](https://github.com/fastify/example) - Multirepo with a broad set of real working examples. @@ -270,7 +268,7 @@ Fastify's supported version matrix is available in the ## Contributing -Whether reporting bugs, discussing improvements and new ideas or writing code, +Whether reporting bugs, discussing improvements and new ideas, or writing code, we welcome contributions from anyone and everyone. Please read the [CONTRIBUTING](./CONTRIBUTING.md) guidelines before submitting pull requests. @@ -345,9 +343,9 @@ listed in alphabetical order. , ### Emeritus Contributors -Great contributors on a specific area in the Fastify ecosystem will be invited +Great contributors to a specific area of the Fastify ecosystem will be invited to join this group by Lead Maintainers when they decide to step down from the -active contributors group. +active contributor's group. * [__Tommaso Allevi__](https://github.com/allevo), , @@ -383,7 +381,7 @@ active contributors group. src="https://github.com/openjs-foundation/artwork/blob/main/openjs_foundation/openjs_foundation-logo-horizontal-color.png?raw=true" width="250px;"/>](https://openjsf.org/projects) -We are a [At-Large +We are an [At-Large Project](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/PROJECT_PROGRESSION.md#at-large-projects) in the [OpenJS Foundation](https://openjsf.org/). @@ -393,7 +391,7 @@ Support this project by becoming a [SPONSOR](./SPONSORS.md)! Fastify has an [Open Collective](https://opencollective.com/fastify) page where we accept and manage financial contributions. -## Acknowledgements +## Acknowledgments This project is kindly sponsored by: - [NearForm](https://nearform.com) @@ -402,8 +400,8 @@ This project is kindly sponsored by: Past Sponsors: - [LetzDoIt](https://www.letzdoitapp.com/) -This list includes all companies that support one or more of the team members -in the maintenance of this project. +This list includes all companies that support one or more team members +in maintaining this project. ## License From 4467cfcd96db8f84a5d2f64bfad8219a208ab0a2 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 14 Jan 2025 17:09:09 +0000 Subject: [PATCH 0900/1295] perf(lib/pluginutils): cache rc version regex (#5940) --- lib/pluginUtils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index 8dc81184714..577296904fb 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -13,6 +13,8 @@ const { FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER } = require('./errors') +const rcRegex = /-(?:rc|pre|alpha).+$/u + function getMeta (fn) { return fn[Symbol.for('plugin-meta')] } @@ -110,7 +112,7 @@ function checkVersion (fn) { const requiredVersion = meta.fastify - const fastifyRc = /-(?:rc|pre|alpha).+$/.test(this.version) + const fastifyRc = rcRegex.test(this.version) if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) { // A Fastify release candidate phase is taking place. In order to reduce // the effort needed to test plugins with the RC, we allow plugins targeting From 0367c8c919f7abd8d8b3cda479d9853313f02534 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 14 Jan 2025 17:09:35 +0000 Subject: [PATCH 0901/1295] build(dependabot): reduce npm updates to monthly (#5939) --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0290237b7ab..72a0d6f1967 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,7 +15,7 @@ updates: # Prefix all commit messages with "chore: " prefix: "chore" schedule: - interval: "weekly" + interval: "monthly" open-pull-requests-limit: 10 groups: # Production dependencies without breaking changes From 9f33293503e74c54b098fda50453aeaceff2bbc9 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 15 Jan 2025 08:46:26 +0000 Subject: [PATCH 0902/1295] docs(guides): grammar and spelling fixes (#5947) * docs(guides): grammar and spelling fixes * docs(guides): stop note being treated as header --- docs/Guides/Benchmarking.md | 8 ++++---- docs/Guides/Contributing.md | 11 ++++++----- docs/Guides/Database.md | 2 +- docs/Guides/Delay-Accepting-Requests.md | 20 ++++++++++---------- docs/Guides/Fluent-Schema.md | 2 +- docs/Guides/Getting-Started.md | 6 +++--- docs/Guides/Index.md | 2 +- docs/Guides/Migration-Guide-V4.md | 2 +- docs/Guides/Plugins-Guide.md | 12 ++++++------ docs/Guides/Serverless.md | 8 ++++---- docs/Guides/Style-Guide.md | 4 ++-- docs/Guides/Write-Plugin.md | 5 ++--- 12 files changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/Guides/Benchmarking.md b/docs/Guides/Benchmarking.md index 5e2e990ba95..9bdcf9c03ca 100644 --- a/docs/Guides/Benchmarking.md +++ b/docs/Guides/Benchmarking.md @@ -1,17 +1,17 @@

Fastify

## Benchmarking -Benchmarking is important if you want to measure how a change can affect the -performance of your application. We provide a simple way to benchmark your +Benchmarking is important if you want to measure how a change can affect your +application's performance. We provide a simple way to benchmark your application from the point of view of a user and contributor. The setup allows you to automate benchmarks in different branches and on different Node.js versions. The modules we will use: -- [Autocannon](https://github.com/mcollina/autocannon): A HTTP/1.1 benchmarking +- [Autocannon](https://github.com/mcollina/autocannon): An HTTP/1.1 benchmarking tool written in node. - [Branch-comparer](https://github.com/StarpTech/branch-comparer): Checkout - multiple git branches, execute scripts and log the results. + multiple git branches, execute scripts, and log the results. - [Concurrently](https://github.com/kimmobrunfeldt/concurrently): Run commands concurrently. - [Npx](https://github.com/npm/npx): NPM package runner used to run scripts diff --git a/docs/Guides/Contributing.md b/docs/Guides/Contributing.md index 334e9f990e9..081823ff615 100644 --- a/docs/Guides/Contributing.md +++ b/docs/Guides/Contributing.md @@ -6,9 +6,10 @@ receive your support and knowledge. This guide is our attempt to help you help us. > ## Note -> This is an informal guide. Please review the formal [CONTRIBUTING -> document](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md) for -> full details and our [Developer Certificate of +> This is an informal guide. For full details, please review the formal +> [CONTRIBUTING +> document](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md) +> our [Developer Certificate of > Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin). ## Table Of Contents @@ -50,7 +51,7 @@ should expect from others): * We have a [Code of Conduct](https://github.com/fastify/fastify/blob/main/CODE_OF_CONDUCT.md). You must adhere to it to participate in this project. -* If you open a pull request, please ensure that your contribution passes all +* If you open a pull request, please ensure your contribution passes all tests. If there are test failures, you will need to address them before we can merge your contribution. @@ -79,7 +80,7 @@ https://github.com/github/opensource.guide/blob/2868efbf0c14aec821909c19e210c360 Please adhere to the project's code and documentation style. Some popular tools that automatically "correct" code and documentation do not follow a style that -conforms to the styles this project uses. Notably, this project uses +conforms to this project's styles. Notably, this project uses [StandardJS](https://standardjs.com) for code formatting. [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/fastify/fastify) diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md index 72449133f16..f63b7bdfb84 100644 --- a/docs/Guides/Database.md +++ b/docs/Guides/Database.md @@ -245,7 +245,7 @@ for Postgres, MySQL, SQL Server and SQLite. For MongoDB migrations, please check #### [Postgrator](https://www.npmjs.com/package/postgrator) Postgrator is Node.js SQL migration tool that uses a directory of SQL scripts to -alter the database schema. Each file in a migrations folder need to follow the +alter the database schema. Each file in a migrations folder needs to follow the pattern: ` [version].[action].[optional-description].sql`. **version:** must be an incrementing number (e.g. `001` or a timestamp). diff --git a/docs/Guides/Delay-Accepting-Requests.md b/docs/Guides/Delay-Accepting-Requests.md index 2ab6eabf579..acf36d84f4b 100644 --- a/docs/Guides/Delay-Accepting-Requests.md +++ b/docs/Guides/Delay-Accepting-Requests.md @@ -77,8 +77,8 @@ server.get('/ping', function (request, reply) { }) server.post('/webhook', function (request, reply) { - // It's good practice to validate webhook requests really come from - // whoever you expect. This is skipped in this sample for the sake + // It's good practice to validate webhook requests come from + // who you expect. This is skipped in this sample for the sake // of simplicity const { magicKey } = request.body @@ -448,10 +448,10 @@ have the possibility of giving the customer meaningful information, like how long they should wait before retrying the request. Going even further, by issuing a [`503` status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) we're -signaling to our infrastructure components (namely load balancers) we're still -not ready to take incoming requests and they should redirect traffic to other -instances, if available, besides in how long we estimate that will be solved. -All of that in a few simple lines! +signaling to our infrastructure components (namely load balancers) that we're +still not ready to take incoming requests and they should redirect traffic to +other instances, if available. Additionally, we are providing a `Retry-After` +header with the time in milliseconds the client should wait before retrying. It's noteworthy that we didn't use the `fastify-plugin` wrapper in the `delay` factory. That's because we wanted the `onRequest` hook to only be set within @@ -524,14 +524,14 @@ Retry-After: 5000 } ``` -Then we attempt a new request (`req-2`), which was a `GET /ping`. As expected, +Then we attempted a new request (`req-2`), which was a `GET /ping`. As expected, since that was not one of the requests we asked our plugin to filter, it -succeeded. That could also be used as means of informing an interested party +succeeded. That could also be used as a means of informing an interested party whether or not we were ready to serve requests (although `/ping` is more commonly associated with *liveness* checks and that would be the responsibility of a *readiness* check -- the curious reader can get more info on these terms [here](https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes)) -with the `ready` field. Below is the response for that request: +with the `ready` field. Below is the response to that request: ```sh HTTP/1.1 200 OK @@ -547,7 +547,7 @@ Keep-Alive: timeout=5 } ``` -After that there were more interesting log messages: +After that, there were more interesting log messages: ```sh diff --git a/docs/Guides/Fluent-Schema.md b/docs/Guides/Fluent-Schema.md index b3463bac2c6..653f6e3df8d 100644 --- a/docs/Guides/Fluent-Schema.md +++ b/docs/Guides/Fluent-Schema.md @@ -55,7 +55,7 @@ fastify.post('/the/url', { schema }, handler) ### Reuse -With `fluent-json-schema` you can manipulate your schemas more easily and +With `fluent-json-schema`, you can manipulate your schemas more easily and programmatically and then reuse them thanks to the `addSchema()` method. You can refer to the schema in two different manners that are detailed in the [Validation and diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index a4d68029a15..5cb9e80e5ee 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -106,7 +106,7 @@ of your code. Fastify offers an easy platform that helps to solve all of the problems outlined above, and more! -> ## Note +> **Note** > The above examples, and subsequent examples in this document, default to > listening *only* on the localhost `127.0.0.1` interface. To listen on all > available IPv4 interfaces the example should be modified to listen on @@ -417,7 +417,7 @@ In this way, you will always have access to all of the properties declared in the current scope. As discussed previously, Fastify offers a solid encapsulation model, to help you -build your application as single and independent services. If you want to +build your application as independent services. If you want to register a plugin only for a subset of routes, you just have to replicate the above structure. ``` @@ -552,7 +552,7 @@ an amazing [ecosystem](./Ecosystem.md)! Fastify does not offer a testing framework, but we do recommend a way to write -your tests that use the features and architecture of Fastify. +your tests that uses the features and architecture of Fastify. Read the [testing](./Testing.md) documentation to learn more! diff --git a/docs/Guides/Index.md b/docs/Guides/Index.md index a26fac6fecb..1220b0c0f4a 100644 --- a/docs/Guides/Index.md +++ b/docs/Guides/Index.md @@ -19,7 +19,7 @@ This table of contents is in alphabetical order. practical guide on detecting if and when a client aborts a request. + [Ecosystem](./Ecosystem.md): Lists all core plugins and many known community plugins. -+ [Fluent Schema](./Fluent-Schema.md): Shows how writing JSON Schema can be ++ [Fluent Schema](./Fluent-Schema.md): Shows how JSON Schema can be written with a fluent API and used in Fastify. + [Getting Started](./Getting-Started.md): Introduction tutorial for Fastify. This is where beginners should start. diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 0049f439544..4e66020727f 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -210,7 +210,7 @@ fastify.get('/posts/:id?', (request, reply) => { The [variadic signature](https://en.wikipedia.org/wiki/Variadic_function) of the `fastify.listen()` method is now deprecated. -Prior to this release, the following invocations of this method were valid: +Before this release, the following invocations of this method were valid: - `fastify.listen(8000)` - `fastify.listen(8000, ‘127.0.0.1’)` diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index 4fc8821b3ae..2732756c29e 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -71,8 +71,8 @@ order of plugins. *How?* Glad you asked, check out [`avvio`](https://github.com/mcollina/avvio)! Fastify starts loading the plugin __after__ `.listen()`, `.inject()` or `.ready()` are called. -Inside a plugin you can do whatever you want, register routes, utilities (we -will see this in a moment) and do nested registers, just remember to call `done` +Inside a plugin you can do whatever you want, register routes and utilities (we +will see this in a moment), and do nested registers, just remember to call `done` when everything is set up! ```js module.exports = function (fastify, options, done) { @@ -117,7 +117,7 @@ Now you can access your utility just by calling `fastify.util` whenever you need it - even inside your test. And here starts the magic; do you remember how just now we were talking about -encapsulation? Well, using `register` and `decorate` in conjunction enable +encapsulation? Well, using `register` and `decorate` in conjunction enables exactly that, let me show you an example to clarify this: ```js fastify.register((instance, opts, done) => { @@ -137,7 +137,7 @@ Inside the second register call `instance.util` will throw an error because `util` exists only inside the first register context. Let's step back for a moment and dig deeper into this: every time you use the -`register` API, a new context is created which avoids the negative situations +`register` API, a new context is created that avoids the negative situations mentioned above. Do note that encapsulation applies to the ancestors and siblings, but not the @@ -202,7 +202,7 @@ a utility that also needs access to the `request` and `reply` instance, a function that is defined using the `function` keyword is needed instead of an *arrow function expression*. -In the same way you can do this for the `request` object: +You can do the same for the `request` object: ```js fastify.decorate('getHeader', (req, header) => { return req.headers[header] @@ -395,7 +395,7 @@ As we mentioned earlier, Fastify starts loading its plugins __after__ have been declared. This means that, even though the plugin may inject variables to the external Fastify instance via [`decorate`](../Reference/Decorators.md), the decorated variables will not be accessible before calling `.listen()`, -`.inject()` or `.ready()`. +`.inject()`, or `.ready()`. In case you rely on a variable injected by a preceding plugin and want to pass that in the `options` argument of `register`, you can do so by using a function diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 454d77bbae7..8bdf60fea02 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -280,8 +280,8 @@ const { onRequest } = require("firebase-functions/v2/https") ### Creation of Fastify instance Create the Fastify instance and encapsulate the returned application instance -in a function which will register routes, await the server's processing of -plugins, hooks and other settings. As follows: +in a function that will register routes, await the server's processing of +plugins, hooks, and other settings. As follows: ```js const fastify = require("fastify")({ @@ -299,7 +299,7 @@ const fastifyApp = async (request, reply) => { Firebase Function's HTTP layer already parses the request and makes a JSON payload available. It also provides access -to the raw body, unparsed, which is useful in order to calculate +to the raw body, unparsed, which is useful for calculating request signatures to validate HTTP webhooks. Add as follows to the `registerRoutes()` function: @@ -384,7 +384,7 @@ familiar with gcloud or just follow their ### Adjust Fastify server -In order for Fastify to properly listen for requests within the container, be +For Fastify to properly listen for requests within the container, be sure to set the correct port and address: ```js diff --git a/docs/Guides/Style-Guide.md b/docs/Guides/Style-Guide.md index 2b811624ddb..68fc7639033 100644 --- a/docs/Guides/Style-Guide.md +++ b/docs/Guides/Style-Guide.md @@ -83,7 +83,7 @@ Result: Make sure you avoid copying other people's work. Keep it as original as possible. You can learn from what they have done and reference where it is from -if you used a particular quote from their work. +if you use a particular quote from their work. ## Word Choice @@ -217,7 +217,7 @@ Styles](https://medium.com/better-programming/string-case-styles-camel-pascal-sn ### Hyperlinks -Hyperlinks should have a clear title of what it references. Here is how your +Hyperlinks should have a clear title of what they reference. Here is how your hyperlink should look: ```MD diff --git a/docs/Guides/Write-Plugin.md b/docs/Guides/Write-Plugin.md index 00eee854673..78e1225844b 100644 --- a/docs/Guides/Write-Plugin.md +++ b/docs/Guides/Write-Plugin.md @@ -14,7 +14,7 @@ suggestion"](https://github.com/fastify/fastify/issues?q=is%3Aissue+is%3Aopen+la in our issue tracker!* ## Code -Fastify uses different techniques to optimize its code, many of them are +Fastify uses different techniques to optimize its code, many of which are documented in our Guides. We highly recommend you read [the hitchhiker's guide to plugins](./Plugins-Guide.md) to discover all the APIs you can use to build your plugin and learn how to use them. @@ -53,8 +53,7 @@ Always put an example file in your repository. Examples are very helpful for users and give a very fast way to test your plugin. Your users will be grateful. ## Test -It is extremely important that a plugin is thoroughly tested to verify that is -working properly. +A plugin **must** be thoroughly tested to verify that is working properly. A plugin without tests will not be accepted to the ecosystem list. A lack of tests does not inspire trust nor guarantee that the code will continue to work From 446e2be302798df0c968ae3d7ac162e97a09cb25 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Wed, 15 Jan 2025 15:47:53 +0100 Subject: [PATCH 0903/1295] test: migrated genReqId.test.js from tap to node:test (#5943) --- test/genReqId.test.js | 299 ++++++++++++++++++------------------------ 1 file changed, 125 insertions(+), 174 deletions(-) diff --git a/test/genReqId.test.js b/test/genReqId.test.js index f23be410d04..682935ba4ac 100644 --- a/test/genReqId.test.js +++ b/test/genReqId.test.js @@ -1,11 +1,11 @@ 'use strict' const { Readable } = require('node:stream') -const { test } = require('tap') +const { test } = require('node:test') const fp = require('fastify-plugin') const Fastify = require('..') -test('Should accept a custom genReqId function', t => { +test('Should accept a custom genReqId function', (t, done) => { t.plan(4) const fastify = Fastify({ @@ -14,62 +14,64 @@ test('Should accept a custom genReqId function', t => { } }) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + url: `http://localhost:${fastify.server.address().port}` }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'a') - fastify.close() + t.assert.strictEqual(payload.id, 'a') + done() }) }) }) -test('Custom genReqId function gets raw request as argument', t => { +test('Custom genReqId function gets raw request as argument', (t, done) => { t.plan(9) const REQUEST_ID = 'REQ-1234' const fastify = Fastify({ genReqId: function (req) { - t.notOk('id' in req) - t.notOk('raw' in req) - t.ok(req instanceof Readable) + t.assert.strictEqual('id' in req, false) + t.assert.strictEqual('raw' in req, false) + t.assert.ok(req instanceof Readable) // http.IncomingMessage does have `rawHeaders` property, but FastifyRequest does not const index = req.rawHeaders.indexOf('x-request-id') const xReqId = req.rawHeaders[index + 1] - t.equal(xReqId, REQUEST_ID) - t.equal(req.headers['x-request-id'], REQUEST_ID) + t.assert.strictEqual(xReqId, REQUEST_ID) + t.assert.strictEqual(req.headers['x-request-id'], REQUEST_ID) return xReqId } }) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) reply.send({ id: req.id }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', headers: { 'x-request-id': REQUEST_ID }, - url: 'http://localhost:' + fastify.server.address().port + url: `http://localhost:${fastify.server.address().port}` }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, REQUEST_ID) - fastify.close() + t.assert.strictEqual(payload.id, REQUEST_ID) + done() }) }) }) @@ -77,13 +79,13 @@ test('Custom genReqId function gets raw request as argument', t => { test('Should handle properly requestIdHeader option', t => { t.plan(4) - t.equal(Fastify({ requestIdHeader: '' }).initialConfig.requestIdHeader, false) - t.equal(Fastify({ requestIdHeader: false }).initialConfig.requestIdHeader, false) - t.equal(Fastify({ requestIdHeader: true }).initialConfig.requestIdHeader, 'request-id') - t.equal(Fastify({ requestIdHeader: 'x-request-id' }).initialConfig.requestIdHeader, 'x-request-id') + t.assert.strictEqual(Fastify({ requestIdHeader: '' }).initialConfig.requestIdHeader, false) + t.assert.strictEqual(Fastify({ requestIdHeader: false }).initialConfig.requestIdHeader, false) + t.assert.strictEqual(Fastify({ requestIdHeader: true }).initialConfig.requestIdHeader, 'request-id') + t.assert.strictEqual(Fastify({ requestIdHeader: 'x-request-id' }).initialConfig.requestIdHeader, 'x-request-id') }) -test('Should accept option to set genReqId with setGenReqId option', t => { +test('Should accept option to set genReqId with setGenReqId option', (t, done) => { t.plan(9) const fastify = Fastify({ @@ -92,12 +94,14 @@ test('Should accept option to set genReqId with setGenReqId option', t => { } }) + t.after(() => fastify.close()) + fastify.register(function (instance, opts, next) { instance.setGenReqId(function (req) { return 'foo' }) instance.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) next() @@ -108,49 +112,57 @@ test('Should accept option to set genReqId with setGenReqId option', t => { return 'bar' }) instance.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) next() }, { prefix: 'bar' }) fastify.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) + let pending = 3 + + function completed () { + if (--pending === 0) { + done() + } + } + fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'base') - fastify.close() + t.assert.strictEqual(payload.id, 'base') + completed() }) fastify.inject({ method: 'GET', url: '/foo' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - fastify.close() + t.assert.strictEqual(payload.id, 'foo') + completed() }) fastify.inject({ method: 'GET', url: '/bar' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'bar') - fastify.close() + t.assert.strictEqual(payload.id, 'bar') + completed() }) }) -test('Should encapsulate setGenReqId', t => { +test('Should encapsulate setGenReqId', (t, done) => { t.plan(12) const fastify = Fastify({ @@ -159,6 +171,7 @@ test('Should encapsulate setGenReqId', t => { } }) + t.after(() => fastify.close()) const bazInstance = function (instance, opts, next) { instance.register(barInstance, { prefix: 'baz' }) @@ -166,7 +179,7 @@ test('Should encapsulate setGenReqId', t => { return 'baz' }) instance.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) next() @@ -177,7 +190,7 @@ test('Should encapsulate setGenReqId', t => { return 'bar' }) instance.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) next() @@ -192,7 +205,7 @@ test('Should encapsulate setGenReqId', t => { }) instance.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) next() @@ -201,159 +214,71 @@ test('Should encapsulate setGenReqId', t => { fastify.register(fooInstance, { prefix: 'foo' }) fastify.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, 'base') - fastify.close() - }) - - fastify.inject({ - method: 'GET', - url: '/foo' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - fastify.close() - }) + let pending = 4 - fastify.inject({ - method: 'GET', - url: '/foo/bar' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, 'bar') - fastify.close() - }) - - fastify.inject({ - method: 'GET', - url: '/foo/baz' - }, (err, res) => { - t.error(err) - const payload = JSON.parse(res.payload) - t.equal(payload.id, 'baz') - fastify.close() - }) -}) - -test('Should encapsulate setGenReqId', t => { - t.plan(12) - - const fastify = Fastify({ - genReqId: function (req) { - return 'base' + function completed () { + if (--pending === 0) { + done() } - }) - - const bazInstance = function (instance, opts, next) { - instance.register(barInstance, { prefix: 'baz' }) - - instance.setGenReqId(function (req) { - return 'baz' - }) - instance.get('/', (req, reply) => { - t.ok(req.id) - reply.send({ id: req.id }) - }) - next() - } - - const barInstance = function (instance, opts, next) { - instance.setGenReqId(function (req) { - return 'bar' - }) - instance.get('/', (req, reply) => { - t.ok(req.id) - reply.send({ id: req.id }) - }) - next() - } - - const fooInstance = function (instance, opts, next) { - instance.register(bazInstance, { prefix: 'baz' }) - instance.register(barInstance, { prefix: 'bar' }) - - instance.setGenReqId(function (req) { - return 'foo' - }) - - instance.get('/', (req, reply) => { - t.ok(req.id) - reply.send({ id: req.id }) - }) - next() } - fastify.register(fooInstance, { prefix: 'foo' }) - - fastify.get('/', (req, reply) => { - t.ok(req.id) - reply.send({ id: req.id }) - }) - fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'base') - fastify.close() + t.assert.strictEqual(payload.id, 'base') + completed() }) fastify.inject({ method: 'GET', url: '/foo' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - fastify.close() + t.assert.strictEqual(payload.id, 'foo') + completed() }) fastify.inject({ method: 'GET', url: '/foo/bar' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'bar') - fastify.close() + t.assert.strictEqual(payload.id, 'bar') + completed() }) fastify.inject({ method: 'GET', url: '/foo/baz' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'baz') - fastify.close() + t.assert.strictEqual(payload.id, 'baz') + completed() }) }) -test('Should not alter parent of genReqId', t => { +test('Should not alter parent of genReqId', (t, done) => { t.plan(6) const fastify = Fastify() - + t.after(() => fastify.close()) const fooInstance = function (instance, opts, next) { instance.setGenReqId(function (req) { return 'foo' }) instance.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) next() @@ -362,32 +287,40 @@ test('Should not alter parent of genReqId', t => { fastify.register(fooInstance, { prefix: 'foo' }) fastify.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'req-1') - fastify.close() + t.assert.strictEqual(payload.id, 'req-1') + completed() }) fastify.inject({ method: 'GET', url: '/foo' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - fastify.close() + t.assert.strictEqual(payload.id, 'foo') + completed() }) }) -test('Should have child instance user parent genReqId', t => { +test('Should have child instance user parent genReqId', (t, done) => { t.plan(6) const fastify = Fastify({ @@ -395,10 +328,11 @@ test('Should have child instance user parent genReqId', t => { return 'foo' } }) + t.after(() => fastify.close()) const fooInstance = function (instance, opts, next) { instance.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) next() @@ -407,69 +341,86 @@ test('Should have child instance user parent genReqId', t => { fastify.register(fooInstance, { prefix: 'foo' }) fastify.get('/', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - fastify.close() + t.assert.strictEqual(payload.id, 'foo') + completed() }) fastify.inject({ method: 'GET', url: '/foo' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'foo') - fastify.close() + t.assert.strictEqual(payload.id, 'foo') + completed() }) }) -test('genReqId set on root scope when using fastify-plugin', t => { +test('genReqId set on root scope when using fastify-plugin', (t, done) => { t.plan(6) const fastify = Fastify() + t.after(() => fastify.close()) fastify.register(fp(function (fastify, options, done) { fastify.setGenReqId(function (req) { return 'not-encapsulated' }) fastify.get('/not-encapsulated-1', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) done() })) fastify.get('/not-encapsulated-2', (req, reply) => { - t.ok(req.id) + t.assert.ok(req.id) reply.send({ id: req.id }) }) + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + fastify.inject({ method: 'GET', url: '/not-encapsulated-1' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'not-encapsulated') - fastify.close() + t.assert.strictEqual(payload.id, 'not-encapsulated') + completed() }) fastify.inject({ method: 'GET', url: '/not-encapsulated-2' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(payload.id, 'not-encapsulated') - fastify.close() + t.assert.strictEqual(payload.id, 'not-encapsulated') + completed() }) }) From 45461d5f8ce2c4877d86df73ef5750323aab96d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 02:08:40 +0000 Subject: [PATCH 0904/1295] chore: Bump lycheeverse/lychee-action from 2.1.0 to 2.2.0 (#5948) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/f81112d0d2814ded911bd23e3beaa9dda9093915...f796c8b7d468feb9b8c0a46da3fac0af6874d374) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index b36aaf4030f..41c49209712 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@f81112d0d2814ded911bd23e3beaa9dda9093915 + uses: lycheeverse/lychee-action@f796c8b7d468feb9b8c0a46da3fac0af6874d374 with: fail: true # As external links behavior is not predictable, we check only internal links From 03c7e451f63768a13dff08aa1e6a9f789f2859a0 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 16 Jan 2025 11:59:32 +0000 Subject: [PATCH 0905/1295] docs(reference/contenttypeparser): make more concise (#5950) * docs(reference/contenttypeparser): make more concise * Update ContentTypeParser.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Frazer Smith * Update ContentTypeParser.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Frazer Smith * Update ContentTypeParser.md Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Frazer Smith * chore: lint --------- Signed-off-by: Frazer Smith Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> --- docs/Reference/ContentTypeParser.md | 133 ++++++++++++---------------- docs/Reference/Reply.md | 3 +- 2 files changed, 59 insertions(+), 77 deletions(-) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index c33c50d9df9..eb685c37298 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -1,38 +1,32 @@

Fastify

## `Content-Type` Parser -Natively, Fastify only supports `'application/json'` and `'text/plain'` content -types. If the content type is not one of these, an -`FST_ERR_CTP_INVALID_MEDIA_TYPE` error will be thrown. -Other common content types are supported through the use of -[plugins](https://fastify.dev/ecosystem/). +Fastify natively supports `'application/json'` and `'text/plain'` content types +with a default charset of `utf-8`. These default parsers can be changed or +removed. -The default charset is `utf-8`. If you need to support different content types, -you can use the `addContentTypeParser` API. *The default JSON and/or plain text -parser can be changed or removed.* +Unsupported content types will throw an `FST_ERR_CTP_INVALID_MEDIA_TYPE` error. -*Note: If you decide to specify your own content type with the `Content-Type` -header, UTF-8 will not be the default. Be sure to include UTF-8 like this -`text/html; charset=utf-8`.* +To support other content types, use the `addContentTypeParser` API or an +existing [plugin](https://fastify.dev/ecosystem/). -As with the other APIs, `addContentTypeParser` is encapsulated in the scope in -which it is declared. This means that if you declare it in the root scope it -will be available everywhere, while if you declare it inside a plugin it will be -available only in that scope and its children. +As with other APIs, `addContentTypeParser` is encapsulated in the scope in which +it is declared. If declared in the root scope, it is available everywhere; if +declared in a plugin, it is available only in that scope and its children. Fastify automatically adds the parsed request payload to the [Fastify -request](./Request.md) object which you can access with `request.body`. - -Note that for `GET` and `HEAD` requests the payload is never parsed. For -`OPTIONS` and `DELETE` requests the payload is only parsed if the content type -is given in the content-type header. If it is not given, the -[catch-all](#catch-all) parser is not executed as with `POST`, `PUT` and -`PATCH`, but the payload is simply not parsed. - -> ## ⚠ Security Notice -> When using with RegExp to detect `Content-Type`, you should beware of -> how to properly detect the `Content-Type`. For example, if you need -> `application/*`, you should use `/^application\/([\w-]+);?/` to match the +request](./Request.md) object, accessible via `request.body`. + +Note that for `GET` and `HEAD` requests, the payload is never parsed. For +`OPTIONS` and `DELETE` requests, the payload is parsed only if a valid +`content-type` header is provided. Unlike `POST`, `PUT`, and `PATCH`, the +[catch-all](#catch-all) parser is not executed, and the payload is simply not +parsed. + +> ## ⚠ Security Notice +> When using regular expressions to detect `Content-Type`, it is important to +> ensure proper detection. For example, to match `application/*`, use +> `/^application\/([\w-]+);?/` to match the > [essence MIME type](https://mimesniff.spec.whatwg.org/#mime-type-miscellaneous) > only. @@ -70,11 +64,10 @@ fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefa ``` Fastify first tries to match a content-type parser with a `string` value before -trying to find a matching `RegExp`. If you provide overlapping content types, -Fastify tries to find a matching content type by starting with the last one -passed and ending with the first one. So if you want to specify a general -content type more precisely, first specify the general content type and then the -more specific one, like in the example below. +trying to find a matching `RegExp`. For overlapping content types, it starts +with the last one configured and ends with the first (last in, first out). +To specify a general content type more precisely, first specify the general +type, then the specific one, as shown below. ```js // Here only the second content type parser is called because its value also matches the first one @@ -88,10 +81,9 @@ fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) ``` ### Using addContentTypeParser with fastify.register -When using `addContentTypeParser` in combination with `fastify.register`, -`await` should not be used when registering routes. Using `await` causes -the route registration to be asynchronous and can lead to routes being registered -before the addContentTypeParser has been set. +When using `addContentTypeParser` with `fastify.register`, avoid `await` +when registering routes. Using `await` makes route registration asynchronous, +potentially registering routes before `addContentTypeParser` is set. #### Correct Usage ```js @@ -109,14 +101,13 @@ fastify.register((fastify, opts) => { }); ``` -Besides the `addContentTypeParser` API there are further APIs that can be used. -These are `hasContentTypeParser`, `removeContentTypeParser` and -`removeAllContentTypeParsers`. +In addition to `addContentTypeParser`, the `hasContentTypeParser`, +`removeContentTypeParser`, and `removeAllContentTypeParsers` APIs are available. #### hasContentTypeParser -You can use the `hasContentTypeParser` API to find if a specific content type -parser already exists. +Use the `hasContentTypeParser` API to check if a specific content type parser +exists. ```js if (!fastify.hasContentTypeParser('application/jsoff')){ @@ -130,8 +121,8 @@ if (!fastify.hasContentTypeParser('application/jsoff')){ #### removeContentTypeParser -With `removeContentTypeParser` a single or an array of content types can be -removed. The method supports `string` and `RegExp` content types. +`removeContentTypeParser` can remove a single content type or an array of +content types, supporting both `string` and `RegExp`. ```js fastify.addContentTypeParser('text/xml', function (request, payload, done) { @@ -145,16 +136,11 @@ fastify.removeContentTypeParser(['application/json', 'text/plain']) ``` #### removeAllContentTypeParsers - -In the example from just above, it is noticeable that we need to specify each -content type that we want to remove. To solve this problem Fastify provides the -`removeAllContentTypeParsers` API. This can be used to remove all currently -existing content type parsers. In the example below we achieve the same as in -the example above except that we do not need to specify each content type to -delete. Just like `removeContentTypeParser`, this API supports encapsulation. -The API is especially useful if you want to register a [catch-all content type -parser](#catch-all) that should be executed for every content type and the -built-in parsers should be ignored as well. +The `removeAllContentTypeParsers` API removes all existing content type parsers +eliminating the need to specify each one individually. This API supports +encapsulation and is useful for registering a +[catch-all content type parser](#catch-all) that should be executed for every +content type, ignoring built-in parsers. ```js fastify.removeAllContentTypeParsers() @@ -166,18 +152,16 @@ fastify.addContentTypeParser('text/xml', function (request, payload, done) { }) ``` -**Notice**: The old syntaxes `function(req, done)` and `async function(req)` for -the parser are still supported but they are deprecated. +**Notice**: `function(req, done)` and `async function(req)` are +still supported but deprecated. #### Body Parser -You can parse the body of a request in two ways. The first one is shown above: -you add a custom content type parser and handle the request stream. In the -second one, you should pass a `parseAs` option to the `addContentTypeParser` -API, where you declare how you want to get the body. It could be of type -`'string'` or `'buffer'`. If you use the `parseAs` option, Fastify will -internally handle the stream and perform some checks, such as the [maximum -size](./Server.md#factory-body-limit) of the body and the content length. If the -limit is exceeded the custom parser will not be invoked. +The request body can be parsed in two ways. First, add a custom content type +parser and handle the request stream. Or second, use the `parseAs` option in the +`addContentTypeParser` API, specifying `'string'` or `'buffer'`. Fastify will +handle the stream, check the [maximum size](./Server.md#factory-body-limit) of +the body, and the content length. If the limit is exceeded, the custom parser +will not be invoked. ```js fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { try { @@ -195,15 +179,14 @@ See for an example. ##### Custom Parser Options -+ `parseAs` (string): Either `'string'` or `'buffer'` to designate how the - incoming data should be collected. Default: `'buffer'`. ++ `parseAs` (string): `'string'` or `'buffer'` to designate how the incoming + data should be collected. Default: `'buffer'`. + `bodyLimit` (number): The maximum payload size, in bytes, that the custom parser will accept. Defaults to the global body limit passed to the [`Fastify factory function`](./Server.md#bodylimit). #### Catch-All -There are some cases where you need to catch all requests regardless of their -content type. With Fastify, you can just use the `'*'` content type. +To catch all requests regardless of content type, use the `'*'` content type: ```js fastify.addContentTypeParser('*', function (request, payload, done) { let data = '' @@ -213,9 +196,8 @@ fastify.addContentTypeParser('*', function (request, payload, done) { }) }) ``` - -Using this, all requests that do not have a corresponding content type parser -will be handled by the specified function. +All requests without a corresponding content type parser will be handled by +this function. This is also useful for piping the request stream. You can define a content parser like: @@ -226,7 +208,7 @@ fastify.addContentTypeParser('*', function (request, payload, done) { }) ``` -and then access the core HTTP request directly for piping it where you want: +And then access the core HTTP request directly for piping it where you want: ```js app.post('/hello', (request, reply) => { @@ -254,12 +236,11 @@ fastify.route({ }) ``` -For piping file uploads you may want to check out [this -plugin](https://github.com/fastify/fastify-multipart). +For piping file uploads, check out +[`@fastify/multipart`](https://github.com/fastify/fastify-multipart). -If you want the content type parser to be executed on all content types and not -only on those that don't have a specific one, you should call the -`removeAllContentTypeParsers` method first. +To execute the content type parser on all content types, call +`removeAllContentTypeParsers` first. ```js // Without this call, the request body with the content type application/json would be processed by the built-in JSON parser diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index d3a521a2cac..7d41a0bd757 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -362,7 +362,8 @@ Sets the content type for the response. This is a shortcut for reply.type('text/html') ``` If the `Content-Type` has a JSON subtype, and the charset parameter is not set, -`utf-8` will be used as the charset by default. +`utf-8` will be used as the charset by default. For other content types, the +charset must be set explicitly. ### .getSerializationFunction(schema | httpStatus, [contentType]) From 15c91ac05c5560d5d3a56d4cd43d9a224201eda8 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 16 Jan 2025 11:59:48 +0000 Subject: [PATCH 0906/1295] docs(getting-started): clarify fastify-cli is separate (#5949) Signed-off-by: Frazer Smith --- docs/Guides/Getting-Started.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index 5cb9e80e5ee..b55a3cc4e80 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -559,8 +559,9 @@ Read the [testing](./Testing.md) documentation to learn more! ### Run your server from CLI -Fastify also has CLI integration thanks to -[fastify-cli](https://github.com/fastify/fastify-cli). +Fastify also has CLI integration via +[fastify-cli](https://github.com/fastify/fastify-cli), +a separate tool for scaffolding and managing Fastify projects. First, install `fastify-cli`: From 9672b588c7153ba8bc40de6d0d3412ded82c6003 Mon Sep 17 00:00:00 2001 From: Matthias Keckl <53833818+matthyk@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:55:21 +0100 Subject: [PATCH 0907/1295] docs(validation-and-serialization): fix typo (#5952) Signed-off-by: Matthias Keckl <53833818+matthyk@users.noreply.github.com> --- docs/Reference/Validation-and-Serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 02445deee26..5f1c9f91847 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -6,7 +6,7 @@ recommend using [JSON Schema](https://json-schema.org/) to validate your routes and serialize your outputs. Internally, Fastify compiles the schema into a highly performant function. -Validation will only be attempted if the content type is `application-json`, as +Validation will only be attempted if the content type is `application/json`, as described in the documentation for the [content type parser](./ContentTypeParser.md). From 14ad3ca897bef513dcd2153bf4e9d170fadef203 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 18 Jan 2025 10:05:06 +0100 Subject: [PATCH 0908/1295] test: migrated route-shorthand.test.js from tap to node:test (#5923) --- test/route-shorthand.test.js | 68 +++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/test/route-shorthand.test.js b/test/route-shorthand.test.js index 1c3ca6048b2..e9585d0fc97 100644 --- a/test/route-shorthand.test.js +++ b/test/route-shorthand.test.js @@ -1,62 +1,66 @@ 'use strict' -const t = require('tap') -const test = t.test +const { describe, test } = require('node:test') const sget = require('simple-get').concat -const Fastify = require('../fastify') +const Fastify = require('..') -test('route-shorthand', t => { +describe('route-shorthand', () => { const methodsReader = new Fastify() const supportedMethods = methodsReader.supportedMethods - t.plan(supportedMethods.length + 1) - const test = t.test - for (const method of supportedMethods) { - test(`route-shorthand - ${method.toLowerCase()}`, t => { - t.plan(3) + test(`route-shorthand - ${method.toLowerCase()}`, async (t) => { + t.plan(2) const fastify = new Fastify() - fastify[method.toLowerCase()]('/', function (req, reply) { - t.equal(req.method, method) + fastify[method.toLowerCase()]('/', (req, reply) => { + t.assert.strictEqual(req.method, method) reply.send() }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await new Promise((resolve, reject) => { sget({ method, - url: 'http://localhost:' + fastify.server.address().port + url: `http://localhost:${fastify.server.address().port}` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + if (err) { + t.assert.ifError(err) + return reject(err) + } + t.assert.strictEqual(response.statusCode, 200) + resolve() }) }) }) } - test('route-shorthand - all', t => { - t.plan(3 * supportedMethods.length) + test('route-shorthand - all', async (t) => { + t.plan(2 * supportedMethods.length) const fastify = new Fastify() let currentMethod = '' fastify.all('/', function (req, reply) { - t.equal(req.method, currentMethod) + t.assert.strictEqual(req.method, currentMethod) reply.send() }) - fastify.listen({ port: 0 }, async function (err) { - if (err) t.error(err) - t.teardown(() => { fastify.close() }) - for (const method of supportedMethods) { - currentMethod = method - await new Promise(resolve => sget({ + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + for (const method of supportedMethods) { + currentMethod = method + await new Promise((resolve, reject) => { + sget({ method, - url: 'http://localhost:' + fastify.server.address().port + url: `http://localhost:${fastify.server.address().port}` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + if (err) { + t.assert.ifError(err) + return reject(err) + } + t.assert.strictEqual(response.statusCode, 200) resolve() }) - ) - } - }) + }) + } }) }) From 84d7e6f0012149f111bcbc3eadc34222034a7a08 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 18 Jan 2025 10:05:27 +0100 Subject: [PATCH 0909/1295] test: migrated register.test.js from tap to node:test (#5918) --- test/register.test.js | 105 ++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/test/register.test.js b/test/register.test.js index f75ded87f5d..87370694a98 100644 --- a/test/register.test.js +++ b/test/register.test.js @@ -1,21 +1,20 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('..') -test('register', t => { - t.plan(17) +test('register', async (t) => { + t.plan(14) const fastify = Fastify() fastify.register(function (instance, opts, done) { - t.not(instance, fastify) - t.ok(Object.prototype.isPrototypeOf.call(fastify, instance)) + t.assert.notStrictEqual(instance, fastify) + t.assert.ok(Object.prototype.isPrototypeOf.call(fastify, instance)) - t.equal(typeof opts, 'object') - t.equal(typeof done, 'function') + t.assert.strictEqual(typeof opts, 'object') + t.assert.strictEqual(typeof done, 'function') instance.get('/first', function (req, reply) { reply.send({ hello: 'world' }) @@ -24,11 +23,11 @@ test('register', t => { }) fastify.register(function (instance, opts, done) { - t.not(instance, fastify) - t.ok(Object.prototype.isPrototypeOf.call(fastify, instance)) + t.assert.notStrictEqual(instance, fastify) + t.assert.ok(Object.prototype.isPrototypeOf.call(fastify, instance)) - t.equal(typeof opts, 'object') - t.equal(typeof done, 'function') + t.assert.strictEqual(typeof opts, 'object') + t.assert.strictEqual(typeof done, 'function') instance.get('/second', function (req, reply) { reply.send({ hello: 'world' }) @@ -36,28 +35,32 @@ test('register', t => { done() }) - fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) - - makeRequest('first') - makeRequest('second') - }) - - function makeRequest (path) { - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/' + path - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await makeRequest('first') + await makeRequest('second') + + async function makeRequest (path) { + return new Promise((resolve, reject) => { + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + '/' + path + }, (err, response, body) => { + if (err) { + t.assert.ifError(err) + return reject(err) + } + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + resolve() + }) }) } }) -test('internal route declaration should pass the error generated by the register to the done handler / 1', t => { +test('internal route declaration should pass the error generated by the register to the done handler / 1', (t, done) => { t.plan(1) const fastify = Fastify() @@ -70,12 +73,13 @@ test('internal route declaration should pass the error generated by the register }) fastify.listen({ port: 0 }, err => { - fastify.close() - t.equal(err.message, 'kaboom') + t.after(() => fastify.close()) + t.assert.strictEqual(err.message, 'kaboom') + done() }) }) -test('internal route declaration should pass the error generated by the register to the done handler / 2', t => { +test('internal route declaration should pass the error generated by the register to the done handler / 2', (t, done) => { t.plan(2) const fastify = Fastify() @@ -88,12 +92,13 @@ test('internal route declaration should pass the error generated by the register }) fastify.after(err => { - t.equal(err.message, 'kaboom') + t.assert.strictEqual(err.message, 'kaboom') }) fastify.listen({ port: 0 }, err => { - fastify.close() - t.error(err) + t.after(() => fastify.close()) + t.assert.ifError(err) + done() }) }) @@ -107,25 +112,25 @@ test('awaitable register and after', async t => { first = true }) - t.equal(first, true) + t.assert.strictEqual(first, true) fastify.register(async (instance, opts) => { second = true }) await fastify.after() - t.equal(second, true) + t.assert.strictEqual(second, true) fastify.register(async (instance, opts) => { third = true }) await fastify.ready() - t.equal(third, true) + t.assert.strictEqual(third, true) }) function thenableRejects (t, promise, error) { - return t.rejects(async () => { await promise }, error) + return t.assert.rejects(async () => { await promise }, error) } test('awaitable register error handling', async t => { @@ -138,13 +143,13 @@ test('awaitable register error handling', async t => { }), e) fastify.register(async (instance, opts) => { - t.fail('should not be executed') + t.assert.fail('should not be executed') }) - await t.rejects(fastify.after(), e) + await t.assert.rejects(fastify.after(), e) fastify.register(async (instance, opts) => { - t.fail('should not be executed') + t.assert.fail('should not be executed') }) await thenableRejects(t, fastify.ready(), e) @@ -160,16 +165,16 @@ test('awaitable after error handling', async t => { }) fastify.register(async (instance, opts) => { - t.fail('should not be executed') + t.assert.fail('should not be executed') }) - await t.rejects(fastify.after(), e) + await t.assert.rejects(fastify.after(), e) fastify.register(async (instance, opts) => { - t.fail('should not be executed') + t.assert.fail('should not be executed') }) - await t.rejects(fastify.ready()) + await t.assert.rejects(fastify.ready()) }) test('chainable register', async t => { @@ -178,11 +183,11 @@ test('chainable register', async t => { const fastify = Fastify() fastify.register(async () => { - t.pass('first loaded') + t.assert.ok('first loaded') }).register(async () => { - t.pass('second loaded') + t.assert.ok('second loaded') }).register(async () => { - t.pass('third loaded') + t.assert.ok('third loaded') }) await fastify.ready() From 4e0fa5d4fc4a7e484f36cb930f42228e90d1c292 Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Sun, 19 Jan 2025 02:06:28 -0800 Subject: [PATCH 0910/1295] chore: fix broken link to N|Solid (#5954) Signed-off-by: Hong Xu --- docs/Reference/LTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 94a58b48d9b..79fafafa6c4 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -25,7 +25,7 @@ in this document: and verified against alternative runtimes that are compatible with Node.js. The maintenance teams of these alternative runtimes are responsible for ensuring and guaranteeing these tests work properly. - 1. [N|Solid](https://docs.nodesource.com/nsolid), maintained by NodeSource, + 1. [N|Solid](https://docs.nodesource.com/docs/product_suite), maintained by NodeSource, commits to testing and verifying each Fastify major release against the N|Solid LTS versions that are current at the time of the Fastify release. NodeSource guarantees that Fastify will be compatible and function correctly From 0b00f7bece60c5ed9c9785e379a8c04411f3ab18 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 19 Jan 2025 13:57:38 +0000 Subject: [PATCH 0911/1295] docs(reference): even more conciseness (#5951) * docs(reference): even more conciseness * docs(reference/principles): add link to lts doc * chore: shrinky shrink --- docs/Reference/ContentTypeParser.md | 5 +-- docs/Reference/Encapsulation.md | 61 +++++++++++++---------------- docs/Reference/HTTP2.md | 14 +++---- docs/Reference/LTS.md | 15 +++---- docs/Reference/Lifecycle.md | 43 +++++++++----------- docs/Reference/Middleware.md | 38 +++++++++--------- docs/Reference/Principles.md | 55 ++++++++++++-------------- docs/Reference/Warnings.md | 47 +++++++++++----------- 8 files changed, 127 insertions(+), 151 deletions(-) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index eb685c37298..d0d914fd13c 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -199,8 +199,7 @@ fastify.addContentTypeParser('*', function (request, payload, done) { All requests without a corresponding content type parser will be handled by this function. -This is also useful for piping the request stream. You can define a content -parser like: +This is also useful for piping the request stream. Define a content parser like: ```js fastify.addContentTypeParser('*', function (request, payload, done) { @@ -208,7 +207,7 @@ fastify.addContentTypeParser('*', function (request, payload, done) { }) ``` -And then access the core HTTP request directly for piping it where you want: +And then access the core HTTP request directly for piping: ```js app.post('/hello', (request, reply) => { diff --git a/docs/Reference/Encapsulation.md b/docs/Reference/Encapsulation.md index aa26ef170aa..195a53ab824 100644 --- a/docs/Reference/Encapsulation.md +++ b/docs/Reference/Encapsulation.md @@ -3,21 +3,20 @@ ## Encapsulation -A fundamental feature of Fastify is the "encapsulation context." The -encapsulation context governs which [decorators](./Decorators.md), registered -[hooks](./Hooks.md), and [plugins](./Plugins.md) are available to -[routes](./Routes.md). A visual representation of the encapsulation context -is shown in the following figure: +A fundamental feature of Fastify is the "encapsulation context." It governs +which [decorators](./Decorators.md), registered [hooks](./Hooks.md), and +[plugins](./Plugins.md) are available to [routes](./Routes.md). A visual +representation of the encapsulation context is shown in the following figure: ![Figure 1](../resources/encapsulation_context.svg) -In the above figure, there are several entities: +In the figure above, there are several entities: 1. The _root context_ 2. Three _root plugins_ -3. Two _child contexts_ where each _child context_ has +3. Two _child contexts_, each with: * Two _child plugins_ - * One _grandchild context_ where each _grandchild context_ has + * One _grandchild context_, each with: - Three _child plugins_ Every _child context_ and _grandchild context_ has access to the _root plugins_. @@ -26,15 +25,14 @@ _child plugins_ registered within the containing _child context_, but the containing _child context_ **does not** have access to the _child plugins_ registered within its _grandchild context_. -Given that everything in Fastify is a [plugin](./Plugins.md), except for the +Given that everything in Fastify is a [plugin](./Plugins.md) except for the _root context_, every "context" and "plugin" in this example is a plugin -that can consist of decorators, hooks, plugins, and routes. Thus, to put -this example into concrete terms, consider a basic scenario of a REST API -server that has three routes: the first route (`/one`) requires authentication, -the second route (`/two`) does not, and the third route (`/three`) has -access to the same context as the second route. Using -[@fastify/bearer-auth][bearer] to provide the authentication, the code for this -example is as follows: +that can consist of decorators, hooks, plugins, and routes. To put this +example into concrete terms, consider a basic scenario of a REST API server +with three routes: the first route (`/one`) requires authentication, the +second route (`/two`) does not, and the third route (`/three`) has access to +the same context as the second route. Using [@fastify/bearer-auth][bearer] to +provide authentication, the code for this example is as follows: ```js 'use strict' @@ -52,9 +50,9 @@ fastify.register(async function authenticatedContext (childServer) { handler (request, response) { response.send({ answer: request.answer, - // request.foo will be undefined as it's only defined in publicContext + // request.foo will be undefined as it is only defined in publicContext foo: request.foo, - // request.bar will be undefined as it's only defined in grandchildContext + // request.bar will be undefined as it is only defined in grandchildContext bar: request.bar }) } @@ -71,7 +69,7 @@ fastify.register(async function publicContext (childServer) { response.send({ answer: request.answer, foo: request.foo, - // request.bar will be undefined as it's only defined in grandchildContext + // request.bar will be undefined as it is only defined in grandchildContext bar: request.bar }) } @@ -97,16 +95,16 @@ fastify.register(async function publicContext (childServer) { fastify.listen({ port: 8000 }) ``` -The above server example shows all of the encapsulation concepts outlined in the +The server example above demonstrates the encapsulation concepts from the original diagram: 1. Each _child context_ (`authenticatedContext`, `publicContext`, and -`grandchildContext`) has access to the `answer` request decorator defined in -the _root context_. + `grandchildContext`) has access to the `answer` request decorator defined in + the _root context_. 2. Only the `authenticatedContext` has access to the `@fastify/bearer-auth` -plugin. + plugin. 3. Both the `publicContext` and `grandchildContext` have access to the `foo` -request decorator. + request decorator. 4. Only the `grandchildContext` has access to the `bar` request decorator. To see this, start the server and issue requests: @@ -125,16 +123,13 @@ To see this, start the server and issue requests: ## Sharing Between Contexts -Notice that each context in the prior example inherits _only_ from the parent -contexts. Parent contexts cannot access any entities within their descendent -contexts. This default is occasionally not desired. In such cases, the -encapsulation context can be broken through the usage of -[fastify-plugin][fastify-plugin] such that anything registered in a descendent -context is available to the containing parent context. +Each context in the prior example inherits _only_ from its parent contexts. Parent +contexts cannot access entities within their descendant contexts. If needed, +encapsulation can be broken using [fastify-plugin][fastify-plugin], making +anything registered in a descendant context available to the parent context. -Assuming the `publicContext` needs access to the `bar` decorator defined -within the `grandchildContext` in the previous example, the code can be -rewritten as: +To allow `publicContext` access to the `bar` decorator in `grandchildContext`, +rewrite the code as follows: ```js 'use strict' diff --git a/docs/Reference/HTTP2.md b/docs/Reference/HTTP2.md index 69c7cbfb026..d6e7a2c46fd 100644 --- a/docs/Reference/HTTP2.md +++ b/docs/Reference/HTTP2.md @@ -2,11 +2,11 @@ ## HTTP2 -_Fastify_ supports HTTP2 over either HTTPS (h2) or plaintext (h2c). +_Fastify_ supports HTTP2 over HTTPS (h2) or plaintext (h2c). Currently, none of the HTTP2-specific APIs are available through _Fastify_, but -Node's `req` and `res` can be accessed through our `Request` and `Reply` -interface. PRs are welcome. +Node's `req` and `res` can be accessed through the `Request` and `Reply` +interfaces. PRs are welcome. ### Secure (HTTPS) @@ -61,7 +61,7 @@ fastify.get('/', function (request, reply) { fastify.listen({ port: 3000 }) ``` -You can test your new server with: +Test the new server with: ``` $ npx h2url https://localhost:3000 @@ -69,8 +69,8 @@ $ npx h2url https://localhost:3000 ### Plain or insecure -If you are building microservices, you can connect to HTTP2 in plain text, -however, this is not supported by browsers. +For microservices, HTTP2 can connect in plain text, but this is not +supported by browsers. ```js 'use strict' @@ -86,7 +86,7 @@ fastify.get('/', function (request, reply) { fastify.listen({ port: 3000 }) ``` -You can test your new server with: +Test the new server with: ``` $ npx h2url http://localhost:3000 diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 79fafafa6c4..9cf6ab2f64e 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -25,13 +25,11 @@ in this document: and verified against alternative runtimes that are compatible with Node.js. The maintenance teams of these alternative runtimes are responsible for ensuring and guaranteeing these tests work properly. - 1. [N|Solid](https://docs.nodesource.com/docs/product_suite), maintained by NodeSource, - commits to testing and verifying each Fastify major release against the N|Solid - LTS versions that are current at the time of the Fastify release. - NodeSource guarantees that Fastify will be compatible and function correctly - with N|Solid, aligning with the support and compatibility scope of the N|Solid - LTS versions available at the time of the Fastify release. - This ensures users of N|Solid can confidently use Fastify. + 1. [N|Solid](https://docs.nodesource.com/docs/product_suite) tests and + verifies each Fastify major release against current N|Solid LTS versions. + NodeSource ensures Fastify compatibility with N|Solid, aligning with the + support scope of N|Solid LTS versions at the time of the Fastify release. + This guarantees N|Solid users can confidently use Fastify. A "month" is defined as 30 consecutive days. @@ -40,13 +38,10 @@ A "month" is defined as 30 consecutive days. > As a consequence of providing long-term support for major releases, there are > occasions where we need to release breaking changes as a _minor_ version > release. Such changes will _always_ be noted in the [release -> notes](https://github.com/fastify/fastify/releases). > > To avoid automatically receiving breaking security updates it is possible to > use the tilde (`~`) range qualifier. For example, to get patches for the 3.15 > release, and avoid automatically updating to the 3.16 release, specify the -> dependency as `"fastify": "~3.15.x"`. This will leave your application -> vulnerable, so please use with caution. ### Security Support Beyond LTS diff --git a/docs/Reference/Lifecycle.md b/docs/Reference/Lifecycle.md index d8c29495f7d..bd41c1de59a 100644 --- a/docs/Reference/Lifecycle.md +++ b/docs/Reference/Lifecycle.md @@ -3,12 +3,11 @@ ## Lifecycle -Following the schema of the internal lifecycle of Fastify. +This schema shows the internal lifecycle of Fastify. -On the right branch of every section there is the next phase of the lifecycle, -on the left branch there is the corresponding error code that will be generated -if the parent throws an error *(note that all the errors are automatically -handled by Fastify)*. +The right branch of each section shows the next phase of the lifecycle. The left +branch shows the corresponding error code generated if the parent throws an +error. All errors are automatically handled by Fastify. ``` Incoming Request @@ -42,26 +41,23 @@ Incoming Request └─▶ onResponse Hook ``` -At any point before or during the `User Handler`, `reply.hijack()` can be called -to prevent Fastify from: -- Running all the following hooks and user handler -- Sending the response automatically +Before or during the `User Handler`, `reply.hijack()` can be called to: +- Prevent Fastify from running subsequent hooks and the user handler +- Prevent Fastify from sending the response automatically -NB (*): If `reply.raw` is used to send a response back to the user, `onResponse` -hooks will still be executed +If `reply.raw` is used to send a response, `onResponse` hooks will still +be executed. ## Reply Lifecycle -Whenever the user handles the request, the result may be: +When the user handles the request, the result may be: -- in async handler: it returns a payload -- in async handler: it throws an `Error` -- in sync handler: it sends a payload -- in sync handler: it sends an `Error` instance +- In an async handler: it returns a payload or throws an `Error` +- In a sync handler: it sends a payload or an `Error` instance -If the reply was hijacked, we skip all the below steps. Otherwise, when it is -being submitted, the data flow performed is the following: +If the reply was hijacked, all subsequent steps are skipped. Otherwise, when +submitted, the data flow is as follows: ``` ★ schema validation Error @@ -81,9 +77,8 @@ being submitted, the data flow performed is the following: └─▶ reply sent ``` -Note: `reply sent` means that the JSON payload will be serialized by: - -- the [reply serialized](./Server.md#setreplyserializer) if set -- or by the [serializer compiler](./Server.md#setserializercompiler) when a JSON - schema has been set for the returning HTTP status code -- or by the default `JSON.stringify` function +`reply sent` means the JSON payload will be serialized by one of the following: +- The [reply serializer](./Server.md#setreplyserializer) if set +- The [serializer compiler](./Server.md#setserializercompiler) if a JSON schema + is set for the HTTP status code +- The default `JSON.stringify` function \ No newline at end of file diff --git a/docs/Reference/Middleware.md b/docs/Reference/Middleware.md index 8664aa6fe3b..c61a9a309d9 100644 --- a/docs/Reference/Middleware.md +++ b/docs/Reference/Middleware.md @@ -22,36 +22,36 @@ fastify.use(require('ienoopen')()) fastify.use(require('x-xss-protection')()) ``` -You can also use [`@fastify/middie`](https://github.com/fastify/middie), which provides -support for simple Express-style middleware but with improved performance: +[`@fastify/middie`](https://github.com/fastify/middie) can also be used, +which provides support for simple Express-style middleware with improved +performance: ```js await fastify.register(require('@fastify/middie')) fastify.use(require('cors')()) ``` -Remember that middleware can be encapsulated; this means that you can decide -where your middleware should run by using `register` as explained in the -[plugins guide](../Guides/Plugins-Guide.md). +Middleware can be encapsulated, allowing control over where it runs using +`register` as explained in the [plugins guide](../Guides/Plugins-Guide.md). -Fastify middleware does not expose the `send` method or other methods specific to -the Fastify [Reply](./Reply.md#reply) instance. This is because Fastify wraps +Fastify middleware does not expose the `send` method or other methods specific +to the Fastify [Reply](./Reply.md#reply) instance. This is because Fastify wraps the incoming `req` and `res` Node instances using the [Request](./Request.md#request) and [Reply](./Reply.md#reply) objects -internally, but this is done after the middleware phase. If you need to create -middleware, you have to use the Node `req` and `res` instances. Otherwise, you -can use the `preHandler` hook that already has the -[Request](./Request.md#request) and [Reply](./Reply.md#reply) Fastify instances. -For more information, see [Hooks](./Hooks.md#hooks). +internally, but this is done after the middleware phase. To create middleware, +use the Node `req` and `res` instances. Alternatively, use the `preHandler` hook +that already has the Fastify [Request](./Request.md#request) and +[Reply](./Reply.md#reply) instances. For more information, see +[Hooks](./Hooks.md#hooks). #### Restrict middleware execution to certain paths -If you need to only run middleware under certain paths, just pass the path as -the first parameter to `use` and you are done! +To run middleware under certain paths, pass the path as the first parameter to +`use`. -*Note that this does not support routes with parameters, (e.g. -`/user/:id/comments`) and wildcards are not supported in multiple paths.* +*Note: This does not support routes with parameters (e.g. `/user/:id/comments`) +and wildcards are not supported in multiple paths.* ```js const path = require('node:path') @@ -69,10 +69,10 @@ fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets'))) ### Alternatives -Fastify offers some alternatives to the most commonly used middleware, such as -[`@fastify/helmet`](https://github.com/fastify/fastify-helmet) in case of +Fastify offers alternatives to commonly used middleware, such as +[`@fastify/helmet`](https://github.com/fastify/fastify-helmet) for [`helmet`](https://github.com/helmetjs/helmet), [`@fastify/cors`](https://github.com/fastify/fastify-cors) for [`cors`](https://github.com/expressjs/cors), and [`@fastify/static`](https://github.com/fastify/fastify-static) for -[`serve-static`](https://github.com/expressjs/serve-static). +[`serve-static`](https://github.com/expressjs/serve-static). \ No newline at end of file diff --git a/docs/Reference/Principles.md b/docs/Reference/Principles.md index 7439bf7eea6..6acd3adce0a 100644 --- a/docs/Reference/Principles.md +++ b/docs/Reference/Principles.md @@ -16,48 +16,41 @@ the following technical principles: ## "Zero" Overhead in Production -Fastify aims to implement its features by adding as minimal overhead to your -application as possible. -This is usually delivered by implementing fast algorithms and data structures, -as well as JavaScript-specific features. +Fastify aims to implement features with minimal overhead. This is achieved by +using fast algorithms, data structures, and JavaScript-specific features. -Given that JavaScript does not offer zero-overhead data structures, this principle -is at odds with providing a great developer experience and providing more features, -as usually those cost some overhead. +Since JavaScript does not offer zero-overhead data structures, this principle +can conflict with providing a great developer experience and additional features, +as these usually incur some overhead. ## "Good" Developer Experience -Fastify aims to provide the best developer experience at the performance point -it is operating. -It provides a great out-of-the-box experience that is flexible enough to be -adapted to a variety of situations. +Fastify aims to provide the best developer experience at its performance point. +It offers a great out-of-the-box experience that is flexible enough to adapt to +various situations. -As an example, this means that binary addons are forbidden because most JavaScript -developers would not -have access to a compiler. +For example, binary addons are forbidden because most JavaScript developers do +not have access to a compiler. ## Works great for small and big projects alike -We recognize that most applications start small and become more complex over time. -Fastify aims to grow with -the complexity of your application, providing advanced features to structure -your codebase. +Most applications start small and become more complex over time. Fastify aims to +grow with this complexity, providing advanced features to structure codebases. ## Easy to migrate to microservices (or even serverless) and back -How you deploy your routes should not matter. The framework should "just work". +Route deployment should not matter. The framework should "just work". ## Security and Data Validation -Your web framework is the first point of contact with untrusted data, and it -needs to act as the first line of defense for your system. +A web framework is the first point of contact with untrusted data and must act +as the first line of defense for the system. ## If something could be a plugin, it likely should -We recognize that there are an infinite amount of use cases for an HTTP framework -for Node.js. Catering to them in a single module would make the codebase unmaintainable. -Therefore we provide hooks and options to allow you to customize the framework -as you please. +Recognizing the infinite use cases for an HTTP framework, catering to all in a +single module would make the codebase unmaintainable. Therefore, hooks and +options are provided to customize the framework as needed. ## Easily testable @@ -65,14 +58,16 @@ Testing Fastify applications should be a first-class concern. ## Do not monkeypatch core -Monkeypatch Node.js APIs or installing globals that alter the behavior of the -runtime makes building modular applications harder, and limit the use cases of Fastify. -Other frameworks do this and we do not. +Monkeypatching Node.js APIs or installing globals that alter the runtime makes +building modular applications harder and limits Fastify's use cases. Other +frameworks do this; Fastify does not. ## Semantic Versioning and Long Term Support -We provide a clear Long Term Support strategy so developers can know when to upgrade. +A clear [Long Term Support strategy is provided](./LTS.md) to inform developers when +to upgrade. ## Specification adherence -In doubt, we chose the strict behavior as defined by the relevant Specifications. +In doubt, we chose the strict behavior as defined by the relevant +Specifications. diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 7358454ce31..165baa44775 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -14,30 +14,27 @@ ### Warnings In Fastify -Fastify utilizes Node.js's [warning event](https://nodejs.org/api/process.html#event-warning) -API to notify users of deprecated features and known coding mistakes. Fastify's -warnings are recognizable by the `FSTWRN` and `FSTDEP` prefixes on warning -code. When encountering such a warning, it is highly recommended that the -cause of the warning be determined through use of the -[`--trace-warnings`](https://nodejs.org/api/cli.html#--trace-warnings) and -[`--trace-deprecation`](https://nodejs.org/api/cli.html#--trace-deprecation) -flags. These will produce stack traces pointing out where the issue occurs -in the application's code. Issues opened about warnings without including -this information may be closed due to lack of information. - -In addition to tracing, warnings can also be disabled. It is not recommended to -disable warnings as a matter of course, but if necessary, they can be disabled -by using any of the following methods: - -- setting the `NODE_NO_WARNINGS` environment variable to `1` -- passing the `--no-warnings` flag to the node process -- setting 'no-warnings' in the `NODE_OPTIONS` environment variable - -For more information on how to disable warnings, see [node's documentation](https://nodejs.org/api/cli.html). - -However, disabling warnings is not recommended as it may cause -potential problems when upgrading Fastify versions. -Only experienced users should consider disabling warnings. +Fastify uses Node.js's [warning event](https://nodejs.org/api/process.html#event-warning) +API to notify users of deprecated features and coding mistakes. Fastify's +warnings are recognizable by the `FSTWRN` and `FSTDEP` prefixes. When +encountering such a warning, it is highly recommended to determine the cause +using the [`--trace-warnings`](https://nodejs.org/api/cli.html#--trace-warnings) +and [`--trace-deprecation`](https://nodejs.org/api/cli.html#--trace-deprecation) +flags. These produce stack traces pointing to where the issue occurs in the +application's code. Issues opened about warnings without this information will +be closed due to lack of details. + +Warnings can also be disabled, though it is not recommended. If necessary, use +one of the following methods: + +- Set the `NODE_NO_WARNINGS` environment variable to `1` +- Pass the `--no-warnings` flag to the node process +- Set `no-warnings` in the `NODE_OPTIONS` environment variable + +For more information on disabling warnings, see [Node's documentation](https://nodejs.org/api/cli.html). + +Disabling warnings may cause issues when upgrading Fastify versions. Only +experienced users should consider disabling warnings. ### Fastify Warning Codes @@ -49,7 +46,7 @@ Only experienced users should consider disabling warnings. ### Fastify Deprecation Codes -Deprecation codes are further supported by the Node.js CLI options: +Deprecation codes are supported by the Node.js CLI options: - [--no-deprecation](https://nodejs.org/api/cli.html#--no-deprecation) - [--throw-deprecation](https://nodejs.org/api/cli.html#--throw-deprecation) From 8312df23de26477e19b8a4b5519a093d2919d456 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 19 Jan 2025 16:51:10 +0000 Subject: [PATCH 0912/1295] chore(docs): remove trailing whitespace Signed-off-by: Frazer Smith --- docs/Reference/ContentTypeParser.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index d0d914fd13c..43005beaa94 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -82,7 +82,7 @@ fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) ### Using addContentTypeParser with fastify.register When using `addContentTypeParser` with `fastify.register`, avoid `await` -when registering routes. Using `await` makes route registration asynchronous, +when registering routes. Using `await` makes route registration asynchronous, potentially registering routes before `addContentTypeParser` is set. #### Correct Usage From 794d7d19dfa0dbba4ec7284682050be93b2fcfc2 Mon Sep 17 00:00:00 2001 From: Coluzzi Andrea Date: Tue, 21 Jan 2025 21:23:16 +0100 Subject: [PATCH 0913/1295] test: migrate from tap to node test for close & custom-parser-async (#5915) --- test/close.test.js | 233 +++++++++++++++++-------------- test/custom-parser-async.test.js | 70 +++++----- 2 files changed, 159 insertions(+), 144 deletions(-) diff --git a/test/close.test.js b/test/close.test.js index 51e13b28782..f80eec8717f 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -2,42 +2,43 @@ const net = require('node:net') const http = require('node:http') -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') const { Client } = require('undici') const split = require('split2') const { sleep } = require('./helper') -test('close callback', t => { +test('close callback', (t, testDone) => { t.plan(7) const fastify = Fastify() fastify.addHook('onClose', onClose) function onClose (instance, done) { - t.type(fastify, this) - t.type(fastify, instance) - t.equal(fastify, this) - t.equal(fastify, instance) + t.assert.ok(typeof fastify === typeof this) + t.assert.ok(typeof fastify === typeof instance) + t.assert.strictEqual(fastify, this) + t.assert.strictEqual(fastify, instance) done() } fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close((err) => { - t.error(err) - t.ok('close callback') + t.assert.ifError(err) + t.assert.ok('close callback') + testDone() }) }) }) -test('inside register', t => { +test('inside register', (t, done) => { t.plan(5) const fastify = Fastify() fastify.register(function (f, opts, done) { f.addHook('onClose', onClose) function onClose (instance, done) { - t.ok(instance.prototype === fastify.prototype) - t.equal(instance, f) + t.assert.ok(instance.prototype === fastify.prototype) + t.assert.strictEqual(instance, f) done() } @@ -45,23 +46,24 @@ test('inside register', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close((err) => { - t.error(err) - t.ok('close callback') + t.assert.ifError(err) + t.assert.ok('close callback') + done() }) }) }) -test('close order', t => { +test('close order', (t, done) => { t.plan(5) const fastify = Fastify() const order = [1, 2, 3] fastify.register(function (f, opts, done) { f.addHook('onClose', (instance, done) => { - t.equal(order.shift(), 1) + t.assert.strictEqual(order.shift(), 1) done() }) @@ -69,16 +71,17 @@ test('close order', t => { }) fastify.addHook('onClose', (instance, done) => { - t.equal(order.shift(), 2) + t.assert.strictEqual(order.shift(), 2) done() }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close((err) => { - t.error(err) - t.equal(order.shift(), 3) + t.assert.ifError(err) + t.assert.strictEqual(order.shift(), 3) + done() }) }) }) @@ -90,37 +93,38 @@ test('close order - async', async t => { fastify.register(function (f, opts, done) { f.addHook('onClose', async instance => { - t.equal(order.shift(), 1) + t.assert.strictEqual(order.shift(), 1) }) done() }) fastify.addHook('onClose', () => { - t.equal(order.shift(), 2) + t.assert.strictEqual(order.shift(), 2) }) await fastify.listen({ port: 0 }) await fastify.close() - t.equal(order.shift(), 3) + t.assert.strictEqual(order.shift(), 3) }) -test('should not throw an error if the server is not listening', t => { +test('should not throw an error if the server is not listening', (t, done) => { t.plan(2) const fastify = Fastify() fastify.addHook('onClose', onClose) function onClose (instance, done) { - t.type(fastify, instance) + t.assert.ok(instance.prototype === fastify.prototype) done() } fastify.close((err) => { - t.error(err) + t.assert.ifError(err) + done() }) }) -test('onClose should keep the context', t => { +test('onClose should keep the context', (t, done) => { t.plan(4) const fastify = Fastify() fastify.register(plugin) @@ -128,11 +132,11 @@ test('onClose should keep the context', t => { function plugin (instance, opts, done) { instance.decorate('test', true) instance.addHook('onClose', onClose) - t.ok(instance.prototype === fastify.prototype) + t.assert.ok(instance.prototype === fastify.prototype) function onClose (i, done) { - t.ok(i.test) - t.equal(i, instance) + t.assert.ok(i.test) + t.assert.strictEqual(i, instance) done() } @@ -140,11 +144,12 @@ test('onClose should keep the context', t => { } fastify.close((err) => { - t.error(err) + t.assert.ifError(err) + done() }) }) -test('Should return error while closing (promise) - injection', t => { +test('Should return error while closing (promise) - injection', (t, done) => { t.plan(4) const fastify = Fastify() @@ -158,8 +163,8 @@ test('Should return error while closing (promise) - injection', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) fastify.close() process.nextTick(() => { @@ -167,14 +172,15 @@ test('Should return error while closing (promise) - injection', t => { method: 'GET', url: '/' }).catch(err => { - t.ok(err) - t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + done() }) }, 100) }) }) -test('Should return error while closing (callback) - injection', t => { +test('Should return error while closing (callback) - injection', (t, done) => { t.plan(4) const fastify = Fastify() @@ -190,8 +196,8 @@ test('Should return error while closing (callback) - injection', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) fastify.close() setTimeout(() => { @@ -199,14 +205,15 @@ test('Should return error while closing (callback) - injection', t => { method: 'GET', url: '/' }, (err, res) => { - t.ok(err) - t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + done() }) }, 100) }) }) -test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false', t => { +test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false', (t, done) => { t.plan(4) const fastify = Fastify({ return503OnClosing: false, @@ -219,7 +226,7 @@ test('Current opened connection should NOT continue to work after closing and re }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const port = fastify.server.address().port const client = net.createConnection({ port }, () => { @@ -232,12 +239,13 @@ test('Current opened connection should NOT continue to work after closing and re }) client.on('close', function () { - t.pass('close') + t.assert.ok(true) + done() }) client.once('data', data => { - t.match(data.toString(), /Connection:\s*keep-alive/i) - t.match(data.toString(), /200 OK/i) + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /200 OK/i) client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') }) @@ -245,7 +253,7 @@ test('Current opened connection should NOT continue to work after closing and re }) }) -test('Current opened connection should not accept new incoming connections', t => { +test('Current opened connection should not accept new incoming connections', (t, done) => { t.plan(3) const fastify = Fastify({ forceCloseConnections: false }) fastify.get('/', (req, reply) => { @@ -255,19 +263,20 @@ test('Current opened connection should not accept new incoming connections', t = }, 250) }) - fastify.listen({ port: 0 }, err => { - t.error(err) + fastify.listen({ port: 0 }, async err => { + t.assert.ifError(err) const instance = new Client('http://localhost:' + fastify.server.address().port) - instance.request({ path: '/', method: 'GET' }).then(data => { - t.equal(data.statusCode, 200) - }) - instance.request({ path: '/', method: 'GET' }).then(data => { - t.equal(data.statusCode, 503) - }) + let response = await instance.request({ path: '/', method: 'GET' }) + t.assert.strictEqual(response.statusCode, 200) + + response = await instance.request({ path: '/', method: 'GET' }) + t.assert.strictEqual(response.statusCode, 503) + + done() }) }) -test('rejected incoming connections should be logged', t => { +test('rejected incoming connections should be logged', (t, done) => { t.plan(2) const stream = split(JSON.parse) const fastify = Fastify({ @@ -290,13 +299,14 @@ test('rejected incoming connections should be logged', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const instance = new Client('http://localhost:' + fastify.server.address().port) // initial request to trigger close instance.request({ path: '/', method: 'GET' }) // subsequent request should be rejected instance.request({ path: '/', method: 'GET' }).then(() => { - t.ok(messages.find(message => message.msg.includes('request aborted'))) + t.assert.ok(messages.find(message => message.msg.includes('request aborted'))) + done() }) }) }) @@ -311,8 +321,8 @@ test('Cannot be reopened the closed server without listen callback', async t => try { await fastify.listen({ port: 0 }) } catch (err) { - t.ok(err) - t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') } }) @@ -328,15 +338,15 @@ test('Cannot be reopened the closed server has listen callback', async t => { reject(err) }) }).catch(err => { - t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') - t.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + t.assert.ok(err) }) }) const server = http.createServer() const noSupport = typeof server.closeAllConnections !== 'function' -test('shutsdown while keep-alive connections are active (non-async, native)', { skip: noSupport }, t => { +test('shutsdown while keep-alive connections are active (non-async, native)', { skip: noSupport }, (t, done) => { t.plan(5) const timeoutTime = 2 * 60 * 1000 @@ -350,31 +360,32 @@ test('shutsdown while keep-alive connections are active (non-async, native)', { }) fastify.listen({ port: 0 }, (err, address) => { - t.error(err) + t.assert.ifError(err) const client = new Client( 'http://localhost:' + fastify.server.address().port, { keepAliveTimeout: 1 * 60 * 1000 } ) client.request({ path: '/', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(client.closed, false) + t.assert.ifError(err) + t.assert.strictEqual(client.closed, false) fastify.close((err) => { - t.error(err) + t.assert.ifError(err) // Due to the nature of the way we reap these keep-alive connections, // there hasn't been enough time before the server fully closed in order // for the client to have seen the socket get destroyed. The mere fact // that we have reached this callback is enough indication that the // feature being tested works as designed. - t.equal(client.closed, false) + t.assert.strictEqual(client.closed, false) + done() }) }) }) }) -test('shutsdown while keep-alive connections are active (non-async, idle, native)', { skip: noSupport }, t => { +test('shutsdown while keep-alive connections are active (non-async, idle, native)', { skip: noSupport }, (t, done) => { t.plan(5) const timeoutTime = 2 * 60 * 1000 @@ -388,25 +399,27 @@ test('shutsdown while keep-alive connections are active (non-async, idle, native }) fastify.listen({ port: 0 }, (err, address) => { - t.error(err) + t.assert.ifError(err) const client = new Client( 'http://localhost:' + fastify.server.address().port, { keepAliveTimeout: 1 * 60 * 1000 } ) client.request({ path: '/', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(client.closed, false) + t.assert.ifError(err) + t.assert.strictEqual(client.closed, false) fastify.close((err) => { - t.error(err) + t.assert.ifError(err) // Due to the nature of the way we reap these keep-alive connections, // there hasn't been enough time before the server fully closed in order // for the client to have seen the socket get destroyed. The mere fact // that we have reached this callback is enough indication that the // feature being tested works as designed. - t.equal(client.closed, false) + t.assert.strictEqual(client.closed, false) + + done() }) }) }) @@ -434,9 +447,9 @@ test('triggers on-close hook in the right order with multiple bindings', async t setTimeout(() => { fastify.close(err => { order.push(3) - t.match(order, expectedOrder) + t.assert.deepEqual(order, expectedOrder) - if (err) t.error(err) + if (err) t.assert.ifError(err) else resolve() }) }, 2000) @@ -479,20 +492,20 @@ test('triggers on-close hook in the right order with multiple bindings (forceClo }) client.request({ path: '/', method: 'GET' }) - .then((res) => res.body.json(), err => t.error(err)) + .then((res) => res.body.json(), err => t.assert.ifError(err)) .then(json => { - t.match(json, expectedPayload, 'should payload match') - t.notOk(client.closed, 'should client not be closed') - }, err => t.error(err)) + t.assert.deepEqual(json, expectedPayload, 'should payload match') + t.assert.ok(!client.closed, 'should client not be closed') + }, err => t.assert.ifError(err)) } await new Promise((resolve, reject) => { setTimeout(() => { fastify.close(err => { order.push(2) - t.match(order, expectedOrder) + t.assert.deepEqual(order, expectedOrder) - if (err) t.error(err) + if (err) t.assert.ifError(err) else resolve() }) }, 2000) @@ -535,27 +548,27 @@ test('triggers on-close hook in the right order with multiple bindings (forceClo }) client.request({ path: '/', method: 'GET' }) - .then((res) => res.body.json(), err => t.error(err)) + .then((res) => res.body.json(), err => t.assert.ifError(err)) .then(json => { - t.match(json, expectedPayload, 'should payload match') - t.notOk(client.closed, 'should client not be closed') - }, err => t.error(err)) + t.assert.deepEqual(json, expectedPayload, 'should payload match') + t.assert.ok(!client.closed, 'should client not be closed') + }, err => t.assert.ifError(err)) } await new Promise((resolve, reject) => { setTimeout(() => { fastify.close(err => { order.push(2) - t.match(order, expectedOrder) + t.assert.deepEqual(order, expectedOrder) - if (err) t.error(err) + if (err) t.assert.ifError(err) else resolve() }) }, 2000) }) }) -test('shutsdown while keep-alive connections are active (non-async, custom)', t => { +test('shutsdown while keep-alive connections are active (non-async, custom)', (t, done) => { t.plan(5) const timeoutTime = 2 * 60 * 1000 @@ -578,53 +591,56 @@ test('shutsdown while keep-alive connections are active (non-async, custom)', t }) fastify.listen({ port: 0 }, (err, address) => { - t.error(err) + t.assert.ifError(err) const client = new Client( 'http://localhost:' + fastify.server.address().port, { keepAliveTimeout: 1 * 60 * 1000 } ) client.request({ path: '/', method: 'GET' }, (err, response) => { - t.error(err) - t.equal(client.closed, false) + t.assert.ifError(err) + t.assert.strictEqual(client.closed, false) fastify.close((err) => { - t.error(err) + t.assert.ifError(err) // Due to the nature of the way we reap these keep-alive connections, // there hasn't been enough time before the server fully closed in order // for the client to have seen the socket get destroyed. The mere fact // that we have reached this callback is enough indication that the // feature being tested works as designed. - t.equal(client.closed, false) + t.assert.strictEqual(client.closed, false) + + done() }) }) }) }) -test('preClose callback', t => { +test('preClose callback', (t, done) => { t.plan(5) const fastify = Fastify() fastify.addHook('onClose', onClose) let preCloseCalled = false function onClose (instance, done) { - t.equal(preCloseCalled, true) + t.assert.strictEqual(preCloseCalled, true) done() } fastify.addHook('preClose', preClose) function preClose (done) { - t.type(this, fastify) + t.assert.ok(typeof this === typeof fastify) preCloseCalled = true done() } fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close((err) => { - t.error(err) - t.ok('close callback') + t.assert.ifError(err) + t.assert.ok('close callback') + done() }) }) }) @@ -635,13 +651,13 @@ test('preClose async', async t => { fastify.addHook('onClose', onClose) let preCloseCalled = false async function onClose () { - t.equal(preCloseCalled, true) + t.assert.strictEqual(preCloseCalled, true) } fastify.addHook('preClose', preClose) async function preClose () { preCloseCalled = true - t.type(this, fastify) + t.assert.ok(typeof this === typeof fastify) } await fastify.listen({ port: 0 }) @@ -649,13 +665,13 @@ test('preClose async', async t => { await fastify.close() }) -test('preClose execution order', t => { +test('preClose execution order', (t, done) => { t.plan(4) const fastify = Fastify() const order = [] fastify.addHook('onClose', onClose) function onClose (instance, done) { - t.same(order, [1, 2, 3]) + t.assert.deepStrictEqual(order, [1, 2, 3]) done() } @@ -679,11 +695,12 @@ test('preClose execution order', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) fastify.close((err) => { - t.error(err) - t.ok('close callback') + t.assert.ifError(err) + t.assert.ok('close callback') + done() }) }) }) diff --git a/test/custom-parser-async.test.js b/test/custom-parser-async.test.js index 4eea7550118..0eb622530f6 100644 --- a/test/custom-parser-async.test.js +++ b/test/custom-parser-async.test.js @@ -1,14 +1,13 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../fastify') process.removeAllListeners('warning') -test('contentTypeParser should add a custom async parser', t => { - t.plan(3) +test('contentTypeParser should add a custom async parser', async t => { + t.plan(2) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -24,43 +23,42 @@ test('contentTypeParser should add a custom async parser', t => { return res }) - fastify.listen({ port: 0 }, err => { - t.error(err) + t.after(() => fastify.close()) + await fastify.listen({ port: 0 }) - t.teardown(() => fastify.close()) + await t.test('in POST', (t, done) => { + t.plan(3) - t.test('in POST', t => { - t.plan(3) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port, + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + done() }) + }) - t.test('in OPTIONS', t => { - t.plan(3) + await t.test('in OPTIONS', (t, done) => { + t.plan(3) - sget({ - method: 'OPTIONS', - url: 'http://localhost:' + fastify.server.address().port, - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) - }) + sget({ + method: 'OPTIONS', + url: 'http://localhost:' + fastify.server.address().port, + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + done() }) }) }) From 2bbe2ef44c1ecbd4e79208cbfeec702093c0fc97 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 22 Jan 2025 11:41:52 +0000 Subject: [PATCH 0914/1295] docs(reference/type-providers): improve conciseness (#5962) --- docs/Reference/Type-Providers.md | 40 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 5c1c2b285b4..9406ba8d5af 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -2,18 +2,16 @@ ## Type Providers -Type Providers are a TypeScript only feature that enables Fastify to statically -infer type information directly from inline JSON Schema. They are an alternative -to specifying generic arguments on routes; and can greatly reduce the need to -keep associated types for each schema defined in your project. +Type Providers are a TypeScript feature that enables Fastify to infer type +information from inline JSON Schema. They are an alternative to specifying +generic arguments on routes and can reduce the need to keep associated types for +each schema in a project. ### Providers -Type Providers are offered as additional packages you will need to install into -your project. Each provider uses a different inference library under the hood; -allowing you to select the library most appropriate for your needs. Official Type -Provider packages follow a `@fastify/type-provider-{provider-name}` naming -convention, and there are several community ones available as well. +Official Type Provider packages follow the +`@fastify/type-provider-{provider-name}` naming convention. +Several community providers are also available. The following inference packages are supported: @@ -30,7 +28,7 @@ See also the Type Provider wrapper packages for each of the packages respectivel ### Json Schema to Ts -The following sets up a `json-schema-to-ts` Type Provider +The following sets up a `json-schema-to-ts` Type Provider: ```bash $ npm i @fastify/type-provider-json-schema-to-ts @@ -62,7 +60,7 @@ server.get('/route', { ### TypeBox -The following sets up a TypeBox Type Provider +The following sets up a TypeBox Type Provider: ```bash $ npm i @fastify/type-provider-typebox @@ -89,14 +87,14 @@ server.get('/route', { }) ``` -See also the [TypeBox -documentation](https://github.com/sinclairzx81/typebox#validation) on how to set -up AJV to work with TypeBox. +See the [TypeBox +documentation](https://github.com/sinclairzx81/typebox#validation) +for setting up AJV to work with TypeBox. ### Zod See [official documentation](https://github.com/turkerdev/fastify-type-provider-zod) -for Zod type provider instructions. +for Zod Type Provider instructions. ### Scoped Type-Provider @@ -154,9 +152,9 @@ fastify.register(pluginWithJsonSchema) fastify.register(pluginWithTypebox) ``` -It's also important to mention that since the types don't propagate globally, -_currently_ it is not possible to avoid multiple registrations on routes when -dealing with several scopes, see below: +It is important to note that since the types do not propagate globally, it is +currently not possible to avoid multiple registrations on routes when dealing +with several scopes, as shown below: ```ts import Fastify from 'fastify' @@ -178,7 +176,7 @@ function plugin1(fastify: FastifyInstance, _opts, done): void { }) } }, (req) => { - // it doesn't work! in a new scope needs to call `withTypeProvider` again + // In a new scope, call `withTypeProvider` again to ensure it works const { x, y, z } = req.body }); done() @@ -205,8 +203,8 @@ function plugin2(fastify: FastifyInstance, _opts, done): void { ### Type Definition of FastifyInstance + TypeProvider -When working with modules one has to make use of `FastifyInstance` with Type -Provider generics. See the example below: +When working with modules, use `FastifyInstance` with Type Provider generics. +See the example below: ```ts // index.ts From 3db024edc7f8284b96fdd705126317724bc1ca13 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 22 Jan 2025 11:42:34 +0000 Subject: [PATCH 0915/1295] docs(reference/errors): conciseness (#5963) --- docs/Reference/Errors.md | 103 +++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 53 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 97151d567a0..038c3d83edf 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -102,8 +102,8 @@ #### Uncaught Errors -In Node.js, uncaught errors are likely to cause memory leaks, file descriptor -leaks, and other major production issues. +In Node.js, uncaught errors can cause memory leaks, file descriptor leaks, and +other major production issues. [Domains](https://nodejs.org/en/docs/guides/domain-postmortem/) were a failed attempt to fix this. @@ -112,29 +112,28 @@ way to deal with them is to [crash](https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly). #### Catching Errors In Promises -If you are using promises, you should attach a `.catch()` handler synchronously. +When using promises, attach a `.catch()` handler synchronously. ### Errors In Fastify -Fastify follows an all-or-nothing approach and aims to be lean and optimal as -much as possible. The developer is responsible for making sure that the errors -are handled properly. +Fastify follows an all-or-nothing approach and aims to be lean and optimal. The +developer is responsible for ensuring errors are handled properly. #### Errors In Input Data -Most errors are a result of unexpected input data, so we recommend [validating -your input data against a JSON schema](./Validation-and-Serialization.md). +Most errors result from unexpected input data, so it is recommended to +[validate input data against a JSON schema](./Validation-and-Serialization.md). #### Catching Uncaught Errors In Fastify -Fastify tries to catch as many uncaught errors as it can without hindering +Fastify tries to catch as many uncaught errors as possible without hindering performance. This includes: 1. synchronous routes, e.g. `app.get('/', () => { throw new Error('kaboom') })` 2. `async` routes, e.g. `app.get('/', async () => { throw new Error('kaboom') })` -The error in both cases will be caught safely and routed to Fastify's default -error handler for a generic `500 Internal Server Error` response. +In both cases, the error will be caught safely and routed to Fastify's default +error handler, resulting in a generic `500 Internal Server Error` response. -To customize this behavior you should use +To customize this behavior, use [`setErrorHandler`](./Server.md#seterrorhandler). ### Errors In Fastify Lifecycle Hooks And A Custom Error Handler @@ -144,52 +143,50 @@ From the [Hooks documentation](./Hooks.md#manage-errors-from-a-hook): > `done()` and Fastify will automatically close the request and send the > appropriate error code to the user. -When a custom error handler has been defined through -[`setErrorHandler`](./Server.md#seterrorhandler), the custom error handler will -receive the error passed to the `done()` callback (or through other supported -automatic error handling mechanisms). If `setErrorHandler` has been used -multiple times to define multiple handlers, the error will be routed to the most -precedent handler defined within the error [encapsulation -context](./Encapsulation.md). Error handlers are fully encapsulated, so a -`setErrorHandler` call within a plugin will limit the error handler to that -plugin's context. +When a custom error handler is defined through +[`setErrorHandler`](./Server.md#seterrorhandler), it will receive the error +passed to the `done()` callback or through other supported automatic error +handling mechanisms. If `setErrorHandler` is used multiple times, the error will +be routed to the most precedent handler within the error +[encapsulation context](./Encapsulation.md). Error handlers are fully +encapsulated, so a `setErrorHandler` call within a plugin will limit the error +handler to that plugin's context. The root error handler is Fastify's generic error handler. This error handler will use the headers and status code in the `Error` object, if they exist. The headers and status code will not be automatically set if a custom error handler is provided. -Some things to consider in your custom error handler: +The following should be considered when using a custom error handler: -- you can `reply.send(data)`, which will behave as it would in [regular route - handlers](./Reply.md#senddata) +- `reply.send(data)` behaves as in [regular route handlers](./Reply.md#senddata) - objects are serialized, triggering the `preSerialization` lifecycle hook if - you have one defined - - strings, buffers, and streams are sent to the client, with appropriate - headers (no serialization) - -- You can throw a new error in your custom error handler - errors (new error or - the received error parameter re-thrown) - will call the parent `errorHandler`. - - `onError` hook will be triggered once only for the first error being thrown. - - an error will not be triggered twice from a lifecycle hook - Fastify - internally monitors the error invocation to avoid infinite loops for errors - thrown in the reply phases of the lifecycle. (those after the route handler) - -When utilizing Fastify's custom error handling through [`setErrorHandler`](./Server.md#seterrorhandler), -you should be aware of how errors are propagated between custom and default -error handlers. - -If a plugin's error handler re-throws an error, and the error is not an -instance of [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) -(as seen in the `/bad` route in the following example), it will not propagate -to the parent context error handler. Instead, it will be caught by the default -error handler. - -To ensure consistent error handling, it is recommended to throw instances of -`Error`. For instance, in the following example, replacing `throw 'foo'` with -`throw new Error('foo')` in the `/bad` route ensures that errors propagate through -the custom error handling chain as intended. This practice helps avoid potential -pitfalls when working with custom error handling in Fastify. + defined + - strings, buffers, and streams are sent to the client with appropriate headers + (no serialization) + +- Throwing a new error in a custom error handler will call the parent + `errorHandler`. + - The `onError` hook will be triggered once for the first error thrown + - An error will not be triggered twice from a lifecycle hook. Fastify + internally monitors error invocation to avoid infinite loops for errors + thrown in the reply phases of the lifecycle (those after the route handler) + +When using Fastify's custom error handling through +[`setErrorHandler`](./Server.md#seterrorhandler), be aware of how errors are +propagated between custom and default error handlers. + +If a plugin's error handler re-throws an error that is not an instance of +[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error), +it will not propagate to the parent context error handler. Instead, it will be +caught by the default error handler. This can be seen in the `/bad` route of the +example below. + +To ensure consistent error handling, throw instances of `Error`. For example, +replace `throw 'foo'` with `throw new Error('foo')` in the `/bad` route to +ensure errors propagate through the custom error handling chain as intended. +This practice helps avoid potential pitfalls when working with custom error +handling in Fastify. For example: ```js @@ -242,7 +239,7 @@ You can access `errorCodes` for mapping: // ESM import { errorCodes } from 'fastify' -// CommonJs +// CommonJS const errorCodes = require('fastify').errorCodes ``` @@ -267,7 +264,7 @@ fastify.setErrorHandler(function (error, request, reply) { // Send error response reply.status(500).send({ ok: false }) } else { - // fastify will use parent error handler to handle this + // Fastify will use parent error handler to handle this reply.send(error) } }) @@ -282,7 +279,7 @@ fastify.listen({ port: 3000 }, function (err, address) { }) ``` -Below is a table with all the error codes that Fastify uses. +Below is a table with all the error codes used by Fastify. | Code | Description | How to solve | Discussion | |------|-------------|--------------|------------| From 61626f136ce02b23b002d9aac69f6cae48be2c9d Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 23 Jan 2025 10:54:43 +0000 Subject: [PATCH 0916/1295] docs(reference/logging): conciseness improvements (#5958) --- docs/Reference/Logging.md | 111 +++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index b837a6b8b0b..e7e4ac72d9b 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -2,17 +2,18 @@ ## Logging -### Enable logging -Logging is disabled by default, and you can enable it by passing `{ logger: true -}` or `{ logger: { level: 'info' } }` when you create a Fastify instance. Note -that if the logger is disabled, it is impossible to enable it at runtime. We use -[abstract-logging](https://www.npmjs.com/package/abstract-logging) for this -purpose. +### Enable Logging +Logging is disabled by default. Enable it by passing `{ logger: true }` or +`{ logger: { level: 'info' } }` when creating a Fastify instance. Note that if +the logger is disabled, it cannot be enabled at runtime. +[abstract-logging](https://www.npmjs.com/package/abstract-logging) is used for +this purpose. As Fastify is focused on performance, it uses [pino](https://github.com/pinojs/pino) as its logger, with the default log -level, when enabled, set to `'info'`. +level set to `'info'` when enabled. +#### Basic logging setup Enabling the production JSON logger: ```js @@ -21,8 +22,9 @@ const fastify = require('fastify')({ }) ``` -Enabling the logger with appropriate configuration for both local development -and production and test environment requires a bit more configuration: +#### Environment-Specific Configuration +Enabling the logger with appropriate configuration for local development, +production, and test environments requires more configuration: ```js const envToLogger = { @@ -42,11 +44,11 @@ const fastify = require('fastify')({ logger: envToLogger[environment] ?? true // defaults to true if no entry matches in the map }) ``` -⚠️ `pino-pretty` needs to be installed as a dev dependency, it is not included +⚠️ `pino-pretty` needs to be installed as a dev dependency. It is not included by default for performance reasons. ### Usage -You can use the logger like this in your route handlers: +The logger can be used in route handlers as follows: ```js fastify.get('/', options, function (request, reply) { @@ -55,16 +57,16 @@ fastify.get('/', options, function (request, reply) { }) ``` -You can trigger new logs outside route handlers by using the Pino instance from -the Fastify instance: +Trigger new logs outside route handlers using the Pino instance from the Fastify +instance: ```js fastify.log.info('Something important happened!'); ``` -If you want to pass some options to the logger, just pass them to Fastify. -You can find all available options in the -[Pino documentation](https://github.com/pinojs/pino/blob/master/docs/api.md#options). -If you want to specify a file destination, use: +#### Passing Logger Options +To pass options to the logger, provide them to Fastify. See the +[Pino documentation](https://github.com/pinojs/pino/blob/master/docs/api.md#options) +for available options. To specify a file destination, use: ```js const fastify = require('fastify')({ @@ -80,8 +82,8 @@ fastify.get('/', options, function (request, reply) { }) ``` -If you want to pass a custom stream to the Pino instance, just add a stream -field to the logger object. +To pass a custom stream to the Pino instance, add a `stream` field to the logger +object: ```js const split = require('split2') @@ -95,19 +97,21 @@ const fastify = require('fastify')({ }) ``` - +### Advanced Logger Configuration + +#### Request ID Tracking By default, Fastify adds an ID to every request for easier tracking. If the -requestIdHeader-option is set and the corresponding header is present than -its value is used, otherwise a new incremental ID is generated. See Fastify -Factory [`requestIdHeader`](./Server.md#factory-request-id-header) and Fastify -Factory [`genReqId`](./Server.md#genreqid) for customization options. +`requestIdHeader` option is set and the corresponding header is present, its +value is used; otherwise, a new incremental ID is generated. See Fastify Factory +[`requestIdHeader`](./Server.md#factory-request-id-header) and Fastify Factory +[`genReqId`](./Server.md#genreqid) for customization options. -The default logger is configured with a set of standard serializers that -serialize objects with `req`, `res`, and `err` properties. The object received -by `req` is the Fastify [`Request`](./Request.md) object, while the object -received by `res` is the Fastify [`Reply`](./Reply.md) object. This behavior -can be customized by specifying custom serializers. +#### Serializers +The default logger uses standard serializers for objects with `req`, `res`, and +`err` properties. The `req` object is the Fastify [`Request`](./Request.md) +object, and the `res` object is the Fastify [`Reply`](./Reply.md) object. This +behavior can be customized with custom serializers. ```js const fastify = require('fastify')({ @@ -121,7 +125,7 @@ const fastify = require('fastify')({ }) ``` For example, the response payload and headers could be logged using the approach -below (even if it is *not recommended*): +below (not recommended): ```js const fastify = require('fastify')({ @@ -142,10 +146,9 @@ const fastify = require('fastify')({ url: request.url, path: request.routeOptions.url, parameters: request.params, - // Including the headers in the log could be in violation - // of privacy laws, e.g. GDPR. You should use the "redact" option to - // remove sensitive fields. It could also leak authentication data in - // the logs. + // Including headers in the log could violate privacy laws, + // e.g., GDPR. Use the "redact" option to remove sensitive + // fields. It could also leak authentication data in the logs. headers: request.headers }; } @@ -154,11 +157,11 @@ const fastify = require('fastify')({ }); ``` -**Note**: In certain cases, the [`Reply`](./Reply.md) object passed to the `res` +**Note**: In some cases, the [`Reply`](./Reply.md) object passed to the `res` serializer cannot be fully constructed. When writing a custom `res` serializer, -it is necessary to check for the existence of any properties on `reply` aside -from `statusCode`, which is always present. For example, the existence of -`getHeaders` must be verified before it can be called: +check for the existence of any properties on `reply` aside from `statusCode`, +which is always present. For example, verify the existence of `getHeaders` +before calling it: ```js const fastify = require('fastify')({ @@ -170,7 +173,7 @@ const fastify = require('fastify')({ res (reply) { // The default return { - statusCode: reply.statusCode + statusCode: reply.statusCode, headers: typeof reply.getHeaders === 'function' ? reply.getHeaders() : {} @@ -182,10 +185,10 @@ const fastify = require('fastify')({ ``` **Note**: The body cannot be serialized inside a `req` method because the -request is serialized when we create the child logger. At that time, the body is -not yet parsed. +request is serialized when the child logger is created. At that time, the body +is not yet parsed. -See an approach to log `req.body` +See the following approach to log `req.body`: ```js app.addHook('preHandler', function (req, reply, done) { @@ -196,18 +199,18 @@ app.addHook('preHandler', function (req, reply, done) { }) ``` -**Note**: Care should be taken to ensure serializers never throw, as an error -thrown from a serializer has the potential to cause the Node process to exit. -See the [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) -on serializers for more information. +**Note**: Ensure serializers never throw errors, as this can cause the Node +process to exit. See the +[Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) for more +information. *Any logger other than Pino will ignore this option.* -You can also supply your own logger instance. Instead of passing configuration -options, pass the instance as `loggerInstance`. The logger you supply must -conform to the Pino interface; that is, it must have the following methods: -`info`, `error`, `debug`, `fatal`, `warn`, `trace`, `silent`, `child` and a -string property `level`. +### Using Custom Loggers +A custom logger instance can be supplied by passing it as `loggerInstance`. The +logger must conform to the Pino interface, with methods: `info`, `error`, +`debug`, `fatal`, `warn`, `trace`, `silent`, `child`, and a string property +`level`. Example: @@ -226,11 +229,11 @@ fastify.get('/', function (request, reply) { *The logger instance for the current request is available in every part of the [lifecycle](./Lifecycle.md).* -## Log Redaction +### Log Redaction [Pino](https://getpino.io) supports low-overhead log redaction for obscuring -values of specific properties in recorded logs. As an example, we might want to -log all the HTTP headers minus the `Authorization` header for security concerns: +values of specific properties in recorded logs. For example, log all HTTP +headers except the `Authorization` header for security: ```js const fastify = Fastify({ From 57519b26e4c87a5fa98a4a380a19857df83b92fb Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 23 Jan 2025 10:59:26 +0000 Subject: [PATCH 0917/1295] docs(reference/request): conciseness improvements (#5965) --- docs/Reference/Request.md | 185 ++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 97 deletions(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index eb62cf32ac0..009c0f2ff8c 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -4,74 +4,71 @@ The first parameter of the handler function is `Request`. Request is a core Fastify object containing the following fields: -- `query` - the parsed querystring, its format is specified by - [`querystringParser`](./Server.md#querystringparser) -- `body` - the request payload, see [Content-Type - Parser](./ContentTypeParser.md) for details on what request payloads Fastify - natively parses and how to support other content types -- `params` - the params matching the URL -- [`headers`](#headers) - the headers getter and setter -- `raw` - the incoming HTTP request from Node core -- `server` - The Fastify server instance, scoped to the current [encapsulation - context](./Encapsulation.md) -- `id` - the request ID -- `log` - the logger instance of the incoming request -- `ip` - the IP address of the incoming request -- `ips` - an array of the IP addresses, ordered from closest to furthest, in the +- `query` - The parsed querystring, its format is specified by + [`querystringParser`](./Server.md#querystringparser). +- `body` - The request payload, see [Content-Type Parser](./ContentTypeParser.md) + for details on what request payloads Fastify natively parses and how to support + other content types. +- `params` - The params matching the URL. +- [`headers`](#headers) - The headers getter and setter. +- `raw` - The incoming HTTP request from Node core. +- `server` - The Fastify server instance, scoped to the current + [encapsulation context](./Encapsulation.md). +- `id` - The request ID. +- `log` - The logger instance of the incoming request. +- `ip` - The IP address of the incoming request. +- `ips` - An array of the IP addresses, ordered from closest to furthest, in the `X-Forwarded-For` header of the incoming request (only when the - [`trustProxy`](./Server.md#factory-trust-proxy) option is enabled) -- `host` - the host of the incoming request (derived from `X-Forwarded-Host` + [`trustProxy`](./Server.md#factory-trust-proxy) option is enabled). +- `host` - The host of the incoming request (derived from `X-Forwarded-Host` header when the [`trustProxy`](./Server.md#factory-trust-proxy) option is - enabled). For HTTP/2 compatibility it returns `:authority` if no host header - exists. The host header may return an empty string if `requireHostHeader` - is false, not provided with HTTP/1.0, or removed by schema validation. -- `hostname` - the hostname derived from the `host` property of - the incoming request -- `port` - the port from the `host` property, which may refer to - the port the server is listening on -- `protocol` - the protocol of the incoming request (`https` or `http`) -- `method` - the method of the incoming request -- `url` - the URL of the incoming request -- `originalUrl` - similar to `url`, this allows you to access the - original `url` in case of internal re-routing -- `is404` - true if request is being handled by 404 handler, false if it is not -- `socket` - the underlying connection of the incoming request -- `context` - Deprecated, use `request.routeOptions.config` instead. -A Fastify internal object. You should not use -it directly or modify it. It is useful to access one special key: + enabled). For HTTP/2 compatibility, it returns `:authority` if no host header + exists. The host header may return an empty string if `requireHostHeader` is + `false`, not provided with HTTP/1.0, or removed by schema validation. +- `hostname` - The hostname derived from the `host` property of the incoming request. +- `port` - The port from the `host` property, which may refer to the port the + server is listening on. +- `protocol` - The protocol of the incoming request (`https` or `http`). +- `method` - The method of the incoming request. +- `url` - The URL of the incoming request. +- `originalUrl` - Similar to `url`, allows access to the original `url` in + case of internal re-routing. +- `is404` - `true` if request is being handled by 404 handler, `false` otherwise. +- `socket` - The underlying connection of the incoming request. +- `context` - Deprecated, use `request.routeOptions.config` instead. A Fastify + internal object. Do not use or modify it directly. It is useful to access one + special key: - `context.config` - The route [`config`](./Routes.md#routes-config) object. -- `routeOptions` - The route [`option`](./Routes.md#routes-options) object - - `bodyLimit` - either server limit or route limit - - `config` - the [`config`](./Routes.md#routes-config) object for this route - - `method` - the http method for the route - - `url` - the path of the URL to match this route - - `handler` - the handler for this route - - `attachValidation` - attach `validationError` to request - (if there is a schema defined) - - `logLevel` - log level defined for this route - - `schema` - the JSON schemas definition for this route - - `version` - a semver compatible string that defines the version of the endpoint - - `exposeHeadRoute` - creates a sibling HEAD route for any GET routes - - `prefixTrailingSlash` - string used to determine how to handle passing / +- `routeOptions` - The route [`option`](./Routes.md#routes-options) object. + - `bodyLimit` - Either server limit or route limit. + - `config` - The [`config`](./Routes.md#routes-config) object for this route. + - `method` - The HTTP method for the route. + - `url` - The path of the URL to match this route. + - `handler` - The handler for this route. + - `attachValidation` - Attach `validationError` to request (if there is + a schema defined). + - `logLevel` - Log level defined for this route. + - `schema` - The JSON schemas definition for this route. + - `version` - A semver compatible string that defines the version of the endpoint. + - `exposeHeadRoute` - Creates a sibling HEAD route for any GET routes. + - `prefixTrailingSlash` - String used to determine how to handle passing `/` as a route with a prefix. - [.getValidationFunction(schema | httpPart)](#getvalidationfunction) - - Returns a validation function for the specified schema or http part, - if any of either are set or cached. + Returns a validation function for the specified schema or HTTP part, if + set or cached. - [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema) - - Compiles the specified schema and returns a validation function - using the default (or customized) `ValidationCompiler`. - The optional `httpPart` is forwarded to the `ValidationCompiler` - if provided, defaults to `null`. + Compiles the specified schema and returns a validation function using the + default (or customized) `ValidationCompiler`. The optional `httpPart` is + forwarded to the `ValidationCompiler` if provided, defaults to `null`. - [.validateInput(data, schema | httpPart, [httpPart])](#validate) - - Validates the specified input by using the specified - schema and returns the serialized payload. If the optional - `httpPart` is provided, the function will use the serializer - function given for that HTTP Status Code. Defaults to `null`. + Validates the input using the specified schema and returns the serialized + payload. If `httpPart` is provided, the function uses the serializer for + that HTTP Status Code. Defaults to `null`. ### Headers -The `request.headers` is a getter that returns an Object with the headers of the -incoming request. You can set custom headers like this: +The `request.headers` is a getter that returns an object with the headers of the +incoming request. Set custom headers as follows: ```js request.headers = { @@ -80,14 +77,14 @@ request.headers = { } ``` -This operation will add to the request headers the new values that can be read -calling `request.headers.bar`. Moreover, you can still access the standard -request's headers with the `request.raw.headers` property. +This operation adds new values to the request headers, accessible via +`request.headers.bar`. Standard request headers remain accessible via +`request.raw.headers`. -> Note: For performance reason on `not found` route, you may see that we will -add an extra property `Symbol('fastify.RequestAcceptVersion')` on the headers. +For performance reasons, `Symbol('fastify.RequestAcceptVersion')` may be added +to headers on `not found` routes. -> Note: Using schema validation may mutate the `request.headers` and +> Note: Schema validation may mutate the `request.headers` and `request.raw.headers` objects, causing the headers to become empty. ```js @@ -122,13 +119,12 @@ fastify.post('/:params', options, function (request, reply) { ### .getValidationFunction(schema | httpPart) -By calling this function using a provided `schema` or `httpPart`, -it will return a `validation` function that can be used to -validate diverse inputs. It returns `undefined` if no -serialization function was found using either of the provided inputs. +By calling this function with a provided `schema` or `httpPart`, it returns a +`validation` function to validate diverse inputs. It returns `undefined` if no +serialization function is found using the provided inputs. -This function has property errors. Errors encountered during the last validation -are assigned to errors +This function has an `errors` property. Errors encountered during the last +validation are assigned to `errors`. ```js const validate = request @@ -151,24 +147,23 @@ console.log(validate({ foo: 0.5 })) // false console.log(validate.errors) // validation errors ``` -See [.compileValidationSchema(schema, [httpStatus])](#compilevalidationschema) -for more information on how to compile validation function. +See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema) +for more information on compiling validation schemas. ### .compileValidationSchema(schema, [httpPart]) -This function will compile a validation schema and -return a function that can be used to validate data. -The function returned (a.k.a. _validation function_) is compiled -by using the provided [`SchemaController#ValidationCompiler`](./Server.md#schema-controller). -A `WeakMap` is used to cache this, reducing compilation calls. +This function compiles a validation schema and returns a function to validate data. +The returned function (a.k.a. _validation function_) is compiled using the provided +[`SchemaController#ValidationCompiler`](./Server.md#schema-controller). A `WeakMap` +is used to cache this, reducing compilation calls. -The optional parameter `httpPart`, if provided, is forwarded directly -the `ValidationCompiler`, so it can be used to compile the validation -function if a custom `ValidationCompiler` is provided for the route. +The optional parameter `httpPart`, if provided, is forwarded to the +`ValidationCompiler`, allowing it to compile the validation function if a custom +`ValidationCompiler` is provided for the route. -This function has property errors. Errors encountered during the last validation -are assigned to errors +This function has an `errors` property. Errors encountered during the last +validation are assigned to `errors`. ```js const validate = request @@ -198,16 +193,13 @@ console.log(validate({ hello: 'world' })) // false console.log(validate.errors) // validation errors ``` -Note that you should be careful when using this function, as it will cache -the compiled validation functions based on the schema provided. If the -schemas provided are mutated or changed, the validation functions will not -detect that the schema has been altered and for instance it will reuse the -previously compiled validation function, as the cache is based on -the reference of the schema (Object) previously provided. +Be careful when using this function, as it caches compiled validation functions +based on the provided schema. If schemas are mutated or changed, the validation +functions will not detect the alterations and will reuse the previously compiled +validation function, as the cache is based on the schema's reference. -If there is a need to change the properties of a schema, always opt to create -a totally new schema (object), otherwise the implementation will not benefit from -the cache mechanism. +If schema properties need to be changed, create a new schema object to benefit +from the cache mechanism. Using the following schema as an example: ```js @@ -248,12 +240,11 @@ console.log(newValidate === validate) // false ### .validateInput(data, [schema | httpStatus], [httpStatus]) -This function will validate the input based on the provided schema, -or HTTP part passed. If both are provided, the `httpPart` parameter -will take precedence. +This function validates the input based on the provided schema or HTTP part. If +both are provided, the `httpPart` parameter takes precedence. -If there is not a validation function for a given `schema`, a new validation -function will be compiled, forwarding the `httpPart` if provided. +If no validation function exists for a given `schema`, a new validation function +will be compiled, forwarding the `httpPart` if provided. ```js request @@ -285,4 +276,4 @@ request ``` See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema) -for more information on how to compile validation schemas. +for more information on compiling validation schemas. From 072f29250b48809d0e9a8fd8d39e88c413162a49 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 23 Jan 2025 11:00:46 +0000 Subject: [PATCH 0918/1295] docs(ecosystem): add `@fastify/otel` to core list (#5967) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 043c1a85f3d..dd88ceefa6b 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -86,6 +86,8 @@ section. [`simple-oauth2`](https://github.com/lelylan/simple-oauth2). - [`@fastify/one-line-logger`](https://github.com/fastify/one-line-logger) Formats Fastify's logs into a nice one-line message. +- [`@fastify/otel`](https://github.com/fastify/otel) OpenTelemetry + instrumentation library. - [`@fastify/passport`](https://github.com/fastify/fastify-passport) Use Passport strategies to authenticate requests and protect route. - [`@fastify/postgres`](https://github.com/fastify/fastify-postgres) Fastify From 30f788aa1f6c60c4f44990eef6501d5db100a892 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 23 Jan 2025 12:07:34 +0000 Subject: [PATCH 0919/1295] docs(reference/validation): conciseness improvements (#5964) --- .../Reference/Validation-and-Serialization.md | 281 ++++++++---------- 1 file changed, 128 insertions(+), 153 deletions(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 5f1c9f91847..565f0c89e00 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -1,67 +1,56 @@

Fastify

## Validation and Serialization -Fastify uses a schema-based approach, and even if it is not mandatory we -recommend using [JSON Schema](https://json-schema.org/) to validate your routes -and serialize your outputs. Internally, Fastify compiles the schema into a -highly performant function. +Fastify uses a schema-based approach. We recommend using +[JSON Schema](https://json-schema.org/) to validate routes and serialize outputs. +Fastify compiles the schema into a highly performant function. -Validation will only be attempted if the content type is `application/json`, as -described in the documentation for the [content type -parser](./ContentTypeParser.md). +Validation is only attempted if the content type is `application/json`. -All the examples in this section are using the [JSON Schema Draft -7](https://json-schema.org/specification-links.html#draft-7) specification. +All examples use the +[JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7) +specification. > ## ⚠ Security Notice -> Treat the schema definition as application code. Validation and serialization -> features dynamically evaluate code with `new Function()`, which is not safe to -> use with user-provided schemas. See [Ajv](https://npm.im/ajv) and -> [fast-json-stringify](https://npm.im/fast-json-stringify) for more details. +> Treat schema definitions as application code. Validation and serialization +> features use `new Function()`, which is unsafe with user-provided schemas. See +> [Ajv](https://npm.im/ajv) and +> [fast-json-stringify](https://npm.im/fast-json-stringify) for details. > -> Regardless the [`$async` Ajv -> feature](https://ajv.js.org/guide/async-validation.html) is supported -> by Fastify, it should not be used as -> part of the first validation strategy. This option is used to access Databases -> and reading them during the validation process may lead to Denial of Service -> Attacks to your application. If you need to run `async` tasks, use [Fastify's -> hooks](./Hooks.md) instead after validation completes, such as `preHandler`. - +> Whilst Fastify supports the +> [`$async` Ajv feature](https://ajv.js.org/guide/async-validation.html), +> it should not be used for initial validation. Accessing databases during +> validation may lead to Denial of Service attacks. Use +> [Fastify's hooks](./Hooks.md) like `preHandler` for `async` tasks after validation. ### Core concepts -The validation and the serialization tasks are processed by two different, and -customizable, actors: -- [Ajv v8](https://www.npmjs.com/package/ajv) for the validation of a request +Validation and serialization are handled by two customizable dependencies: +- [Ajv v8](https://www.npmjs.com/package/ajv) for request validation - [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) for - the serialization of a response's body + response body serialization -These two separate entities share only the JSON schemas added to Fastify's -instance through `.addSchema(schema)`. +These dependencies share only the JSON schemas added to Fastify's instance via +`.addSchema(schema)`. #### Adding a shared schema -Thanks to the `addSchema` API, you can add multiple schemas to the Fastify -instance and then reuse them in multiple parts of your application. As usual, -this API is encapsulated. +The `addSchema` API allows adding multiple schemas to the Fastify instance for +reuse throughout the application. This API is encapsulated. -The shared schemas can be reused through the JSON Schema +Shared schemas can be reused with the JSON Schema [**`$ref`**](https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8) -keyword. Here is an overview of _how_ references work: +keyword. Here is an overview of how references work: -+ `myField: { $ref: '#foo' }` will search for field with `$id: '#foo'` inside the ++ `myField: { $ref: '#foo' }` searches for `$id: '#foo'` in the current schema ++ `myField: { $ref: '#/definitions/foo' }` searches for `definitions.foo` in the current schema -+ `myField: { $ref: '#/definitions/foo' }` will search for field - `definitions.foo` inside the current schema -+ `myField: { $ref: 'http://url.com/sh.json#' }` will search for a shared schema - added with `$id: 'http://url.com/sh.json'` -+ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo' }` will search for - a shared schema added with `$id: 'http://url.com/sh.json'` and will use the - field `definitions.foo` -+ `myField: { $ref: 'http://url.com/sh.json#foo' }` will search for a shared - schema added with `$id: 'http://url.com/sh.json'` and it will look inside of - it for object with `$id: '#foo'` - ++ `myField: { $ref: 'http://url.com/sh.json#' }` searches for a shared schema + with `$id: 'http://url.com/sh.json'` ++ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo' }` searches for a + shared schema with `$id: 'http://url.com/sh.json'` and uses `definitions.foo` ++ `myField: { $ref: 'http://url.com/sh.json#foo' }` searches for a shared schema + with `$id: 'http://url.com/sh.json'` and looks for `$id: '#foo'` within it **Simple usage:** @@ -108,9 +97,9 @@ fastify.post('/', { #### Retrieving the shared schemas -If the validator and the serializer are customized, the `.addSchema` method will -not be useful since the actors are no longer controlled by Fastify. To access -the schemas added to the Fastify instance, you can simply use `.getSchemas()`: +If the validator and serializer are customized, `.addSchema` is not useful since +Fastify no longer controls them. To access schemas added to the Fastify instance, +use `.getSchemas()`: ```js fastify.addSchema({ @@ -125,8 +114,8 @@ const mySchemas = fastify.getSchemas() const mySchema = fastify.getSchema('schemaId') ``` -As usual, the function `getSchemas` is encapsulated and returns the shared -schemas available in the selected scope: +The `getSchemas` function is encapsulated and returns shared schemas available +in the selected scope: ```js fastify.addSchema({ $id: 'one', my: 'hello' }) @@ -150,25 +139,22 @@ fastify.register((instance, opts, done) => { ### Validation -The route validation internally relies upon [Ajv -v8](https://www.npmjs.com/package/ajv) which is a high-performance JSON Schema -validator. Validating the input is very easy: just add the fields that you need -inside the route schema, and you are done! - -The supported validations are: -- `body`: validates the body of the request if it is a POST, PUT, or PATCH - method. +Route validation relies on [Ajv v8](https://www.npmjs.com/package/ajv), a +high-performance JSON Schema validator. To validate input, add the required +fields to the route schema. + +Supported validations include: +- `body`: validates the request body for POST, PUT, or PATCH methods. - `querystring` or `query`: validates the query string. -- `params`: validates the route params. +- `params`: validates the route parameters. - `headers`: validates the request headers. -All the validations can be a complete JSON Schema object (with a `type` property -of `'object'` and a `'properties'` object containing parameters) or a simpler -variation in which the `type` and `properties` attributes are forgone and the -parameters are listed at the top level (see the example below). +Validations can be a complete JSON Schema object with a `type` of `'object'` and +a `'properties'` object containing parameters, or a simpler variation listing +parameters at the top level. -> ℹ If you need to use the latest version of Ajv (v8) you should read how to do -> it in the [`schemaController`](./Server.md#schema-controller) section. +> ℹ For using the latest Ajv (v8), refer to the +> [`schemaController`](./Server.md#schema-controller) section. Example: ```js @@ -257,9 +243,9 @@ fastify.post('/the/url', { }, handler) ``` -*Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) the values -to the types specified in your schema `type` keywords, both to pass the -validation and to use the correctly typed data afterwards.* +Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) values to +the types specified in the schema `type` keywords, both to pass validation and +to use the correctly typed data afterwards. The Ajv default configuration in Fastify supports coercing array parameters in `querystring`. Example: @@ -294,11 +280,11 @@ curl -X GET "http://localhost:3000/?ids=1 {"params":{"ids":["1"]}} ``` -You can also specify a custom schema validator for each parameter type (body, +A custom schema validator can be specified for each parameter type (body, querystring, params, headers). -For example, the following code disable type coercion only for the `body` -parameters, changing the ajv default options: +For example, the following code disables type coercion only for the `body` +parameters, changing the Ajv default options: ```js const schemaCompilers = { @@ -336,16 +322,15 @@ server.setValidatorCompiler(req => { }) ``` -For further information see [here](https://ajv.js.org/coercion.html) +For more information, see [Ajv Coercion](https://ajv.js.org/coercion.html). #### Ajv Plugins -You can provide a list of plugins you want to use with the default `ajv` -instance. Note that the plugin must be **compatible with the Ajv version shipped -within Fastify**. +A list of plugins can be provided for use with the default `ajv` instance. +Ensure the plugin is **compatible with the Ajv version shipped within Fastify**. -> Refer to [`ajv options`](./Server.md#ajv) to check plugins format +> Refer to [`ajv options`](./Server.md#ajv) to check plugins format. ```js const fastify = require('fastify')({ @@ -406,11 +391,10 @@ fastify.post('/foo', { #### Validator Compiler -The `validatorCompiler` is a function that returns a function that validates the -body, URL parameters, headers, and query string. The default -`validatorCompiler` returns a function that implements the -[ajv](https://ajv.js.org/) validation interface. Fastify uses it internally to -speed the validation up. +The `validatorCompiler` is a function that returns a function to validate the +body, URL parameters, headers, and query string. The default `validatorCompiler` +returns a function that implements the [ajv](https://ajv.js.org/) validation +interface. Fastify uses it internally to speed up validation. Fastify's [baseline ajv configuration](https://github.com/fastify/ajv-compiler#ajv-configuration) is: @@ -428,11 +412,11 @@ configuration](https://github.com/fastify/ajv-compiler#ajv-configuration) is: } ``` -This baseline configuration can be modified by providing -[`ajv.customOptions`](./Server.md#factory-ajv) to your Fastify factory. +Modify the baseline configuration by providing +[`ajv.customOptions`](./Server.md#factory-ajv) to the Fastify factory. -If you want to change or set additional config options, you will need to create -your own instance and override the existing one like: +To change or set additional config options, create a custom instance and +override the existing one: ```js const fastify = require('fastify')() @@ -448,17 +432,16 @@ fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { return ajv.compile(schema) }) ``` -_**Note:** If you use a custom instance of any validator (even Ajv), you have to -add schemas to the validator instead of Fastify, since Fastify's default -validator is no longer used, and Fastify's `addSchema` method has no idea what -validator you are using._ +_**Note:** When using a custom validator instance, add schemas to the validator +instead of Fastify. Fastify's `addSchema` method will not recognize the custom +validator._ ##### Using other validation libraries -The `setValidatorCompiler` function makes it easy to substitute `ajv` with -almost any JavaScript validation library ([joi](https://github.com/hapijs/joi/), -[yup](https://github.com/jquense/yup/), ...) or a custom one: +The `setValidatorCompiler` function allows substituting `ajv` with other +JavaScript validation libraries like [joi](https://github.com/hapijs/joi/) or +[yup](https://github.com/jquense/yup/), or a custom one: ```js const Joi = require('joi') @@ -511,8 +494,8 @@ fastify.post('/the/url', { ##### .statusCode property -All validation errors will be added a `.statusCode` property set to `400`. This guarantees -that the default error handler will set the status code of the response to `400`. +All validation errors have a `.statusCode` property set to `400`, ensuring the +default error handler sets the response status code to `400`. ```js fastify.setErrorHandler(function (error, request, reply) { @@ -525,30 +508,27 @@ fastify.setErrorHandler(function (error, request, reply) { Fastify's validation error messages are tightly coupled to the default validation engine: errors returned from `ajv` are eventually run through the -`schemaErrorFormatter` function which is responsible for building human-friendly -error messages. However, the `schemaErrorFormatter` function is written with -`ajv` in mind. As a result, you may run into odd or incomplete error messages -when using other validation libraries. +`schemaErrorFormatter` function which builds human-friendly error messages. +However, the `schemaErrorFormatter` function is written with `ajv` in mind. +This may result in odd or incomplete error messages when using other validation +libraries. -To circumvent this issue, you have 2 main options : +To circumvent this issue, there are two main options: -1. make sure your validation function (returned by your custom `schemaCompiler`) - returns errors in the same structure and format as `ajv` (although this could - prove to be difficult and tricky due to differences between validation - engines) -2. or use a custom `errorHandler` to intercept and format your 'custom' - validation errors +1. Ensure the validation function (returned by the custom `schemaCompiler`) + returns errors in the same structure and format as `ajv`. +2. Use a custom `errorHandler` to intercept and format custom validation errors. -To help you in writing a custom `errorHandler`, Fastify adds 2 properties to all -validation errors: +Fastify adds two properties to all validation errors to help write a custom +`errorHandler`: * `validation`: the content of the `error` property of the object returned by - the validation function (returned by your custom `schemaCompiler`) -* `validationContext`: the 'context' (body, params, query, headers) where the + the validation function (returned by the custom `schemaCompiler`) +* `validationContext`: the context (body, params, query, headers) where the validation error occurred -A very contrived example of such a custom `errorHandler` handling validation -errors is shown below: +A contrived example of such a custom `errorHandler` handling validation errors +is shown below: ```js const errorHandler = (error, request, reply) => { @@ -560,9 +540,9 @@ const errorHandler = (error, request, reply) => { // check if we have a validation error if (validation) { response = { - // validationContext will be 'body' or 'params' or 'headers' or 'query' + // validationContext will be 'body', 'params', 'headers', or 'query' message: `A validation error occurred when validating the ${validationContext}...`, - // this is the result of your validation library... + // this is the result of the validation library... errors: validation } } else { @@ -581,12 +561,10 @@ const errorHandler = (error, request, reply) => { ### Serialization -Usually, you will send your data to the clients as JSON, and Fastify has a -powerful tool to help you, -[fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify), which -is used if you have provided an output schema in the route options. We encourage -you to use an output schema, as it can drastically increase throughput and help -prevent accidental disclosure of sensitive information. +Fastify uses [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) +to send data as JSON if an output schema is provided in the route options. Using +an output schema can drastically increase throughput and help prevent accidental +disclosure of sensitive information. Example: ```js @@ -605,9 +583,8 @@ const schema = { fastify.post('/the/url', { schema }, handler) ``` -As you can see, the response schema is based on the status code. If you want to -use the same schema for multiple status codes, you can use `'2xx'` or `default`, -for example: +The response schema is based on the status code. To use the same schema for +multiple status codes, use `'2xx'` or `default`, for example: ```js const schema = { response: { @@ -636,7 +613,7 @@ const schema = { fastify.post('/the/url', { schema }, handler) ``` -You can even have a specific response schema for different content types. +A specific response schema can be defined for different content types. For example: ```js const schema = { @@ -688,10 +665,9 @@ fastify.post('/url', { schema }, handler) #### Serializer Compiler -The `serializerCompiler` is a function that returns a function that must return -a string from an input object. When you define a response JSON Schema, you can -change the default serialization method by providing a function to serialize -every route where you do. +The `serializerCompiler` returns a function that must return a string from an +input object. When defining a response JSON Schema, change the default +serialization method by providing a function to serialize each route. ```js fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => { @@ -716,13 +692,13 @@ fastify.get('/user', { }) ``` -*If you need a custom serializer in a very specific part of your code, you can -set one with [`reply.serializer(...)`](./Reply.md#serializerfunc).* +*To set a custom serializer in a specific part of the code, use +[`reply.serializer(...)`](./Reply.md#serializerfunc).* ### Error Handling When schema validation fails for a request, Fastify will automatically return a -status 400 response including the result from the validator in the payload. As -an example, if you have the following schema for your route +status 400 response including the result from the validator in the payload. For +example, if the following schema is used for a route: ```js const schema = { @@ -736,8 +712,8 @@ const schema = { } ``` -and fail to satisfy it, the route will immediately return a response with the -following payload +If the request fails to satisfy the schema, the route will return a response +with the following payload: ```js { @@ -747,10 +723,9 @@ following payload } ``` -If you want to handle errors inside the route, you can specify the -`attachValidation` option for your route. If there is a _validation error_, the -`validationError` property of the request will contain the `Error` object with -the raw `validation` result as shown below +To handle errors inside the route, specify the `attachValidation` option. If +there is a validation error, the `validationError` property of the request will +contain the `Error` object with the raw validation result as shown below: ```js const fastify = Fastify() @@ -765,13 +740,13 @@ fastify.post('/', { schema, attachValidation: true }, function (req, reply) { #### `schemaErrorFormatter` -If you want to format errors yourself, you can provide a sync function that must -return an error as the `schemaErrorFormatter` option to Fastify when -instantiating. The context function will be the Fastify server instance. +To format errors, provide a sync function that returns an error as the +`schemaErrorFormatter` option when instantiating Fastify. The context function +will be the Fastify server instance. `errors` is an array of Fastify schema errors `FastifySchemaValidationError`. -`dataVar` is the currently validated part of the schema. (params | body | -querystring | headers). +`dataVar` is the currently validated part of the schema (params, body, +querystring, headers). ```js const fastify = Fastify({ @@ -789,8 +764,8 @@ fastify.setSchemaErrorFormatter(function (errors, dataVar) { }) ``` -You can also use [setErrorHandler](./Server.md#seterrorhandler) to define a -custom response for validation errors such as +Use [setErrorHandler](./Server.md#seterrorhandler) to define a custom response +for validation errors such as: ```js fastify.setErrorHandler(function (error, request, reply) { @@ -800,18 +775,18 @@ fastify.setErrorHandler(function (error, request, reply) { }) ``` -If you want a custom error response in the schema without headaches, and -quickly, take a look at +For custom error responses in the schema, see [`ajv-errors`](https://github.com/epoberezkin/ajv-errors). Check out the [example](https://github.com/fastify/example/blob/HEAD/validation-messages/custom-errors-messages.js) usage. -> Make sure to install version 1.0.1 of `ajv-errors`, because later versions of -> it are not compatible with AJV v6 (the version shipped by Fastify v3). + +> Install version 1.0.1 of `ajv-errors`, as later versions are not compatible +> with AJV v6 (the version shipped by Fastify v3). Below is an example showing how to add **custom error messages for each property** of a schema by supplying custom AJV options. Inline comments in the -schema below describe how to configure it to show a different error message for -each case: +schema describe how to configure it to show a different error message for each +case: ```js const fastify = Fastify({ @@ -862,8 +837,8 @@ fastify.post('/', { schema, }, (request, reply) => { }) ``` -If you want to return localized error messages, take a look at -[ajv-i18n](https://github.com/epoberezkin/ajv-i18n) +To return localized error messages, see +[ajv-i18n](https://github.com/epoberezkin/ajv-i18n). ```js const localize = require('ajv-i18n') @@ -897,8 +872,8 @@ fastify.setErrorHandler(function (error, request, reply) { ### JSON Schema support -JSON Schema provides utilities to optimize your schemas that, in conjunction -with Fastify's shared schema, let you reuse all your schemas easily. +JSON Schema provides utilities to optimize schemas. Combined with Fastify's +shared schema, all schemas can be easily reused. | Use Case | Validator | Serializer | |-----------------------------------|-----------|------------| From 4a51a82de75eefcc7887fbdea361e3134d49992f Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 23 Jan 2025 12:08:00 +0000 Subject: [PATCH 0920/1295] docs(reference/plugins): conciseness improvements (#5956) --- docs/Reference/Plugins.md | 124 +++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 70 deletions(-) diff --git a/docs/Reference/Plugins.md b/docs/Reference/Plugins.md index 57c22ab1661..76fd3ba40fa 100644 --- a/docs/Reference/Plugins.md +++ b/docs/Reference/Plugins.md @@ -1,21 +1,18 @@

Fastify

## Plugins -Fastify allows the user to extend its functionalities with plugins. A plugin can -be a set of routes, a server [decorator](./Decorators.md), or whatever. The API -that you will need to use one or more plugins, is `register`. - -By default, `register` creates a *new scope*, this means that if you make some -changes to the Fastify instance (via `decorate`), this change will not be -reflected by the current context ancestors, but only by its descendants. This -feature allows us to achieve plugin *encapsulation* and *inheritance*, in this -way we create a *directed acyclic graph* (DAG) and we will not have issues -caused by cross dependencies. - -You may have already seen in the [Getting -Started](../Guides/Getting-Started.md#your-first-plugin) guide how easy it is -to use this API: -``` +Fastify can be extended with plugins, which can be a set of routes, a server +[decorator](./Decorators.md), or other functionality. Use the `register` API to +add one or more plugins. + +By default, `register` creates a *new scope*, meaning changes to the Fastify +instance (via `decorate`) will not affect the current context ancestors, only +its descendants. This feature enables plugin *encapsulation* and *inheritance*, +creating a *directed acyclic graph* (DAG) and avoiding cross-dependency issues. + +The [Getting Started](../Guides/Getting-Started.md#your-first-plugin) guide +includes an example of using this API: +```js fastify.register(plugin, [options]) ``` @@ -33,10 +30,9 @@ Fastify specific options is: + [`logSerializers`](./Routes.md#custom-log-serializer) + [`prefix`](#route-prefixing-option) -**Note: Those options will be ignored when used with fastify-plugin** +These options will be ignored when used with fastify-plugin. -It is possible that Fastify will directly support other options in the future. -Thus, to avoid collisions, a plugin should consider namespacing its options. For +To avoid collisions, a plugin should consider namespacing its options. For example, a plugin `foo` might be registered like so: ```js @@ -49,8 +45,7 @@ fastify.register(require('fastify-foo'), { }) ``` -If collisions are not a concern, the plugin may simply accept the options object -as-is: +If collisions are not a concern, the plugin may accept the options object as-is: ```js fastify.register(require('fastify-foo'), { @@ -60,9 +55,8 @@ fastify.register(require('fastify-foo'), { }) ``` -The `options` parameter can also be a `Function` that will be evaluated at the -time the plugin is registered while giving access to the Fastify instance via -the first positional argument: +The `options` parameter can also be a `Function` evaluated at plugin registration, +providing access to the Fastify instance via the first argument: ```js const fp = require('fastify-plugin') @@ -77,40 +71,38 @@ fastify.register(fp((fastify, opts, done) => { fastify.register(require('fastify-foo'), parent => parent.foo_bar) ``` -The Fastify instance passed on to the function is the latest state of the -**external Fastify instance** the plugin was declared on, allowing access to -variables injected via [`decorate`](./Decorators.md) by preceding plugins -according to the **order of registration**. This is useful in case a plugin -depends on changes made to the Fastify instance by a preceding plugin i.e. -utilizing an existing database connection to wrap around it. +The Fastify instance passed to the function is the latest state of the **external +Fastify instance** the plugin was declared on, allowing access to variables +injected via [`decorate`](./Decorators.md) by preceding plugins according to the +**order of registration**. This is useful if a plugin depends on changes made to +the Fastify instance by a preceding plugin, such as utilizing an existing database +connection. -Keep in mind that the Fastify instance passed on to the function is the same as -the one that will be passed into the plugin, a copy of the external Fastify -instance rather than a reference. Any usage of the instance will behave the same -as it would if called within the plugins function i.e. if `decorate` is called, -the decorated variables will be available within the plugins function unless it -was wrapped with [`fastify-plugin`](https://github.com/fastify/fastify-plugin). +Keep in mind that the Fastify instance passed to the function is the same as the +one passed into the plugin, a copy of the external Fastify instance rather than a +reference. Any usage of the instance will behave the same as it would if called +within the plugin's function. For example, if `decorate` is called, the decorated +variables will be available within the plugin's function unless it was wrapped +with [`fastify-plugin`](https://github.com/fastify/fastify-plugin). #### Route Prefixing option -If you pass an option with the key `prefix` with a `string` value, Fastify will -use it to prefix all the routes inside the register, for more info check +If an option with the key `prefix` and a `string` value is passed, Fastify will +use it to prefix all the routes inside the register. For more info, check [here](./Routes.md#route-prefixing). -Be aware that if you wrap your routes with +Be aware that if routes are wrapped with [`fastify-plugin`](https://github.com/fastify/fastify-plugin), this option will -not work (there is a [workaround](./Routes.md#fastify-plugin) available). +not work (see the [workaround](./Routes.md#fastify-plugin)). #### Error handling -The error handling is done by -[avvio](https://github.com/mcollina/avvio#error-handling). +Error handling is done by [avvio](https://github.com/mcollina/avvio#error-handling). -As a general rule, it is highly recommended that you handle your errors in the -next `after` or `ready` block, otherwise you will get them inside the `listen` -callback. +As a general rule, handle errors in the next `after` or `ready` block, otherwise +they will be caught inside the `listen` callback. ```js fastify.register(require('my-plugin')) @@ -145,16 +137,15 @@ await fastify.ready() await fastify.listen({ port: 3000 }) ``` -*Note: Using `await` when registering a plugin loads the plugin -and the underlying dependency tree, "finalizing" the encapsulation process. -Any mutations to the plugin after it and its dependencies have been -loaded will not be reflected in the parent instance.* +Using `await` when registering a plugin loads the plugin and its dependencies, +"finalizing" the encapsulation process. Any mutations to the plugin after it and +its dependencies have been loaded will not be reflected in the parent instance. #### ESM support -ESM is supported as well from [Node.js -`v13.3.0`](https://nodejs.org/api/esm.html) and above! +ESM is supported from [Node.js `v13.3.0`](https://nodejs.org/api/esm.html) +and above. ```js // main.mjs @@ -179,9 +170,8 @@ export default plugin ### Create a plugin -Creating a plugin is very easy, you just need to create a function that takes -three parameters, the `fastify` instance, an `options` object, and the `done` -callback. +Creating a plugin is easy. Create a function that takes three parameters: the +`fastify` instance, an `options` object, and the `done` callback. Example: ```js @@ -205,28 +195,23 @@ module.exports = function (fastify, opts, done) { done() } ``` -Sometimes, you will need to know when the server is about to close, for example, -because you must close a connection to a database. To know when this is going to -happen, you can use the [`'onClose'`](./Hooks.md#on-close) hook. -Do not forget that `register` will always create a new Fastify scope, if you do -not need that, read the following section. +Remember, `register` always creates a new Fastify scope. If this is not needed, +read the following section. ### Handle the scope -If you are using `register` only for extending the functionality of the server -with [`decorate`](./Decorators.md), it is your responsibility to tell Fastify -not to create a new scope. Otherwise, your changes will not be accessible by the -user in the upper scope. +If `register` is used only to extend server functionality with +[`decorate`](./Decorators.md), tell Fastify not to create a new scope. Otherwise, +changes will not be accessible in the upper scope. -You have two ways to tell Fastify to avoid the creation of a new context: +There are two ways to avoid creating a new context: - Use the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module - Use the `'skip-override'` hidden property -We recommend using the `fastify-plugin` module, because it solves this problem -for you, and you can pass a version range of Fastify as a parameter that your -plugin will support. +Using the `fastify-plugin` module is recommended, as it solves this problem and +allows passing a version range of Fastify that the plugin will support: ```js const fp = require('fastify-plugin') @@ -238,10 +223,9 @@ module.exports = fp(function (fastify, opts, done) { Check the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) documentation to learn more about how to use this module. -If you do not use the `fastify-plugin` module, you can use the `'skip-override'` -hidden property, but we do not recommend it. If in the future the Fastify API -changes it will be your responsibility to update the module, while if you use -`fastify-plugin`, you can be sure about backward compatibility. +If not using `fastify-plugin`, the `'skip-override'` hidden property can be used, +but it is not recommended. Future Fastify API changes will be your responsibility +to update, whilst `fastify-plugin` ensures backward compatibility. ```js function yourPlugin (fastify, opts, done) { fastify.decorate('utility', function () {}) From af099471412301f1e00d237aa244aded94d7d015 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 23 Jan 2025 14:09:15 +0000 Subject: [PATCH 0921/1295] docs(reference/decorators): conciseness improvements (#5966) * docs(reference/decorators): conciseness improvements * docs(reference/decorators): revert --- docs/Reference/Decorators.md | 104 ++++++++++++++++------------------- 1 file changed, 47 insertions(+), 57 deletions(-) diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 735bd2386c5..452707c13b4 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -2,16 +2,15 @@ ## Decorators -The decorators API allows customization of the core Fastify objects, such as the -server instance itself and any request and reply objects used during the HTTP -request lifecycle. The decorators API can be used to attach any type of property -to the core objects, e.g. functions, plain objects, or native types. +The decorators API customizes core Fastify objects, such as the server instance +and any request and reply objects used during the HTTP request lifecycle. It +can attach any type of property to core objects, e.g., functions, plain +objects, or native types. -This API is *synchronous*. Attempting to define a decoration asynchronously -could result in the Fastify instance booting before the decoration completes its -initialization. To avoid this issue, and register an asynchronous decoration, -the `register` API, in combination with `fastify-plugin`, must be used instead. -To learn more, see the [Plugins](./Plugins.md) documentation. +This API is *synchronous*. Defining a decoration asynchronously could result in +the Fastify instance booting before the decoration completes. To register an +asynchronous decoration, use the `register` API with `fastify-plugin`. See the +[Plugins](./Plugins.md) documentation for more details. Decorating core objects with this API allows the underlying JavaScript engine to optimize the handling of server, request, and reply objects. This is @@ -35,9 +34,9 @@ fastify.get('/', function (req, reply) { }) ``` -Since the above example mutates the request object after it has already been -instantiated, the JavaScript engine must deoptimize access to the request -object. By using the decoration API this deoptimization is avoided: +The above example mutates the request object after instantiation, causing the +JavaScript engine to deoptimize access. Using the decoration API avoids this +deoptimization: ```js // Decorate request with a 'user' property @@ -54,17 +53,13 @@ fastify.get('/', (req, reply) => { }) ``` -Note that it is important to keep the initial shape of a decorated field as -close as possible to the value intended to be set dynamically in the future. -Initialize a decorator as a `''` if the intended value is a string, and as -`null` if it will be an object or a function. - -Remember this example works only with value types as reference types will -thrown and error during the fastify startup. See [decorateRequest](#decorate-request). - -See [JavaScript engine fundamentals: Shapes and Inline -Caches](https://mathiasbynens.be/notes/shapes-ics) for more information on this -topic. +Keep the initial shape of a decorated field close to its future dynamic value. +Initialize a decorator as `''` for strings and `null` for objects or functions. +This works only with value types; reference types will throw an error during +Fastify startup. See [decorateRequest](#decorate-request) and +[JavaScript engine fundamentals: Shapes +and Inline Caches](https://mathiasbynens.be/notes/shapes-ics) +for more information. ### Usage @@ -72,8 +67,7 @@ topic. #### `decorate(name, value, [dependencies])` -This method is used to customize the Fastify [server](./Server.md) -instance. +This method customizes the Fastify [server](./Server.md) instance. For example, to attach a new method to the server instance: @@ -83,7 +77,7 @@ fastify.decorate('utility', function () { }) ``` -As mentioned above, non-function values can be attached to the server instance as: +Non-function values can also be attached to the server instance: ```js fastify.decorate('conf', { @@ -118,9 +112,9 @@ fastify.get('/', async function (request, reply) { ``` The `dependencies` parameter is an optional list of decorators that the -decorator being defined relies upon. This list is simply a list of string names -of other decorators. In the following example, the "utility" decorator depends -upon "greet" and "hi" decorators: +decorator being defined relies upon. This list contains the names of other +decorators. In the following example, the "utility" decorator depends on the +"greet" and "hi" decorators: ```js async function greetDecorator (fastify, opts) { @@ -155,18 +149,17 @@ fastify.listen({ port: 3000 }, (err, address) => { }) ``` -Note: using an arrow function will break the binding of `this` to the -`FastifyInstance`. +Using an arrow function breaks the binding of `this` to +the `FastifyInstance`. -If a dependency is not satisfied, the `decorate` method will throw an exception. -The dependency check is performed before the server instance is booted. Thus, it -cannot occur during runtime. +If a dependency is not satisfied, the `decorate` method throws an exception. +The dependency check occurs before the server instance boots, not during +runtime. #### `decorateReply(name, value, [dependencies])` -As the name suggests, this API is used to add new methods/properties to the core -`Reply` object: +This API adds new methods/properties to the core `Reply` object: ```js fastify.decorateReply('utility', function () { @@ -174,21 +167,21 @@ fastify.decorateReply('utility', function () { }) ``` -Note: using an arrow function will break the binding of `this` to the Fastify +Using an arrow function will break the binding of `this` to the Fastify `Reply` instance. -Note: using `decorateReply` will throw and error if used with a reference type: +Using `decorateReply` will throw and error if used with a reference type: ```js // Don't do this fastify.decorateReply('foo', { bar: 'fizz'}) ``` -In this example, the reference of the object would be shared with all the requests -and **any mutation will impact all requests, potentially creating security -vulnerabilities or memory leaks**, so Fastify blocks it. +In this example, the object reference would be shared with all requests, and +**any mutation will impact all requests, potentially creating security +vulnerabilities or memory leaks**. Fastify blocks this. To achieve proper encapsulation across requests configure a new value for each -incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). Example: +incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). ```js const fp = require('fastify-plugin') @@ -208,8 +201,8 @@ See [`decorate`](#decorate) for information about the `dependencies` parameter. #### `decorateRequest(name, value, [dependencies])` -As above with [`decorateReply`](#decorate-reply), this API is used add new -methods/properties to the core `Request` object: +As with [`decorateReply`](#decorate-reply), this API adds new methods/properties +to the core `Request` object: ```js fastify.decorateRequest('utility', function () { @@ -217,18 +210,18 @@ fastify.decorateRequest('utility', function () { }) ``` -Note: using an arrow function will break the binding of `this` to the Fastify +Using an arrow function will break the binding of `this` to the Fastify `Request` instance. -Note: using `decorateRequest` will emit an error if used with a reference type: +Using `decorateRequest` will emit an error if used with a reference type: ```js // Don't do this fastify.decorateRequest('foo', { bar: 'fizz'}) ``` -In this example, the reference of the object would be shared with all the requests -and **any mutation will impact all requests, potentially creating security -vulnerabilities or memory leaks**, so Fastify blocks it. +In this example, the object reference would be shared with all requests, and +**any mutation will impact all requests, potentially creating security +vulnerabilities or memory leaks**. Fastify blocks this. To achieve proper encapsulation across requests configure a new value for each incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). @@ -268,8 +261,7 @@ fastify.get('/', async function (req, reply) { }) ``` -This ensures that the `user` property is always unique for each -request. +This ensures that the `user` property is always unique for each request. See [`decorate`](#decorate) for information about the `dependencies` parameter. @@ -305,9 +297,7 @@ fastify.hasReplyDecorator('utility') Defining a decorator (using `decorate`, `decorateRequest`, or `decorateReply`) with the same name more than once in the same **encapsulated** context will -throw an exception. - -As an example, the following will throw: +throw an exception. For example, the following will throw: ```js const server = require('fastify')() @@ -358,9 +348,9 @@ server.listen({ port: 3000 }) ### Getters and Setters -Decorators accept special "getter/setter" objects. These objects have functions -named `getter` and `setter` (though the `setter` function is optional). This -allows defining properties via decorators, for example: +Decorators accept special "getter/setter" objects with `getter` and optional +`setter` functions. This allows defining properties via decorators, +for example: ```js fastify.decorate('foo', { From 3688587a285aeed279d7f48fe3b095c9aec5eae1 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Fri, 24 Jan 2025 13:46:54 +0200 Subject: [PATCH 0922/1295] chore(sponsor): Add Lokalise sponsorship reference (#5968) --- SPONSORS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SPONSORS.md b/SPONSORS.md index cbfb84a505f..0d4bc5fc73e 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -13,9 +13,10 @@ _Be the first!_ ## Tier 3 +- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) +- [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship) - [Mercedes-Benz Group](https://github.com/mercedes-benz) - [Val Town, Inc.](https://opencollective.com/valtown) -- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) ## Tier 2 From 503a1254701440a5a1ddd5d2bfe91306361cc2d0 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 25 Jan 2025 08:58:25 +0100 Subject: [PATCH 0923/1295] test: migrated listen.2.test.js from tap to node:test (#5960) --- test/listen.2.test.js | 87 +++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/test/listen.2.test.js b/test/listen.2.test.js index 9861611f7d8..fc9023c9dbe 100644 --- a/test/listen.2.test.js +++ b/test/listen.2.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test, before } = require('tap') +const { test, before } = require('node:test') const Fastify = require('..') const helper = require('./helper') @@ -10,94 +10,101 @@ before(async function () { [, localhostForURL] = await helper.getLoopbackHost() }) -test('register after listen using Promise.resolve()', t => { +test('register after listen using Promise.resolve()', async t => { t.plan(1) - const f = Fastify() + const fastify = Fastify() const handler = (req, res) => res.send({}) - Promise.resolve() + await Promise.resolve() .then(() => { - f.get('/', handler) - f.register((f2, options, done) => { + fastify.get('/', handler) + fastify.register((f2, options, done) => { f2.get('/plugin', handler) done() }) - return f.ready() + return fastify.ready() + }) + .catch((err) => { + t.assert.fail(err.message) }) - .catch(t.error) - .then(() => t.pass('resolved')) + .then(() => t.assert.ok('resolved')) }) -test('double listen errors', t => { +test('double listen errors', (t, done) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, (err) => { - t.error(err) + t.assert.ifError(err) fastify.listen({ port: fastify.server.address().port }, (err, address) => { - t.equal(address, null) - t.ok(err) + t.assert.strictEqual(address, null) + t.assert.ok(err) + done() }) }) }) -test('double listen errors callback with (err, address)', t => { +test('double listen errors callback with (err, address)', (t, done) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, (err1, address1) => { - t.equal(address1, `http://${localhostForURL}:${fastify.server.address().port}`) - t.error(err1) + t.assert.strictEqual(address1, `http://${localhostForURL}:${fastify.server.address().port}`) + t.assert.ifError(err1) fastify.listen({ port: fastify.server.address().port }, (err2, address2) => { - t.equal(address2, null) - t.ok(err2) + t.assert.strictEqual(address2, null) + t.assert.ok(err2) + done() }) }) }) -test('nonlocalhost double listen errors callback with (err, address)', t => { +test('nonlocalhost double listen errors callback with (err, address)', (t, done) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ host: '::1', port: 0 }, (err, address) => { - t.equal(address, `http://${'[::1]'}:${fastify.server.address().port}`) - t.error(err) + t.assert.strictEqual(address, `http://${'[::1]'}:${fastify.server.address().port}`) + t.assert.ifError(err) fastify.listen({ host: '::1', port: fastify.server.address().port }, (err2, address2) => { - t.equal(address2, null) - t.ok(err2) + t.assert.strictEqual(address2, null) + t.assert.ok(err2) + done() }) }) }) -test('listen twice on the same port', t => { +test('listen twice on the same port', (t, done) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, (err1, address1) => { - t.equal(address1, `http://${localhostForURL}:${fastify.server.address().port}`) - t.error(err1) + t.assert.strictEqual(address1, `http://${localhostForURL}:${fastify.server.address().port}`) + t.assert.ifError(err1) const s2 = Fastify() - t.teardown(s2.close.bind(s2)) + t.after(() => fastify.close()) s2.listen({ port: fastify.server.address().port }, (err2, address2) => { - t.equal(address2, null) - t.ok(err2) + t.assert.strictEqual(address2, null) + t.assert.ok(err2) + done() }) }) }) -test('listen twice on the same port callback with (err, address)', t => { +test('listen twice on the same port callback with (err, address)', (t, done) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, (err1, address1) => { const _port = fastify.server.address().port - t.equal(address1, `http://${localhostForURL}:${_port}`) - t.error(err1) + t.assert.strictEqual(address1, `http://${localhostForURL}:${_port}`) + t.assert.ifError(err1) const s2 = Fastify() - t.teardown(s2.close.bind(s2)) + t.after(() => fastify.close()) s2.listen({ port: _port }, (err2, address2) => { - t.equal(address2, null) - t.ok(err2) + t.assert.strictEqual(address2, null) + t.assert.ok(err2) + done() }) }) }) From 0bc4fb44a2c2beb0b96e9808e5c62b512479cc02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Pogeant?= Date: Sat, 25 Jan 2025 09:01:25 +0100 Subject: [PATCH 0924/1295] docs(ecosystem): adding fastify-enforce-routes-pattern to community plugins (#5961) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index dd88ceefa6b..f3deff062ee 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -200,6 +200,8 @@ section. to go! A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine on Fastify +- [`@jerome1337/fastify-enforce-routes-pattern`](https://github.com/Jerome1337/fastify-enforce-routes-pattern) + A Fastify plugin that enforces naming pattern for routes path. - [`@joggr/fastify-prisma`](https://github.com/joggrdocs/fastify-prisma) A plugin for accessing an instantiated PrismaClient on your server. - [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit) From 68c204697e8dbd0254a2cba551ba2e5c93f4eede Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 25 Jan 2025 11:21:23 +0100 Subject: [PATCH 0925/1295] chore(sponsor): add Jspreadsheet (#5971) --- SPONSORS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SPONSORS.md b/SPONSORS.md index 0d4bc5fc73e..8f641f9f1de 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -13,10 +13,11 @@ _Be the first!_ ## Tier 3 -- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) -- [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship) - [Mercedes-Benz Group](https://github.com/mercedes-benz) - [Val Town, Inc.](https://opencollective.com/valtown) +- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) +- [Jspreadsheet](https://jspreadsheet.com/) +- [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship) ## Tier 2 From de610ccc94652719c5c8477344c6ae7dfe127c11 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 28 Jan 2025 11:54:49 +0000 Subject: [PATCH 0926/1295] docs(reference/routes): conciseness improvements (#5969) --- docs/Reference/Decorators.md | 2 +- docs/Reference/Plugins.md | 2 +- docs/Reference/Routes.md | 228 ++++++++++++++++------------------- 3 files changed, 106 insertions(+), 126 deletions(-) diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 452707c13b4..23b3904fb8c 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -242,7 +242,7 @@ module.exports = fp(myPlugin) ``` The hook solution is more flexible and allows for more complex initialization -because you can add more logic to the `onRequest` hook. +because more logic can be added to the `onRequest` hook. Another approach is to use the getter/setter pattern, but it requires 2 decorators: diff --git a/docs/Reference/Plugins.md b/docs/Reference/Plugins.md index 76fd3ba40fa..1f51d0db1e4 100644 --- a/docs/Reference/Plugins.md +++ b/docs/Reference/Plugins.md @@ -183,7 +183,7 @@ module.exports = function (fastify, opts, done) { done() } ``` -You can also use `register` inside another `register`: +`register` can also be used inside another `register`: ```js module.exports = function (fastify, opts, done) { fastify.decorate('utility', function () {}) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 94a3b5912b9..d98d7030ae6 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -2,9 +2,8 @@ ## Routes -The route methods will configure the endpoints of your application. You have two -ways to declare a route with Fastify: the shorthand method and the full -declaration. +The route methods configure the endpoints of the application. Routes can be +declared using the shorthand method or the full declaration. - [Full declaration](#full-declaration) - [Routes options](#routes-options) @@ -138,11 +137,11 @@ fastify.route(options) * `reply` is defined in [Reply](./Reply.md). -**Notice:** The documentation of `onRequest`, `preParsing`, `preValidation`, -`preHandler`, `preSerialization`, `onSend`, and `onResponse` are described in -more detail in [Hooks](./Hooks.md). Additionally, to send a response before the -request is handled by the `handler` please refer to [Respond to a request from a -hook](./Hooks.md#respond-to-a-request-from-a-hook). +> 🛈 Note: The documentation for `onRequest`, `preParsing`, `preValidation`, +> `preHandler`, `preSerialization`, `onSend`, and `onResponse` is detailed in +> [Hooks](./Hooks.md). To send a response before the request is handled by the +> `handler`, see [Respond to a request from +> a hook](./Hooks.md#respond-to-a-request-from-a-hook). Example: ```js @@ -234,17 +233,17 @@ const opts = { fastify.get('/', opts) ``` -> Note: if the handler is specified in both the `options` and as the third -> parameter to the shortcut method then throws a duplicate `handler` error. +> 🛈 Note: Specifying the handler in both `options` and as the third parameter to +> the shortcut method throws a duplicate `handler` error. ### Url building Fastify supports both static and dynamic URLs. -To register a **parametric** path, use the *colon* before the parameter name. -For **wildcard**, use the *star*. *Remember that static routes are always -checked before parametric and wildcard.* +To register a **parametric** path, use a *colon* before the parameter name. For +**wildcard**, use a *star*. Static routes are always checked before parametric +and wildcard routes. ```js // parametric @@ -266,9 +265,8 @@ fastify.get('/example/:userId/:secretToken', function (request, reply) { fastify.get('/example/*', function (request, reply) {}) ``` -Regular expression routes are supported as well, but be aware that you have to -escape slashes. Take note that RegExp is also very expensive in terms of -performance! +Regular expression routes are supported, but slashes must be escaped. +Take note that RegExp is also very expensive in terms of performance! ```js // parametric with regexp fastify.get('/example/:file(^\\d+).png', function (request, reply) { @@ -306,24 +304,24 @@ fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', function (request, r In this case as parameter separator it is possible to use whatever character is not matched by the regular expression. -The last parameter can be made optional if you add a question mark ("?") to the -end of the parameters name. +The last parameter can be made optional by adding a question mark ("?") to the +end of the parameter name. ```js fastify.get('/example/posts/:id?', function (request, reply) { const { id } = request.params; // your code here }) ``` -In this case you can request `/example/posts` as well as `/example/posts/1`. -The optional param will be undefined if not specified. +In this case, `/example/posts` and `/example/posts/1` are both valid. The +optional param will be `undefined` if not specified. -Having a route with multiple parameters may negatively affect performance, so -prefer a single parameter approach whenever possible, especially on routes that -are on the hot path of your application. If you are interested in how we handle -the routing, check out [find-my-way](https://github.com/delvedor/find-my-way). +Having a route with multiple parameters may negatively affect performance. +Prefer a single parameter approach, especially on routes that are on the hot +path of your application. For more details, see +[find-my-way](https://github.com/delvedor/find-my-way). -If you want a path containing a colon without declaring a parameter, use a -double colon. For example: +To include a colon in a path without declaring a parameter, use a double colon. +For example: ```js fastify.post('/name::verb') // will be interpreted as /name:verb ``` @@ -340,12 +338,12 @@ fastify.get('/', options, async function (request, reply) { }) ``` -As you can see, we are not calling `reply.send` to send back the data to the -user. You just need to return the body and you are done! +As shown, `reply.send` is not called to send data back to the user. Simply +return the body and you are done! -If you need it you can also send back the data to the user with `reply.send`. In -this case do not forget to `return reply` or `await reply` in your `async` -handler or you will introduce a race condition in certain situations. +If needed, you can also send data back with `reply.send`. In this case, do not +forget to `return reply` or `await reply` in your `async` handler to avoid race +conditions. ```js fastify.get('/', options, async function (request, reply) { @@ -379,47 +377,41 @@ fastify.get('/', options, async function (request, reply) { ``` **Warning:** -* When using both `return value` and `reply.send(value)` at the same time, the - first one that happens takes precedence, the second value will be discarded, - and a *warn* log will also be emitted because you tried to send a response - twice. +* When using both `return value` and `reply.send(value)`, the first one takes + precedence, the second is discarded, and a *warn* log is emitted. * Calling `reply.send()` outside of the promise is possible but requires special - attention. For more details read [promise-resolution](#promise-resolution). -* You cannot return `undefined`. For more details read - [promise-resolution](#promise-resolution). + attention. See [promise-resolution](#promise-resolution). +* `undefined` cannot be returned. See [promise-resolution](#promise-resolution). ### Promise resolution -If your handler is an `async` function or returns a promise, you should be aware -of the special behavior that is necessary to support the callback and promise -control-flow. When the handler's promise is resolved, the reply will be -automatically sent with its value unless you explicitly await or return `reply` -in your handler. +If the handler is an `async` function or returns a promise, be aware of the +special behavior to support callback and promise control-flow. When the +handler's promise resolves, the reply is automatically sent with its value +unless you explicitly await or return `reply` in the handler. -1. If you want to use `async/await` or promises but respond with a value with - `reply.send`: +1. If using `async/await` or promises but responding with `reply.send`: - **Do** `return reply` / `await reply`. - **Do not** forget to call `reply.send`. -2. If you want to use `async/await` or promises: +2. If using `async/await` or promises: - **Do not** use `reply.send`. - - **Do** return the value that you want to send. + - **Do** return the value to send. -In this way, we can support both `callback-style` and `async-await`, with the -minimum trade-off. Despite so much freedom we highly recommend going with only -one style because error handling should be handled in a consistent way within -your application. +This approach supports both `callback-style` and `async-await` with minimal +trade-off. However, it is recommended to use only one style for consistent +error handling within your application. -**Notice**: Every async function returns a promise by itself. +> 🛈 Note: Every async function returns a promise by itself. ### Route Prefixing -Sometimes you need to maintain two or more different versions of the same API; a -classic approach is to prefix all the routes with the API version number, -`/v1/user` for example. Fastify offers you a fast and smart way to create -different versions of the same API without changing all the route names by hand, -*route prefixing*. Let's see how it works: +Sometimes maintaining multiple versions of the same API is necessary. A common +approach is to prefix routes with the API version number, e.g., `/v1/user`. +Fastify offers a fast and smart way to create different versions of the same API +without changing all the route names by hand, called *route prefixing*. Here is +how it works: ```js // server.js @@ -446,19 +438,18 @@ module.exports = function (fastify, opts, done) { done() } ``` -Fastify will not complain because you are using the same name for two different -routes, because at compilation time it will handle the prefix automatically -*(this also means that the performance will not be affected at all!)*. +Fastify will not complain about using the same name for two different routes +because it handles the prefix automatically at compilation time. This ensures +performance is not affected. -Now your clients will have access to the following routes: +Now clients will have access to the following routes: - `/v1/user` - `/v2/user` -You can do this as many times as you want, it also works for nested `register`, -and route parameters are supported as well. +This can be done multiple times and works for nested `register`. Route +parameters are also supported. -In case you want to use prefix for all of your routes, you can put them inside a -plugin: +To use a prefix for all routes, place them inside a plugin: ```js const fastify = require('fastify')() @@ -483,10 +474,8 @@ await fastify.listen({ port: 3000 }) ### Route Prefixing and fastify-plugin -Be aware that if you use -[`fastify-plugin`](https://github.com/fastify/fastify-plugin) for wrapping your -routes, this option will not work. You can still make it work by wrapping a -plugin in a plugin, e. g.: +If using [`fastify-plugin`](https://github.com/fastify/fastify-plugin) to wrap +routes, this option will not work. To make it work, wrap a plugin in a plugin: ```js const fp = require('fastify-plugin') const routes = require('./lib/routes') @@ -502,27 +491,23 @@ module.exports = fp(async function (app, opts) { #### Handling of / route inside prefixed plugins -The `/` route has different behavior depending on if the prefix ends with `/` or -not. As an example, if we consider a prefix `/something/`, adding a `/` route -will only match `/something/`. If we consider a prefix `/something`, adding a -`/` route will match both `/something` and `/something/`. +The `/` route behaves differently based on whether the prefix ends with `/`. +For example, with a prefix `/something/`, adding a `/` route matches only +`/something/`. With a prefix `/something`, adding a `/` route matches both +`/something` and `/something/`. See the `prefixTrailingSlash` route option above to change this behavior. ### Custom Log Level -You might need different log levels in your routes; Fastify achieves this in a -very straightforward way. +Different log levels can be set for routes in Fastify by passing the `logLevel` +option to the plugin or route with the desired +[value](https://github.com/pinojs/pino/blob/master/docs/api.md#level-string). -You just need to pass the option `logLevel` to the plugin option or the route -option with the -[value](https://github.com/pinojs/pino/blob/master/docs/api.md#level-string) -that you need. - -Be aware that if you set the `logLevel` at plugin level, also the +Be aware that setting `logLevel` at the plugin level also affects [`setNotFoundHandler`](./Server.md#setnotfoundhandler) and -[`setErrorHandler`](./Server.md#seterrorhandler) will be affected. +[`setErrorHandler`](./Server.md#seterrorhandler). ```js // server.js @@ -534,22 +519,21 @@ fastify.register(require('./routes/events'), { logLevel: 'debug' }) fastify.listen({ port: 3000 }) ``` -Or you can directly pass it to a route: +Or pass it directly to a route: ```js fastify.get('/', { logLevel: 'warn' }, (request, reply) => { reply.send({ hello: 'world' }) }) ``` -*Remember that the custom log level is applied only to the routes, and not to -the global Fastify Logger, accessible with `fastify.log`* +*Remember that the custom log level applies only to routes, not to the global +Fastify Logger, accessible with `fastify.log`.* ### Custom Log Serializer -In some contexts, you may need to log a large object but it could be a waste of -resources for some routes. In this case, you can define custom +In some contexts, logging a large object may waste resources. Define custom [`serializers`](https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object) -and attach them in the right context! +and attach them in the appropriate context. ```js const fastify = require('fastify')({ logger: true }) @@ -568,7 +552,7 @@ fastify.register(require('./routes/events'), { fastify.listen({ port: 3000 }) ``` -You can inherit serializers by context: +Serializers can be inherited by context: ```js const fastify = Fastify({ @@ -629,23 +613,22 @@ fastify.listen({ port: 3000 }) ### Constraints -Fastify supports constraining routes to match only certain requests based on -some property of the request, like the `Host` header, or any other value via +Fastify supports constraining routes to match certain requests based on +properties like the `Host` header or any other value via [`find-my-way`](https://github.com/delvedor/find-my-way) constraints. Constraints are specified in the `constraints` property of the route options. -Fastify has two built-in constraints ready for use: the `version` constraint and -the `host` constraint, and you can add your own custom constraint strategies to -inspect other parts of a request to decide if a route should be executed for a -request. +Fastify has two built-in constraints: `version` and `host`. Custom constraint +strategies can be added to inspect other parts of a request to decide if a route +should be executed. #### Version Constraints You can provide a `version` key in the `constraints` option to a route. -Versioned routes allow you to declare multiple handlers for the same HTTP route -path, which will then be matched according to each request's `Accept-Version` -header. The `Accept-Version` header value should follow the -[semver](https://semver.org/) specification, and routes should be declared with -exact semver versions for matching. +Versioned routes allows multiple handlers to be declared for the same HTTP +route path, matched according to the request's `Accept-Version` header. +The `Accept-Version` header value should follow the +[semver](https://semver.org/) specification, and routes should be declared +with exact semver versions for matching. Fastify will require a request `Accept-Version` header to be set if the route has a version set, and will prefer a versioned route to a non-versioned route @@ -677,19 +660,19 @@ fastify.inject({ ``` > ## ⚠ Security Notice -> Remember to set a +> Set a > [`Vary`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) -> header in your responses with the value you are using for defining the -> versioning (e.g.: `'Accept-Version'`), to prevent cache poisoning attacks. You -> can also configure this as part of your Proxy/CDN. +> header in responses with the value used for versioning +> (e.g., `'Accept-Version'`) to prevent cache poisoning attacks. +> This can also be configured in a Proxy/CDN. > > ```js > const append = require('vary').append > fastify.addHook('onSend', (req, reply, payload, done) => { -> if (req.headers['accept-version']) { // or the custom header you are using +> if (req.headers['accept-version']) { // or the custom header being used > let value = reply.getHeader('Vary') || '' > const header = Array.isArray(value) ? value.join(', ') : String(value) -> if ((value = append(header, 'Accept-Version'))) { // or the custom header you are using +> if ((value = append(header, 'Accept-Version'))) { // or the custom header being used > reply.header('Vary', value) > } > } @@ -697,22 +680,20 @@ fastify.inject({ > }) > ``` -If you declare multiple versions with the same major or minor, Fastify will +If multiple versions with the same major or minor are declared, Fastify will always choose the highest compatible with the `Accept-Version` header value. -If the request will not have the `Accept-Version` header, a 404 error will be -returned. +If the request lacks an `Accept-Version` header, a 404 error will be returned. -It is possible to define a custom version matching logic. This can be done -through the [`constraints`](./Server.md#constraints) configuration when creating -a Fastify server instance. +Custom version matching logic can be defined through the +[`constraints`](./Server.md#constraints) configuration when creating a Fastify +server instance. #### Host Constraints -You can provide a `host` key in the `constraints` route option for to limit that -route to only be matched for certain values of the request `Host` header. `host` -constraint values can be specified as strings for exact matches or RegExps for -arbitrary host matching. +Provide a `host` key in the `constraints` route option to limit the route to +certain values of the request `Host` header. `host` constraint values can be +specified as strings for exact matches or RegExps for arbitrary host matching. ```js fastify.route({ @@ -761,10 +742,9 @@ fastify.route({ #### Asynchronous Custom Constraints -Custom constraints can be provided and the `constraint` criteria can be -fetched from another source such as `database`. The use of asynchronous -custom constraints should be a last resort as it impacts router -performance. +Custom constraints can be provided, and the `constraint` criteria can be +fetched from another source such as a database. Use asynchronous custom +constraints as a last resort, as they impact router performance. ```js function databaseOperation(field, done) { @@ -792,10 +772,10 @@ const secret = { ``` > ## ⚠ Security Notice -> When using with asynchronous constraint. It is highly recommend never return error -> inside the callback. If the error is not preventable, it is recommended to provide -> a custom `frameworkErrors` handler to deal with it. Otherwise, you route selection -> may break or expose sensitive information to attackers. +> When using asynchronous constraints, avoid returning errors inside the +> callback. If errors are unavoidable, provide a custom `frameworkErrors` +> handler to manage them. Otherwise, route selection may break or expose +> sensitive information. > > ```js > const Fastify = require('fastify') From 120661fab3b2de662444a835cb3f58a1c4851b8c Mon Sep 17 00:00:00 2001 From: Johan Legrand Date: Thu, 30 Jan 2025 19:14:18 +0100 Subject: [PATCH 0927/1295] fix(types): missing supportedMethods (#5970) * fix(types): missing supportedMethods Signed-off-by: Johaven * test(instance): supportedMethods --------- Signed-off-by: Johaven --- test/types/instance.test-d.ts | 1 + types/instance.d.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 7040d20b170..11058d8a457 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -36,6 +36,7 @@ expectType(server.getSchema('SchemaId')) expectType(server.printRoutes()) expectType(server.printPlugins()) expectType(server.listeningOrigin) +expectType(server.supportedMethods) expectAssignable( server.setErrorHandler(function (error, request, reply) { diff --git a/types/instance.d.ts b/types/instance.d.ts index 10b559fb6ed..3de0c76e019 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -548,6 +548,10 @@ export interface FastifyInstance< * Remove all content type parsers, including the default ones */ removeAllContentTypeParsers: removeAllContentTypeParsers + /** + * Returns an array of strings containing the list of supported HTTP methods + */ + supportedMethods: string[] /** * Add a non-standard HTTP method * From 87453c0e6a4ac3b7729d82ee8f5c5dae59dfdd7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 14:41:16 +0000 Subject: [PATCH 0928/1295] chore: Bump the dev-dependencies-eslint group with 2 updates (#5975) Bumps the dev-dependencies-eslint group with 2 updates: [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) and [@stylistic/eslint-plugin-js](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin-js). Updates `@stylistic/eslint-plugin` from 2.13.0 to 3.0.1 - [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases) - [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v3.0.1/packages/eslint-plugin) Updates `@stylistic/eslint-plugin-js` from 2.13.0 to 3.0.1 - [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases) - [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v3.0.1/packages/eslint-plugin-js) --- updated-dependencies: - dependency-name: "@stylistic/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies-eslint - dependency-name: "@stylistic/eslint-plugin-js" dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies-eslint ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c3f0c685557..8980259f36f 100644 --- a/package.json +++ b/package.json @@ -163,8 +163,8 @@ "@jsumners/line-reporter": "^1.0.1", "@sinclair/typebox": "^0.34.13", "@sinonjs/fake-timers": "^11.2.2", - "@stylistic/eslint-plugin": "^2.1.0", - "@stylistic/eslint-plugin-js": "^2.1.0", + "@stylistic/eslint-plugin": "^3.0.1", + "@stylistic/eslint-plugin-js": "^3.0.1", "@types/node": "^22.0.0", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", From e2c367d361219b62a30f60996f72fa83339870cb Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 3 Feb 2025 11:11:41 +0000 Subject: [PATCH 0929/1295] chore: rename master to main (#5974) --- .github/workflows/ci.yml | 1 - .github/workflows/integration-alternative-runtimes.yml | 1 - .github/workflows/integration.yml | 1 - .github/workflows/lint-ecosystem-order.yml | 1 - .github/workflows/md-lint.yml | 2 -- docs/Reference/TypeScript.md | 2 +- docs/Reference/Validation-and-Serialization.md | 2 +- 7 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99602d1bfac..1685ddce538 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - master - next - 'v*' paths-ignore: diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index 588a045dc2e..da0df012956 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - master - next - 'v*' paths-ignore: diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index c8ab6b322f1..ed4b10b1a29 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - master - next - 'v*' paths-ignore: diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index b079aa1fddf..2f2c053bb6a 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -3,7 +3,6 @@ name: Lint Ecosystem Order on: pull_request: branches: - - master - main paths: - "**/Ecosystem.md" diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index 97e5facc1c4..d1f60ce91c6 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -3,13 +3,11 @@ name: Lint Docs on: push: branches-ignore: - - master - main paths: - "**/*.md" pull_request: branches: - - master - main paths: - "**/*.md" diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 1d84e6ccf03..124a455c31f 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -632,7 +632,7 @@ newer, automatically adds `.default` property and a named export to the exported plugin. Be sure to `export default` and `export const myPlugin` in your typings to provide the best developer experience. For a complete example you can check out -[@fastify/swagger](https://github.com/fastify/fastify-swagger/blob/master/index.d.ts). +[@fastify/swagger](https://github.com/fastify/fastify-swagger/blob/main/index.d.ts). With those files completed, the plugin is now ready to be consumed by any TypeScript project! diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 565f0c89e00..3f59a6b46e7 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -987,4 +987,4 @@ const refToSharedSchemaDefinitions = { - [Ajv i18n](https://github.com/epoberezkin/ajv-i18n) - [Ajv custom errors](https://github.com/epoberezkin/ajv-errors) - Custom error handling with core methods with error file dumping - [example](https://github.com/fastify/example/tree/master/validation-messages) + [example](https://github.com/fastify/example/tree/main/validation-messages) From 6e98c952665a932e3def78280169fdb7b57f93e3 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 3 Feb 2025 11:28:07 +0000 Subject: [PATCH 0930/1295] docs(reference): mimic github notes and warning style (#5973) --- docs/Reference/ContentTypeParser.md | 6 +- docs/Reference/Hooks.md | 61 ++++++++++--------- docs/Reference/Logging.md | 20 +++--- docs/Reference/Middleware.md | 4 +- docs/Reference/Reply.md | 18 +++--- docs/Reference/Request.md | 4 +- docs/Reference/Routes.md | 16 ++--- docs/Reference/Server.md | 24 ++++---- .../Reference/Validation-and-Serialization.md | 10 +-- 9 files changed, 82 insertions(+), 81 deletions(-) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index 43005beaa94..69e3e94df7c 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -23,7 +23,7 @@ Note that for `GET` and `HEAD` requests, the payload is never parsed. For [catch-all](#catch-all) parser is not executed, and the payload is simply not parsed. -> ## ⚠ Security Notice +> ⚠ Warning: > When using regular expressions to detect `Content-Type`, it is important to > ensure proper detection. For example, to match `application/*`, use > `/^application\/([\w-]+);?/` to match the @@ -152,8 +152,8 @@ fastify.addContentTypeParser('text/xml', function (request, payload, done) { }) ``` -**Notice**: `function(req, done)` and `async function(req)` are -still supported but deprecated. +> 🛈 Note: `function(req, done)` and `async function(req)` are +> still supported but deprecated. #### Body Parser The request body can be parsed in two ways. First, add a custom content type diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 206a2ab6fe8..a69f9f85fdf 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -34,9 +34,9 @@ are Request/Reply hooks and application hooks: - [Using Hooks to Inject Custom Properties](#using-hooks-to-inject-custom-properties) - [Diagnostics Channel Hooks](#diagnostics-channel-hooks) -**Notice:** the `done` callback is not available when using `async`/`await` or -returning a `Promise`. If you do invoke a `done` callback in this situation -unexpected behavior may occur, e.g. duplicate invocation of handlers. +> 🛈 Note: The `done` callback is not available when using `async`/`await` or +> returning a `Promise`. If you do invoke a `done` callback in this situation +> unexpected behavior may occur, e.g. duplicate invocation of handlers. ## Request/Reply Hooks @@ -68,9 +68,9 @@ fastify.addHook('onRequest', async (request, reply) => { }) ``` -**Notice:** in the [onRequest](#onrequest) hook, `request.body` will always be -`undefined`, because the body parsing happens before the -[preValidation](#prevalidation) hook. +> 🛈 Note: In the [onRequest](#onrequest) hook, `request.body` will always be +> `undefined`, because the body parsing happens before the +> [preValidation](#prevalidation) hook. ### preParsing @@ -98,17 +98,17 @@ fastify.addHook('preParsing', async (request, reply, payload) => { }) ``` -**Notice:** in the [preParsing](#preparsing) hook, `request.body` will always be -`undefined`, because the body parsing happens before the -[preValidation](#prevalidation) hook. +> 🛈 Note: In the [preParsing](#preparsing) hook, `request.body` will always be +> `undefined`, because the body parsing happens before the +> [preValidation](#prevalidation) hook. -**Notice:** you should also add a `receivedEncodedLength` property to the -returned stream. This property is used to correctly match the request payload -with the `Content-Length` header value. Ideally, this property should be updated -on each received chunk. +> 🛈 Note: You should also add a `receivedEncodedLength` property to the +> returned stream. This property is used to correctly match the request payload +> with the `Content-Length` header value. Ideally, this property should be updated +> on each received chunk. -**Notice:** The size of the returned stream is checked to not exceed the limit -set in [`bodyLimit`](./Server.md#bodylimit) option. +> 🛈 Note: The size of the returned stream is checked to not exceed the limit +> set in [`bodyLimit`](./Server.md#bodylimit) option. ### preValidation @@ -166,8 +166,8 @@ fastify.addHook('preSerialization', async (request, reply, payload) => { }) ``` -Note: the hook is NOT called if the payload is a `string`, a `Buffer`, a -`stream`, or `null`. +> 🛈 Note: The hook is NOT called if the payload is a `string`, a `Buffer`, a +> `stream`, or `null`. ### onError ```js @@ -196,8 +196,8 @@ user *(Note that the default error handler always sends the error back to the user)*. -**Notice:** unlike the other hooks, passing an error to the `done` function is not -supported. +> 🛈 Note: Unlike the other hooks, passing an error to the `done` function is not +> supported. ### onSend If you are using the `onSend` hook, you can change the payload. For example: @@ -233,8 +233,8 @@ fastify.addHook('onSend', (request, reply, payload, done) => { > to `0`, whereas the `Content-Length` header will not be set if the payload is > `null`. -Note: If you change the payload, you may only change it to a `string`, a -`Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`. +> 🛈 Note: If you change the payload, you may only change it to a `string`, a +> `Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`. ### onResponse @@ -256,8 +256,8 @@ The `onResponse` hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example, to gather statistics. -**Note:** setting `disableRequestLogging` to `true` will disable any error log -inside the `onResponse` hook. In this case use `try - catch` to log errors. +> 🛈 Note: Setting `disableRequestLogging` to `true` will disable any error log +> inside the `onResponse` hook. In this case use `try - catch` to log errors. ### onTimeout @@ -298,7 +298,8 @@ The `onRequestAbort` hook is executed when a client closes the connection before the entire request has been processed. Therefore, you will not be able to send data to the client. -**Notice:** client abort detection is not completely reliable. See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md) +> 🛈 Note: Client abort detection is not completely reliable. +> See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md) ### Manage Errors from a hook If you get an error during the execution of your hook, just pass it to `done()` @@ -451,8 +452,8 @@ fastify.addHook('onListen', async function () { }) ``` -> **Note** -> This hook will not run when the server is started using `fastify.inject()` or `fastify.ready()` +> 🛈 Note: This hook will not run when the server is started using +> fastify.inject()` or `fastify.ready()`. ### onClose @@ -575,8 +576,8 @@ This hook can be useful if you are developing a plugin that needs to know when a plugin context is formed, and you want to operate in that specific context, thus this hook is encapsulated. -**Note:** This hook will not be called if a plugin is wrapped inside -[`fastify-plugin`](https://github.com/fastify/fastify-plugin). +> 🛈 Note: This hook will not be called if a plugin is wrapped inside +> [`fastify-plugin`](https://github.com/fastify/fastify-plugin). ```js fastify.decorate('data', []) @@ -773,7 +774,7 @@ fastify.route({ }) ``` -**Note**: both options also accept an array of functions. +> 🛈 Note: Both options also accept an array of functions. ## Using Hooks to Inject Custom Properties @@ -860,7 +861,7 @@ channel.subscribe(function ({ fastify }) { }) ``` -> **Note:** The TracingChannel class API is currently experimental and may undergo +> 🛈 Note: The TracingChannel class API is currently experimental and may undergo > breaking changes even in semver-patch releases of Node.js. Five other events are published on a per-request basis following the diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index e7e4ac72d9b..83388f03e57 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -157,11 +157,11 @@ const fastify = require('fastify')({ }); ``` -**Note**: In some cases, the [`Reply`](./Reply.md) object passed to the `res` -serializer cannot be fully constructed. When writing a custom `res` serializer, -check for the existence of any properties on `reply` aside from `statusCode`, -which is always present. For example, verify the existence of `getHeaders` -before calling it: +> 🛈 Note: In some cases, the [`Reply`](./Reply.md) object passed to the `res` +> serializer cannot be fully constructed. When writing a custom `res` +> serializer, check for the existence of any properties on `reply` aside from +> `statusCode`, which is always present. For example, verify the existence of +> `getHeaders` before calling it: ```js const fastify = require('fastify')({ @@ -184,7 +184,7 @@ const fastify = require('fastify')({ }); ``` -**Note**: The body cannot be serialized inside a `req` method because the +> 🛈 Note: The body cannot be serialized inside a `req` method because the request is serialized when the child logger is created. At that time, the body is not yet parsed. @@ -199,10 +199,10 @@ app.addHook('preHandler', function (req, reply, done) { }) ``` -**Note**: Ensure serializers never throw errors, as this can cause the Node -process to exit. See the -[Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) for more -information. +> 🛈 Note: Ensure serializers never throw errors, as this can cause the Node +> process to exit. See the +> [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) for more +> information. *Any logger other than Pino will ignore this option.* diff --git a/docs/Reference/Middleware.md b/docs/Reference/Middleware.md index c61a9a309d9..4a64a197d58 100644 --- a/docs/Reference/Middleware.md +++ b/docs/Reference/Middleware.md @@ -50,8 +50,8 @@ that already has the Fastify [Request](./Request.md#request) and To run middleware under certain paths, pass the path as the first parameter to `use`. -*Note: This does not support routes with parameters (e.g. `/user/:id/comments`) -and wildcards are not supported in multiple paths.* +> 🛈 Note: This does not support routes with parameters +> (e.g. `/user/:id/comments`) and wildcards are not supported in multiple paths. ```js const path = require('node:path') diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 7d41a0bd757..0b7a8748b67 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -151,7 +151,7 @@ fastify.get('/', async function (req, rep) { Sets a response header. If the value is omitted or undefined, it is coerced to `''`. -> Note: the header's value must be properly encoded using +> 🛈 Note: The header's value must be properly encoded using > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) > or similar modules such as > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid characters @@ -260,11 +260,11 @@ requires heavy resources to be sent after the `data`, for example, `Server-Timing` and `Etag`. It can ensure the client receives the response data as soon as possible. -*Note: The header `Transfer-Encoding: chunked` will be added once you use the -trailer. It is a hard requirement for using trailer in Node.js.* +> 🛈 Note: The header `Transfer-Encoding: chunked` will be added once you use +> the trailer. It is a hard requirement for using trailer in Node.js. -*Note: Any error passed to `done` callback will be ignored. If you interested -in the error, you can turn on `debug` level logging.* +> 🛈 Note: Any error passed to `done` callback will be ignored. If you interested +> in the error, you can turn on `debug` level logging.* ```js reply.trailer('server-timing', function() { @@ -314,7 +314,7 @@ reply.getTrailer('server-timing') // undefined Redirects a request to the specified URL, the status code is optional, default to `302` (if status code is not already set by calling `code`). -> Note: the input URL must be properly encoded using +> 🛈 Note: The input URL must be properly encoded using > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) > or similar modules such as > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid URLs will @@ -823,8 +823,8 @@ automatically create an error structured as the following: You can add custom properties to the Error object, such as `headers`, that will be used to enhance the HTTP response. -*Note: If you are passing an error to `send` and the statusCode is less than -400, Fastify will automatically set it at 500.* +> 🛈 Note: If you are passing an error to `send` and the statusCode is less than +> 400, Fastify will automatically set it at 500. Tip: you can simplify errors by using the [`http-errors`](https://npm.im/http-errors) module or @@ -871,7 +871,7 @@ fastify.get('/', { If you want to customize error handling, check out [`setErrorHandler`](./Server.md#seterrorhandler) API. -*Note: you are responsible for logging when customizing the error handler* +> 🛈 Note: you are responsible for logging when customizing the error handler. API: diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 009c0f2ff8c..80c904845f4 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -84,8 +84,8 @@ This operation adds new values to the request headers, accessible via For performance reasons, `Symbol('fastify.RequestAcceptVersion')` may be added to headers on `not found` routes. -> Note: Schema validation may mutate the `request.headers` and -`request.raw.headers` objects, causing the headers to become empty. +> 🛈 Note: Schema validation may mutate the `request.headers` and +> `request.raw.headers` objects, causing the headers to become empty. ```js fastify.post('/:params', options, function (request, reply) { diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index d98d7030ae6..328cb053c51 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -376,12 +376,12 @@ fastify.get('/', options, async function (request, reply) { }) ``` -**Warning:** -* When using both `return value` and `reply.send(value)`, the first one takes - precedence, the second is discarded, and a *warn* log is emitted. -* Calling `reply.send()` outside of the promise is possible but requires special - attention. See [promise-resolution](#promise-resolution). -* `undefined` cannot be returned. See [promise-resolution](#promise-resolution). +> ⚠ Warning: +> * When using both `return value` and `reply.send(value)`, the first one takes +> precedence, the second is discarded, and a *warn* log is emitted. +> * Calling `reply.send()` outside of the promise is possible but requires special +> attention. See [promise-resolution](#promise-resolution). +> * `undefined` cannot be returned. See [promise-resolution](#promise-resolution). ### Promise resolution @@ -659,7 +659,7 @@ fastify.inject({ }) ``` -> ## ⚠ Security Notice +> ⚠ Warning: > Set a > [`Vary`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) > header in responses with the value used for versioning @@ -771,7 +771,7 @@ const secret = { } ``` -> ## ⚠ Security Notice +> ⚠ Warning: > When using asynchronous constraints, avoid returning errors inside the > callback. If errors are unavoidable, provide a custom `frameworkErrors` > handler to manage them. Otherwise, route selection may break or expose diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 26e52900a59..51961de3bd5 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -166,7 +166,7 @@ When set to `true`, upon [`close`](#close) the server will iterate the current persistent connections and [destroy their sockets](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketdestroyerror). -> **Warning** +> ⚠ Warning: > Connections are not inspected to determine if requests have > been completed. @@ -193,7 +193,7 @@ to understand the effect of this option. This option only applies when HTTP/1.1 is in use. Also, when `serverFactory` option is specified, this option is ignored. -> **Note** +> 🛈 Note: > At the time of writing, only node >= v16.10.0 supports this option. ### `requestTimeout` @@ -211,7 +211,7 @@ It must be set to a non-zero value (e.g. 120 seconds) to protect against potenti Denial-of-Service attacks in case the server is deployed without a reverse proxy in front. -> **Note** +> 🛈 Note: > At the time of writing, only node >= v14.11.0 supports this option ### `ignoreTrailingSlash` @@ -529,7 +529,7 @@ Especially in distributed systems, you may want to override the default ID generation behavior as shown below. For generating `UUID`s you may want to check out [hyperid](https://github.com/mcollina/hyperid). -> **Note** +> 🛈 Note: > `genReqId` will be not called if the header set in > [requestIdHeader](#requestidheader) is available (defaults to > 'request-id'). @@ -581,7 +581,7 @@ fastify.get('/', (request, reply) => { }) ``` -> **Note** +> 🛈 Note: > If a request contains multiple `x-forwarded-host` or `x-forwarded-proto` > headers, it is only the last one that is used to derive `request.hostname` > and `request.protocol`. @@ -735,7 +735,7 @@ Fastify provides default error handlers for the most common use cases. It is possible to override one or more of those handlers with custom code using this option. -> **Note** +> 🛈 Note: > Only `FST_ERR_BAD_URL` and `FST_ERR_ASYNC_CONSTRAINT` are implemented at present. ```js @@ -788,7 +788,7 @@ function defaultClientErrorHandler (err, socket) { } ``` -> **Note** +> 🛈 Note: > `clientErrorHandler` operates with raw sockets. The handler is expected to > return a properly formed HTTP response that includes a status line, HTTP headers > and a message body. Before attempting to write the socket, the handler should @@ -877,7 +877,7 @@ fastify.get('/dev', async (request, reply) => { [server](https://nodejs.org/api/http.html#http_class_http_server) object as returned by the [**`Fastify factory function`**](#factory). -> **Warning** +> ⚠ Warning: > If utilized improperly, certain Fastify features could be disrupted. > It is recommended to only use it for attaching listeners. @@ -1219,7 +1219,7 @@ different ways to define a name (in order). Newlines are replaced by ` -- `. This will help to identify the root cause when you deal with many plugins. -> **Warning** +> ⚠ Warning: > If you have to deal with nested plugins, the name differs with the usage of > the [fastify-plugin](https://github.com/fastify/fastify-plugin) because > no new scope is created and therefore we have no place to attach contextual @@ -1355,7 +1355,7 @@ Set the schema error formatter for all routes. See Set the schema serializer compiler for all routes. See [#schema-serializer](./Validation-and-Serialization.md#schema-serializer). -> **Note** +> 🛈 Note: > [`setReplySerializer`](#set-reply-serializer) has priority if set! #### validatorCompiler @@ -1488,7 +1488,7 @@ lifecycle](./Lifecycle.md#lifecycle). *async-await* is supported as well. You can also register [`preValidation`](./Hooks.md#route-hooks) and [`preHandler`](./Hooks.md#route-hooks) hooks for the 404 handler. -> **Note** +> 🛈 Note: > The `preValidation` hook registered using this method will run for a > route that Fastify does not recognize and **not** when a route handler manually > calls [`reply.callNotFound`](./Reply.md#call-not-found). In which case, only @@ -1524,7 +1524,7 @@ plugins are registered. If you would like to augment the behavior of the default arguments `fastify.setNotFoundHandler()` within the context of these registered plugins. -> **Note** +> 🛈 Note: > Some config properties from the request object will be > undefined inside the custom not found handler. E.g.: > `request.routerPath`, `routerMethod` and `context.config`. diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 3f59a6b46e7..5755d6a0364 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -11,7 +11,7 @@ All examples use the [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7) specification. -> ## ⚠ Security Notice +> ⚠ Warning: > Treat schema definitions as application code. Validation and serialization > features use `new Function()`, which is unsafe with user-provided schemas. See > [Ajv](https://npm.im/ajv) and @@ -432,9 +432,9 @@ fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { return ajv.compile(schema) }) ``` -_**Note:** When using a custom validator instance, add schemas to the validator -instead of Fastify. Fastify's `addSchema` method will not recognize the custom -validator._ +> 🛈 Note: When using a custom validator instance, add schemas to the validator +> instead of Fastify. Fastify's `addSchema` method will not recognize the custom +> validator. ##### Using other validation libraries @@ -793,7 +793,7 @@ const fastify = Fastify({ ajv: { customOptions: { jsonPointers: true, - // Warning: Enabling this option may lead to this security issue https://www.cvedetails.com/cve/CVE-2020-8192/ + // ⚠ Warning: Enabling this option may lead to this security issue https://www.cvedetails.com/cve/CVE-2020-8192/ allErrors: true }, plugins: [ From 0dd81161b7a53b3b07e943589a59e0279de9836c Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 11 Feb 2025 14:36:10 +0100 Subject: [PATCH 0931/1295] Drop platformatic cloud reference in serverless.md (#5982) We pivoted away from this. Signed-off-by: Matteo Collina --- docs/Guides/Serverless.md | 45 +-------------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 8bdf60fea02..cedde410893 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -29,7 +29,6 @@ snippet of code. - [Google Firebase Functions](#google-firebase-functions) - [Google Cloud Run](#google-cloud-run) - [Netlify Lambda](#netlify-lambda) -- [Platformatic Cloud](#platformatic-cloud) - [Vercel](#vercel) ## AWS @@ -562,49 +561,7 @@ Add this command to your `package.json` *scripts* } ``` -Then it should work fine - -## Platformatic Cloud - -[Platformatic](https://platformatic.dev) provides zero-configuration deployment -for Node.js applications. -To use it now, you should wrap your existing Fastify application inside a -[Platformatic Service](https://oss.platformatic.dev/docs/reference/service/introduction), -by running the following: - - -```bash -npm create platformatic@latest -- service -``` - -The wizard would ask you to fill in a few answers: - -``` -? Where would you like to create your project? . -? Do you want to run npm install? yes -? Do you want to use TypeScript? no -? What port do you want to use? 3042 -[13:04:14] INFO: Configuration file platformatic.service.json successfully created. -[13:04:14] INFO: Environment file .env successfully created. -[13:04:14] INFO: Plugins folder "plugins" successfully created. -[13:04:14] INFO: Routes folder "routes" successfully created. -? Do you want to create the github action to deploy this application to Platformatic Cloud dynamic workspace? no -? Do you want to create the github action to deploy this application to Platformatic Cloud static workspace? no -``` - -Then, head to [Platformatic Cloud](https://platformatic.cloud) and sign in -with your GitHub account. -Create your first application and a static workspace: be careful to download the -API key as an env file, e.g. `yourworkspace.txt`. - -Then, you can easily deploy your application with the following command: - -```bash -platformatic deploy --keys `yourworkspace.txt` -``` - -Check out the [Full Guide](https://blog.platformatic.dev/how-to-migrate-a-fastify-app-to-platformatic-service) -on how to wrap Fastify application in Platformatic. +Then it should work fine. ## Vercel From eb5497da1264da366ccc338d714be9e23a8f09f4 Mon Sep 17 00:00:00 2001 From: Cristi Miloiu <65810476+cristim67@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:52:53 +0200 Subject: [PATCH 0932/1295] docs: add a Genezio step by step guide (#5980) * feat: add a Genezio step by step guide * feat: add a Genezio step by step guide --- docs/Guides/Serverless.md | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index cedde410893..b5474a9278c 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -25,6 +25,7 @@ snippet of code. ### Contents - [AWS](#aws) +- [Genezio](#genezio) - [Google Cloud Functions](#google-cloud-functions) - [Google Firebase Functions](#google-firebase-functions) - [Google Cloud Run](#google-cloud-run) @@ -128,6 +129,60 @@ If you need to integrate with more AWS services, take a look at [@h4ad/serverless-adapter](https://viniciusl.com.br/serverless-adapter/docs/main/frameworks/fastify) on Fastify to find out how to integrate. +## Genezio + +[Genezio](https://genezio.com/) is a platform designed to simplify the deployment +of serverless applications to the cloud. It eliminates the complexity +of managing infrastructure and modifying your code for cloud compatibility. +Genezio provides two main deployment methods: + +- [Deploy from a GitHub repository](#deploy-from-a-github-repository) +- [Deploy Using the CLI](#deploy-using-the-cli) + +[![Genezio Deploy](https://raw.githubusercontent.com/Genez-io/graphics/main/svg/deploy-button.svg)](https://app.genez.io/start/deploy?repository=https://github.com/Genez-io/fastify-getting-started&utm_source=github&utm_medium=referral&utm_campaign=github-fastify&utm_term=deploy-project&utm_content=button-head) + +### Deploy from a GitHub repository + +Deploy directly from your GitHub repository with 0 configuration needed: + +1. Navigate to the [Genezio Deployment Page](https://app.genez.io/auth/login?redirect=import). +2. Authenticate with your GitHub account. +3. Select the repository you wish to deploy. + +Genezio will handle the configuration and deployment for you. + + +### Deploy using the CLI + +The Genezio CLI allows you to deploy applications directly from your local environment. + +**Step 1**: Install the Genezio CLI: + +```bash +npm install -g genezio +``` + +**Step 2**: Run the analyze command to generate a configuration file: + +```bash +genezio analyze +``` + +This will create a `genezio.yaml` configuration file in your project. + +**Step 3**: Deploy your application to production: + +```bash +genezio deploy +``` + +This will deploy your Fastify application to Genezio's serverless infrastructure. + +### References + +- [Fastify on Genezio](https://app.genez.io/start/deploy?repository=https://github.com/Genez-io/fastify-getting-started&utm_source=github&utm_medium=referral&utm_campaign=github-fastify&utm_term=deploy-project&utm_content=button-head) +- [Genezio Documentation](https://genezio.com/docs/frameworks/fastify/) + ## Google Cloud Functions ### Creation of Fastify instance From 5867a5b7a63b586e19e582fc9eb38206c7a5c18c Mon Sep 17 00:00:00 2001 From: Yoshiki Kadoshita Date: Sun, 16 Feb 2025 21:58:02 +0900 Subject: [PATCH 0933/1295] docs(lts): fix anchor tag format (#5986) --- docs/Reference/LTS.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 9cf6ab2f64e..38a7a065dde 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -1,8 +1,7 @@

Fastify

## Long Term Support - -`` + Fastify's Long Term Support (LTS) is provided according to the schedule laid out in this document: @@ -50,8 +49,7 @@ OpenJS Ecosystem Sustainability Program for versions of Fastify that are EOL. For more information, see their [Never Ending Support][hd-link] service. ### Schedule - -`` + | Version | Release Date | End Of LTS Date | Node.js | Nsolid(Node) | | :------ | :----------- | :-------------- | :----------------- | :------------- | @@ -62,8 +60,7 @@ For more information, see their [Never Ending Support][hd-link] service. | 5.0.0 | 2024-09-17 | TBD | 20, 22 | v5(20) | ### CI tested operating systems - -`` + Fastify uses GitHub Actions for CI testing, please refer to [GitHub's documentation regarding workflow From 3ad7690ecb6d6254702d377aae96d8afacacf802 Mon Sep 17 00:00:00 2001 From: jonasongg <120372506+jonasongg@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:43:25 +0800 Subject: [PATCH 0934/1295] Update documentation for listening to 0.0.0.0 (#5988) --- docs/Guides/Getting-Started.md | 3 +++ docs/Reference/Server.md | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md index b55a3cc4e80..68a559004fd 100644 --- a/docs/Guides/Getting-Started.md +++ b/docs/Guides/Getting-Started.md @@ -128,6 +128,9 @@ above, and more! > > When deploying to a Docker (or another type of) container using `0.0.0.0` or > `::` would be the easiest method for exposing the application. +> +> Note that when using `0.0.0.0`, the address provided in the callback argument +> above will be the first address the wildcard refers to. ### Your first plugin diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 51961de3bd5..b5d71df4a39 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -973,7 +973,8 @@ server.listen({ By default, the server will listen on the address(es) resolved by `localhost` when no specific host is provided. If listening on any available interface is desired, then specifying `0.0.0.0` for the address will listen on all IPv4 -addresses. The following table details the possible values for `host` when +addresses. The address argument provided above will then return the first such +IPv4 address. The following table details the possible values for `host` when targeting `localhost`, and what the result of those values for `host` will be. Host | IPv4 | IPv6 From 199b1ae8eac7a6c84bc37fc32f2b4cc2ed39c18f Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 19 Feb 2025 11:10:17 +0000 Subject: [PATCH 0935/1295] docs(serverless): cut down genezio example (#5990) Signed-off-by: Frazer Smith --- docs/Guides/Serverless.md | 50 ++------------------------------------- 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index b5474a9278c..17e44e5d08e 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -132,56 +132,10 @@ on Fastify to find out how to integrate. ## Genezio [Genezio](https://genezio.com/) is a platform designed to simplify the deployment -of serverless applications to the cloud. It eliminates the complexity -of managing infrastructure and modifying your code for cloud compatibility. -Genezio provides two main deployment methods: +of serverless applications to the cloud. -- [Deploy from a GitHub repository](#deploy-from-a-github-repository) -- [Deploy Using the CLI](#deploy-using-the-cli) +[Genezio has a dedicated guide for deploying a Fastify application.](https://genezio.com/docs/frameworks/fastify/) -[![Genezio Deploy](https://raw.githubusercontent.com/Genez-io/graphics/main/svg/deploy-button.svg)](https://app.genez.io/start/deploy?repository=https://github.com/Genez-io/fastify-getting-started&utm_source=github&utm_medium=referral&utm_campaign=github-fastify&utm_term=deploy-project&utm_content=button-head) - -### Deploy from a GitHub repository - -Deploy directly from your GitHub repository with 0 configuration needed: - -1. Navigate to the [Genezio Deployment Page](https://app.genez.io/auth/login?redirect=import). -2. Authenticate with your GitHub account. -3. Select the repository you wish to deploy. - -Genezio will handle the configuration and deployment for you. - - -### Deploy using the CLI - -The Genezio CLI allows you to deploy applications directly from your local environment. - -**Step 1**: Install the Genezio CLI: - -```bash -npm install -g genezio -``` - -**Step 2**: Run the analyze command to generate a configuration file: - -```bash -genezio analyze -``` - -This will create a `genezio.yaml` configuration file in your project. - -**Step 3**: Deploy your application to production: - -```bash -genezio deploy -``` - -This will deploy your Fastify application to Genezio's serverless infrastructure. - -### References - -- [Fastify on Genezio](https://app.genez.io/start/deploy?repository=https://github.com/Genez-io/fastify-getting-started&utm_source=github&utm_medium=referral&utm_campaign=github-fastify&utm_term=deploy-project&utm_content=button-head) -- [Genezio Documentation](https://genezio.com/docs/frameworks/fastify/) ## Google Cloud Functions From 97fe02292e6ad3a936664a672c7cf29518a3f4fc Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Mon, 24 Feb 2025 18:33:41 +0100 Subject: [PATCH 0936/1295] docs: add link to official demo (#5994) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8760df156ce..262d29040aa 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ fastify.listen({ port: 3000 }, (err, address) => { Do you want to know more? Head to the Getting Started. +If you learn best by reading code, explore the official [demo](https://github.com/fastify/demo). > ## Note > `.listen` binds to the local host, `localhost`, interface by default From 951e0b8e3193035eb786ecf15c7ab301a72e175e Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 24 Feb 2025 18:35:27 +0100 Subject: [PATCH 0937/1295] chore(license): update licensing year (#5992) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 6f7efa05146..deef1e20e65 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2024 The Fastify Team +Copyright (c) 2016-2025 The Fastify Team The Fastify team members are listed at https://github.com/fastify/fastify#team and in the README file. From 4755f4b6cd51d60eec113df0285ea2e18e68cfbc Mon Sep 17 00:00:00 2001 From: "Stanislav (Stanley) Modrak" <44023416+smith558@users.noreply.github.com> Date: Mon, 24 Feb 2025 18:02:55 +0000 Subject: [PATCH 0938/1295] docs: wrong query string parser information (#5993) --- docs/Reference/Server.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index b5d71df4a39..b9a879be118 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -599,8 +599,9 @@ controls [avvio](https://www.npmjs.com/package/avvio) 's `timeout` parameter. ### `querystringParser` -The default query string parser that Fastify uses is the Node.js's core -`querystring` module. +The default query string parser that Fastify uses is a more performant fork +of Node.js's core `querystring` module called +[`fast-querystring`](https://github.com/anonrig/fast-querystring). You can use this option to use a custom parser, such as [`qs`](https://www.npmjs.com/package/qs). From bcb83217cde739fd3a16482829eb3e04cd832567 Mon Sep 17 00:00:00 2001 From: Salman Mitha Date: Tue, 25 Feb 2025 20:35:22 +0400 Subject: [PATCH 0939/1295] fix: typo in v5 migration docs (#5995) --- docs/Guides/Migration-Guide-V5.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index 9c6884b4e37..1af012dbfd5 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -472,7 +472,7 @@ or turn it into a function ```js // v5 -fastify.decorateRequest('myObject', () => { hello: 'world' }); +fastify.decorateRequest('myObject', () => ({ hello: 'world' })); ``` or as a getter @@ -720,4 +720,3 @@ contributing to those that are capable of accepting sponsorships. | voxpelli | [❤️ sponsor](https://github.com/sponsors/voxpelli) | fastify | | weixinwu | | fastify-cli | | zetaraku | | fastify-cli | - From 59811c04dcfb647b6d9bace4bd653e3a044d18ea Mon Sep 17 00:00:00 2001 From: "Stanislav (Stanley) Modrak" <44023416+smith558@users.noreply.github.com> Date: Fri, 28 Feb 2025 19:48:52 +0000 Subject: [PATCH 0940/1295] Update example to use correct parser (#5996) Signed-off-by: Stanislav (Stanley) Modrak <44023416+smith558@users.noreply.github.com> --- docs/Reference/Server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index b9a879be118..fb37f129d0b 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -620,7 +620,7 @@ You can also use Fastify's default parser but change some handling behavior, like the example below for case insensitive keys and values: ```js -const querystring = require('node:querystring') +const querystring = require('fast-querystring') const fastify = require('fastify')({ querystringParser: str => querystring.parse(str.toLowerCase()) }) From eb0a5f7a7dea01b3cf41cf70ec768fe5c720c970 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 1 Mar 2025 09:33:28 +0100 Subject: [PATCH 0941/1295] test: migrated request-error.test.js from tap to node:test (#5987) --- test/request-error.test.js | 208 ++++++++++++++++++++----------------- 1 file changed, 114 insertions(+), 94 deletions(-) diff --git a/test/request-error.test.js b/test/request-error.test.js index 921dce3e049..cddb4267849 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -2,12 +2,11 @@ const { connect } = require('node:net') const sget = require('simple-get').concat -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const { kRequest } = require('../lib/symbols.js') -test('default 400 on request error', t => { +test('default 400 on request error', (t, done) => { t.plan(4) const fastify = Fastify() @@ -26,25 +25,26 @@ test('default 400 on request error', t => { text: '12345678901234567890123456789012345678901234567890' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Bad Request', message: 'Simulated', statusCode: 400 }) + done() }) }) -test('default 400 on request error with custom error handler', t => { +test('default 400 on request error with custom error handler', (t, done) => { t.plan(6) const fastify = Fastify() fastify.setErrorHandler(function (err, request, reply) { - t.type(request, 'object') - t.type(request, fastify[kRequest].parent) + t.assert.strictEqual(typeof request, 'object') + t.assert.strictEqual(request instanceof fastify[kRequest].parent, true) reply .code(err.statusCode) .type('application/json; charset=utf-8') @@ -65,18 +65,19 @@ test('default 400 on request error with custom error handler', t => { text: '12345678901234567890123456789012345678901234567890' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Bad Request', message: 'Simulated', statusCode: 400 }) + done() }) }) -test('default clientError handler ignores ECONNRESET', t => { +test('default clientError handler ignores ECONNRESET', (t, done) => { t.plan(3) let logs = '' @@ -107,8 +108,8 @@ test('default clientError handler ignores ECONNRESET', t => { }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) const client = connect(fastify.server.address().port) @@ -117,8 +118,9 @@ test('default clientError handler ignores ECONNRESET', t => { }) client.on('end', () => { - t.match(response, /^HTTP\/1.1 200 OK/) - t.notMatch(logs, /ECONNRESET/) + t.assert.match(response, /^HTTP\/1.1 200 OK/) + t.assert.notEqual(logs, /ECONNRESET/) + done() }) client.resume() @@ -138,15 +140,15 @@ test('default clientError handler ignores sockets in destroyed state', t => { }) fastify.server.on('clientError', () => { // this handler is called after default handler, so we can make sure end was not called - t.pass() + t.assert.ok('end should not be called') }) fastify.server.emit('clientError', new Error(), { destroyed: true, end () { - t.fail('end should not be called') + t.assert.fail('end should not be called') }, destroy () { - t.fail('destroy should not be called') + t.assert.fail('destroy should not be called') } }) }) @@ -164,13 +166,13 @@ test('default clientError handler destroys sockets in writable state', t => { writable: true, encrypted: true, end () { - t.fail('end should not be called') + t.assert.fail('end should not be called') }, destroy () { - t.pass('destroy should be called') + t.assert.ok('destroy should be called') }, write (response) { - t.match(response, /^HTTP\/1.1 400 Bad Request/) + t.assert.match(response, /^HTTP\/1.1 400 Bad Request/) } }) }) @@ -187,24 +189,24 @@ test('default clientError handler destroys http sockets in non-writable state', destroyed: false, writable: false, end () { - t.fail('end should not be called') + t.assert.fail('end should not be called') }, destroy () { - t.pass('destroy should be called') + t.assert.ok('destroy should be called') }, write (response) { - t.fail('write should not be called') + t.assert.fail('write should not be called') } }) }) -test('error handler binding', t => { +test('error handler binding', (t, done) => { t.plan(5) const fastify = Fastify() fastify.setErrorHandler(function (err, request, reply) { - t.equal(this, fastify) + t.assert.strictEqual(this, fastify) reply .code(err.statusCode) .type('application/json; charset=utf-8') @@ -225,30 +227,31 @@ test('error handler binding', t => { text: '12345678901234567890123456789012345678901234567890' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Bad Request', message: 'Simulated', statusCode: 400 }) + done() }) }) -test('encapsulated error handler binding', t => { +test('encapsulated error handler binding', (t, done) => { t.plan(7) const fastify = Fastify() fastify.register(function (app, opts, done) { app.decorate('hello', 'world') - t.equal(app.hello, 'world') + t.assert.strictEqual(app.hello, 'world') app.post('/', function (req, reply) { reply.send({ hello: 'world' }) }) app.setErrorHandler(function (err, request, reply) { - t.equal(this.hello, 'world') + t.assert.strictEqual(this.hello, 'world') reply .code(err.statusCode) .type('application/json; charset=utf-8') @@ -267,19 +270,20 @@ test('encapsulated error handler binding', t => { text: '12345678901234567890123456789012345678901234567890' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.json(), { error: 'Bad Request', message: 'Simulated', statusCode: 400 }) - t.equal(fastify.hello, undefined) + t.assert.strictEqual(fastify.hello, undefined) + done() }) }) -test('default clientError replies with bad request on reused keep-alive connection', t => { +test('default clientError replies with bad request on reused keep-alive connection', (t, done) => { t.plan(2) let response = '' @@ -294,7 +298,7 @@ test('default clientError replies with bad request on reused keep-alive connecti }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) fastify.server.unref() const client = connect(fastify.server.address().port) @@ -304,7 +308,8 @@ test('default clientError replies with bad request on reused keep-alive connecti }) client.on('end', () => { - t.match(response, /^HTTP\/1.1 200 OK.*HTTP\/1.1 400 Bad Request/s) + t.assert.match(response, /^HTTP\/1.1 200 OK.*HTTP\/1.1 400 Bad Request/s) + done() }) client.resume() @@ -318,21 +323,21 @@ test('default clientError replies with bad request on reused keep-alive connecti }) }) -test('request.routeOptions should be immutable', t => { +test('request.routeOptions should be immutable', (t, done) => { t.plan(14) const fastify = Fastify() const handler = function (req, res) { - t.equal('POST', req.routeOptions.method) - t.equal('/', req.routeOptions.url) - t.throws(() => { req.routeOptions = null }, new TypeError('Cannot set property routeOptions of # which has only a getter')) - t.throws(() => { req.routeOptions.method = 'INVALID' }, new TypeError('Cannot assign to read only property \'method\' of object \'#\'')) - t.throws(() => { req.routeOptions.url = '//' }, new TypeError('Cannot assign to read only property \'url\' of object \'#\'')) - t.throws(() => { req.routeOptions.bodyLimit = 0xDEADBEEF }, new TypeError('Cannot assign to read only property \'bodyLimit\' of object \'#\'')) - t.throws(() => { req.routeOptions.attachValidation = true }, new TypeError('Cannot assign to read only property \'attachValidation\' of object \'#\'')) - t.throws(() => { req.routeOptions.logLevel = 'invalid' }, new TypeError('Cannot assign to read only property \'logLevel\' of object \'#\'')) - t.throws(() => { req.routeOptions.version = '95.0.1' }, new TypeError('Cannot assign to read only property \'version\' of object \'#\'')) - t.throws(() => { req.routeOptions.prefixTrailingSlash = true }, new TypeError('Cannot assign to read only property \'prefixTrailingSlash\' of object \'#\'')) - t.throws(() => { req.routeOptions.newAttribute = {} }, new TypeError('Cannot add property newAttribute, object is not extensible')) + t.assert.strictEqual('POST', req.routeOptions.method) + t.assert.strictEqual('/', req.routeOptions.url) + t.assert.throws(() => { req.routeOptions = null }, new TypeError('Cannot set property routeOptions of # which has only a getter')) + t.assert.throws(() => { req.routeOptions.method = 'INVALID' }, new TypeError('Cannot assign to read only property \'method\' of object \'#\'')) + t.assert.throws(() => { req.routeOptions.url = '//' }, new TypeError('Cannot assign to read only property \'url\' of object \'#\'')) + t.assert.throws(() => { req.routeOptions.bodyLimit = 0xDEADBEEF }, new TypeError('Cannot assign to read only property \'bodyLimit\' of object \'#\'')) + t.assert.throws(() => { req.routeOptions.attachValidation = true }, new TypeError('Cannot assign to read only property \'attachValidation\' of object \'#\'')) + t.assert.throws(() => { req.routeOptions.logLevel = 'invalid' }, new TypeError('Cannot assign to read only property \'logLevel\' of object \'#\'')) + t.assert.throws(() => { req.routeOptions.version = '95.0.1' }, new TypeError('Cannot assign to read only property \'version\' of object \'#\'')) + t.assert.throws(() => { req.routeOptions.prefixTrailingSlash = true }, new TypeError('Cannot assign to read only property \'prefixTrailingSlash\' of object \'#\'')) + t.assert.throws(() => { req.routeOptions.newAttribute = {} }, new TypeError('Cannot add property newAttribute, object is not extensible')) for (const key of Object.keys(req.routeOptions)) { if (typeof req.routeOptions[key] === 'object' && req.routeOptions[key] !== null) { @@ -347,8 +352,8 @@ test('request.routeOptions should be immutable', t => { handler }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'POST', @@ -357,17 +362,18 @@ test('request.routeOptions should be immutable', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('request.routeOptions.method is an uppercase string /1', t => { +test('request.routeOptions.method is an uppercase string /1', (t, done) => { t.plan(4) const fastify = Fastify() const handler = function (req, res) { - t.equal('POST', req.routeOptions.method) + t.assert.strictEqual('POST', req.routeOptions.method) res.send({}) } @@ -376,8 +382,8 @@ test('request.routeOptions.method is an uppercase string /1', t => { handler }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'POST', @@ -386,17 +392,18 @@ test('request.routeOptions.method is an uppercase string /1', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('request.routeOptions.method is an uppercase string /2', t => { +test('request.routeOptions.method is an uppercase string /2', (t, done) => { t.plan(4) const fastify = Fastify() const handler = function (req, res) { - t.equal('POST', req.routeOptions.method) + t.assert.strictEqual('POST', req.routeOptions.method) res.send({}) } @@ -407,8 +414,8 @@ test('request.routeOptions.method is an uppercase string /2', t => { handler }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'POST', @@ -417,17 +424,18 @@ test('request.routeOptions.method is an uppercase string /2', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('request.routeOptions.method is an uppercase string /3', t => { +test('request.routeOptions.method is an uppercase string /3', (t, done) => { t.plan(4) const fastify = Fastify() const handler = function (req, res) { - t.equal('POST', req.routeOptions.method) + t.assert.strictEqual('POST', req.routeOptions.method) res.send({}) } @@ -438,8 +446,8 @@ test('request.routeOptions.method is an uppercase string /3', t => { handler }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'POST', @@ -448,17 +456,18 @@ test('request.routeOptions.method is an uppercase string /3', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('request.routeOptions.method is an array with uppercase string', t => { +test('request.routeOptions.method is an array with uppercase string', (t, done) => { t.plan(4) const fastify = Fastify() const handler = function (req, res) { - t.strictSame(['POST'], req.routeOptions.method) + t.assert.deepStrictEqual(['POST'], req.routeOptions.method) res.send({}) } @@ -469,8 +478,8 @@ test('request.routeOptions.method is an array with uppercase string', t => { handler }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'POST', @@ -479,13 +488,14 @@ test('request.routeOptions.method is an array with uppercase string', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('test request.routeOptions.version', t => { +test('test request.routeOptions.version', (t, done) => { t.plan(7) const fastify = Fastify() @@ -494,7 +504,7 @@ test('test request.routeOptions.version', t => { url: '/version', constraints: { version: '1.2.0' }, handler: function (request, reply) { - t.equal('1.2.0', request.routeOptions.version) + t.assert.strictEqual('1.2.0', request.routeOptions.version) reply.send({}) } }) @@ -503,13 +513,21 @@ test('test request.routeOptions.version', t => { method: 'POST', url: '/version-undefined', handler: function (request, reply) { - t.equal(undefined, request.routeOptions.version) + t.assert.strictEqual(undefined, request.routeOptions.version) reply.send({}) } }) fastify.listen({ port: 0 }, function (err) { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } sget({ method: 'POST', @@ -518,8 +536,9 @@ test('test request.routeOptions.version', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completed() }) sget({ @@ -529,8 +548,9 @@ test('test request.routeOptions.version', t => { body: [], json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completed() }) }) }) From 8d4e98c56342982a81fe850d78bbda4b14e806da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 14:29:01 +0000 Subject: [PATCH 0942/1295] chore: Bump the dev-dependencies-eslint group with 2 updates (#5999) Bumps the dev-dependencies-eslint group with 2 updates: [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) and [@stylistic/eslint-plugin-js](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin-js). Updates `@stylistic/eslint-plugin` from 3.1.0 to 4.1.0 - [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases) - [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v4.1.0/packages/eslint-plugin) Updates `@stylistic/eslint-plugin-js` from 3.1.0 to 4.1.0 - [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases) - [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v4.1.0/packages/eslint-plugin-js) --- updated-dependencies: - dependency-name: "@stylistic/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies-eslint - dependency-name: "@stylistic/eslint-plugin-js" dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies-eslint ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8980259f36f..91c1f96a130 100644 --- a/package.json +++ b/package.json @@ -163,8 +163,8 @@ "@jsumners/line-reporter": "^1.0.1", "@sinclair/typebox": "^0.34.13", "@sinonjs/fake-timers": "^11.2.2", - "@stylistic/eslint-plugin": "^3.0.1", - "@stylistic/eslint-plugin-js": "^3.0.1", + "@stylistic/eslint-plugin": "^4.1.0", + "@stylistic/eslint-plugin-js": "^4.1.0", "@types/node": "^22.0.0", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", From e9485f1f692ee5d2aa096e9ed51cc2c8edb2bc69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 14:52:54 +0000 Subject: [PATCH 0943/1295] chore: Bump lycheeverse/lychee-action from 2.2.0 to 2.3.0 (#6001) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.2.0 to 2.3.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/f796c8b7d468feb9b8c0a46da3fac0af6874d374...f613c4a64e50d792e0b31ec34bbcbba12263c6a6) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 41c49209712..aa9d04f9fef 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -19,7 +19,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@f796c8b7d468feb9b8c0a46da3fac0af6874d374 + uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 with: fail: true # As external links behavior is not predictable, we check only internal links From c7c5336a26051f0bf7f94eb773d8685c8dae0379 Mon Sep 17 00:00:00 2001 From: Logan Luo <80801525+logan272@users.noreply.github.com> Date: Mon, 3 Mar 2025 07:23:39 +0800 Subject: [PATCH 0944/1295] docs: fix docorators example (#5997) * Update Decorators.md Signed-off-by: Logan Luo <80801525+logan272@users.noreply.github.com> * Update docs/Reference/Decorators.md Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> Signed-off-by: Logan Luo <80801525+logan272@users.noreply.github.com> --------- Signed-off-by: Logan Luo <80801525+logan272@users.noreply.github.com> Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --- docs/Reference/Decorators.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 23b3904fb8c..f7b6a04a714 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -187,9 +187,9 @@ incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). const fp = require('fastify-plugin') async function myPlugin (app) { - app.decorateRequest('foo') + app.decorateReply('foo') app.addHook('onRequest', async (req, reply) => { - req.foo = { bar: 42 } + reply.foo = { bar: 42 } }) } From dd358cb1f3c6e7f7c7e6fe9273e2c26f86dec7a1 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Wed, 5 Mar 2025 18:50:31 +0100 Subject: [PATCH 0945/1295] chore: fix docs (#6007) Signed-off-by: Manuel Spigolon --- docs/Reference/LTS.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 38a7a065dde..f163543f7e8 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -37,10 +37,13 @@ A "month" is defined as 30 consecutive days. > As a consequence of providing long-term support for major releases, there are > occasions where we need to release breaking changes as a _minor_ version > release. Such changes will _always_ be noted in the [release -> +> notes](https://github.com/fastify/fastify/releases). +> > To avoid automatically receiving breaking security updates it is possible to > use the tilde (`~`) range qualifier. For example, to get patches for the 3.15 > release, and avoid automatically updating to the 3.16 release, specify the +> dependency as `"fastify": "~3.15.x"`. This will leave your application +> vulnerable, so please use it with caution. ### Security Support Beyond LTS From fbd7411f40e12a26fc2f841ab382310c0a87e959 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 8 Mar 2025 08:29:46 +0100 Subject: [PATCH 0946/1295] test: migrated stream.5.test.js from tap to node:test (#5955) --- test/stream.5.test.js | 68 ++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/test/stream.5.test.js b/test/stream.5.test.js index 743e934cc31..05a5d79b7c9 100644 --- a/test/stream.5.test.js +++ b/test/stream.5.test.js @@ -1,14 +1,13 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const proxyquire = require('proxyquire') const fs = require('node:fs') const Readable = require('node:stream').Readable const sget = require('simple-get').concat const Fastify = require('..') -test('should destroy stream when response is ended', t => { +test('should destroy stream when response is ended', (t, done) => { t.plan(4) const stream = require('node:stream') const fastify = Fastify() @@ -17,7 +16,7 @@ test('should destroy stream when response is ended', t => { const reallyLongStream = new stream.Readable({ read: function () { }, destroy: function (err, callback) { - t.ok('called') + t.assert.ok('called') callback(err) } }) @@ -26,23 +25,25 @@ test('should destroy stream when response is ended', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) }) -test('should mark reply as sent before pumping the payload stream into response for async route handler', t => { +test('should mark reply as sent before pumping the payload stream into response for async route handler', (t, done) => { t.plan(3) + t.after(() => fastify.close()) const handleRequest = proxyquire('../lib/handleRequest', { './wrapThenable': (thenable, reply) => { thenable.then(function (payload) { - t.equal(reply.sent, true) + t.assert.strictEqual(reply.sent, true) }) } }) @@ -66,20 +67,20 @@ test('should mark reply as sent before pumping the payload stream into response url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.payload, fs.readFileSync(__filename, 'utf8')) - fastify.close() + t.assert.ifError(err) + t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8')) + done() }) }) -test('reply.send handles aborted requests', t => { +test('reply.send handles aborted requests', (t, done) => { t.plan(2) const spyLogger = { level: 'error', fatal: () => { }, error: () => { - t.fail('should not log an error') + t.assert.fail('should not log an error') }, warn: () => { }, info: () => { }, @@ -103,31 +104,31 @@ test('reply.send handles aborted requests', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) const port = fastify.server.address().port const http = require('node:http') const req = http.get(`http://localhost:${port}`) .on('error', (err) => { - t.equal(err.code, 'ECONNRESET') - fastify.close() + t.assert.strictEqual(err.code, 'ECONNRESET') + done() }) setTimeout(() => { - req.abort() + req.destroy() }, 1) }) }) -test('request terminated should not crash fastify', t => { +test('request terminated should not crash fastify', (t, done) => { t.plan(10) const spyLogger = { level: 'error', fatal: () => { }, error: () => { - t.fail('should not log an error') + t.assert.fail('should not log an error') }, warn: () => { }, info: () => { }, @@ -156,18 +157,18 @@ test('request terminated should not crash fastify', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) const port = fastify.server.address().port const http = require('node:http') const req = http.get(`http://localhost:${port}`, function (res) { const { statusCode, headers } = res - t.equal(statusCode, 200) - t.equal(headers['content-type'], 'text/html; charset=utf-8') - t.equal(headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(headers['content-type'], 'text/html; charset=utf-8') + t.assert.strictEqual(headers['transfer-encoding'], 'chunked') res.on('data', function (chunk) { - t.equal(chunk.toString(), '

HTML

') + t.assert.strictEqual(chunk.toString(), '

HTML

') }) setTimeout(() => { @@ -176,16 +177,17 @@ test('request terminated should not crash fastify', t => { // the server is not crash, we can connect it http.get(`http://localhost:${port}`, function (res) { const { statusCode, headers } = res - t.equal(statusCode, 200) - t.equal(headers['content-type'], 'text/html; charset=utf-8') - t.equal(headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(headers['content-type'], 'text/html; charset=utf-8') + t.assert.strictEqual(headers['transfer-encoding'], 'chunked') let payload = '' res.on('data', function (chunk) { payload += chunk.toString() }) res.on('end', function () { - t.equal(payload, '

HTML

should display on second stream

') - t.pass('should end properly') + t.assert.strictEqual(payload, '

HTML

should display on second stream

') + t.assert.ok('should end properly') + done() }) }) }, 1) From 7ae0ed4a23e398e6019385d71b80be6a73a2dae2 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 8 Mar 2025 09:10:09 +0000 Subject: [PATCH 0947/1295] build(test/bundler/esbuild): bump esbuild (#6010) Bumped to mitigate https://github.com/advisories/GHSA-67mh-4wv8-2f99 Signed-off-by: Frazer Smith --- test/bundler/esbuild/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bundler/esbuild/package.json b/test/bundler/esbuild/package.json index 26b94c6adfd..b86ae19679f 100644 --- a/test/bundler/esbuild/package.json +++ b/test/bundler/esbuild/package.json @@ -5,6 +5,6 @@ "test": "npm run bundle && node bundler-test.js" }, "devDependencies": { - "esbuild": "^0.14.11" + "esbuild": "^0.25.0" } } From 30b81299c5b8b2dca9db20558a440c5c2b424660 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 8 Mar 2025 18:12:53 +0100 Subject: [PATCH 0948/1295] test: migrated throw.test.js from tap to node:test (#6002) --- test/throw.test.js | 178 ++++++++++++++++++++++----------------------- 1 file changed, 87 insertions(+), 91 deletions(-) diff --git a/test/throw.test.js b/test/throw.test.js index eb31d756a05..747512e30b3 100644 --- a/test/throw.test.js +++ b/test/throw.test.js @@ -1,20 +1,20 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') -test('Fastify should throw on wrong options', t => { +test('Fastify should throw on wrong options', (t) => { t.plan(2) try { Fastify('lol') - t.fail() + t.assert.fail() } catch (e) { - t.equal(e.message, 'Options must be an object') - t.pass() + t.assert.strictEqual(e.message, 'Options must be an object') + t.assert.ok(true) } }) -test('Fastify should throw on multiple assignment to the same route', t => { +test('Fastify should throw on multiple assignment to the same route', (t) => { t.plan(1) const fastify = Fastify() @@ -22,14 +22,14 @@ test('Fastify should throw on multiple assignment to the same route', t => { try { fastify.get('/', () => {}) - t.fail('Should throw fastify duplicated route declaration') + t.assert.fail('Should throw fastify duplicated route declaration') } catch (error) { - t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') + t.assert.strictEqual(error.code, 'FST_ERR_DUPLICATED_ROUTE') } }) -test('Fastify should throw for an invalid schema, printing the error route - headers', t => { - t.plan(2) +test('Fastify should throw for an invalid schema, printing the error route - headers', async (t) => { + t.plan(1) const badSchema = { type: 'object', @@ -39,20 +39,18 @@ test('Fastify should throw for an invalid schema, printing the error route - hea } } } - const fastify = Fastify() fastify.get('/', { schema: { headers: badSchema } }, () => {}) fastify.get('/not-loaded', { schema: { headers: badSchema } }, () => {}) - fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.match(err.message, /Failed building the validation schema for GET: \//) + await t.assert.rejects(fastify.ready(), { + code: 'FST_ERR_SCH_VALIDATION_BUILD', + message: /Failed building the validation schema for GET: \// }) }) -test('Fastify should throw for an invalid schema, printing the error route - body', t => { - t.plan(2) - +test('Fastify should throw for an invalid schema, printing the error route - body', async (t) => { + t.plan(1) const badSchema = { type: 'object', properties: { @@ -68,13 +66,13 @@ test('Fastify should throw for an invalid schema, printing the error route - bod done() }, { prefix: 'hello' }) - fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.match(err.message, /Failed building the validation schema for POST: \/hello\/form/) + await t.assert.rejects(fastify.ready(), { + code: 'FST_ERR_SCH_VALIDATION_BUILD', + message: /Failed building the validation schema for POST: \/hello\/form/ }) }) -test('Should throw on unsupported method', t => { +test('Should throw on unsupported method', async (t) => { t.plan(1) const fastify = Fastify() try { @@ -84,13 +82,13 @@ test('Should throw on unsupported method', t => { schema: {}, handler: function (req, reply) {} }) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) -test('Should throw on missing handler', t => { +test('Should throw on missing handler', (t) => { t.plan(1) const fastify = Fastify() try { @@ -98,15 +96,15 @@ test('Should throw on missing handler', t => { method: 'GET', url: '/' }) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) -test('Should throw if one method is unsupported', t => { - const fastify = Fastify() +test('Should throw if one method is unsupported', async (t) => { t.plan(1) + const fastify = Fastify() try { fastify.route({ method: ['GET', 'TROLL'], @@ -114,28 +112,27 @@ test('Should throw if one method is unsupported', t => { schema: {}, handler: function (req, reply) {} }) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) -test('Should throw on duplicate content type parser', t => { +test('Should throw on duplicate content type parser', async (t) => { t.plan(1) - const fastify = Fastify() function customParser (req, payload, done) { done(null, '') } fastify.addContentTypeParser('application/qq', customParser) try { fastify.addContentTypeParser('application/qq', customParser) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) -test('Should throw on duplicate decorator', t => { +test('Should throw on duplicate decorator', async (t) => { t.plan(1) const fastify = Fastify() @@ -144,31 +141,30 @@ test('Should throw on duplicate decorator', t => { fastify.decorate('foo', fooObj) try { fastify.decorate('foo', fooObj) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) -test('Should not throw on duplicate decorator encapsulation', t => { +test('Should not throw on duplicate decorator encapsulation', async (t) => { t.plan(1) - const fastify = Fastify() const foo2Obj = {} fastify.decorate('foo2', foo2Obj) fastify.register(function (fastify, opts, done) { - t.doesNotThrow(() => { + t.assert.doesNotThrow(() => { fastify.decorate('foo2', foo2Obj) }) done() }) - fastify.ready() + await fastify.ready() }) -test('Should throw on duplicate request decorator', t => { +test('Should throw on duplicate request decorator', async (t) => { t.plan(2) const fastify = Fastify() @@ -176,28 +172,28 @@ test('Should throw on duplicate request decorator', t => { fastify.decorateRequest('foo', null) try { fastify.decorateRequest('foo', null) - t.fail() + t.assert.fail() } catch (e) { - t.equal(e.code, 'FST_ERR_DEC_ALREADY_PRESENT') - t.equal(e.message, 'The decorator \'foo\' has already been added!') + t.assert.strictEqual(e.code, 'FST_ERR_DEC_ALREADY_PRESENT') + t.assert.strictEqual(e.message, 'The decorator \'foo\' has already been added!') } }) -test('Should throw if request decorator dependencies are not met', t => { +test('Should throw if request decorator dependencies are not met', async (t) => { t.plan(2) const fastify = Fastify() try { fastify.decorateRequest('bar', null, ['world']) - t.fail() + t.assert.fail() } catch (e) { - t.equal(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') - t.equal(e.message, 'The decorator is missing dependency \'world\'.') + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'world\'.') } }) -test('Should throw on duplicate reply decorator', t => { +test('Should throw on duplicate reply decorator', async (t) => { t.plan(1) const fastify = Fastify() @@ -205,149 +201,149 @@ test('Should throw on duplicate reply decorator', t => { fastify.decorateReply('foo', null) try { fastify.decorateReply('foo', null) - t.fail() + t.assert.fail() } catch (e) { - t.ok(/has already been added/.test(e.message)) + t.assert.ok(/has already been added/.test(e.message)) } }) -test('Should throw if reply decorator dependencies are not met', t => { +test('Should throw if reply decorator dependencies are not met', async (t) => { t.plan(1) const fastify = Fastify() try { fastify.decorateReply('bar', null, ['world']) - t.fail() + t.assert.fail() } catch (e) { - t.ok(/missing dependency/.test(e.message)) + t.assert.ok(/missing dependency/.test(e.message)) } }) -test('Should throw if handler as the third parameter to the shortcut method is missing and the second parameter is not a function and also not an object', t => { +test('Should throw if handler as the third parameter to the shortcut method is missing and the second parameter is not a function and also not an object', async (t) => { t.plan(5) const fastify = Fastify() try { fastify.get('/foo/1', '') - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/2', 1) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/3', []) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/4', undefined) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/5', null) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) -test('Should throw if handler as the third parameter to the shortcut method is missing and the second parameter is not a function and also not an object', t => { +test('Should throw if handler as the third parameter to the shortcut method is missing and the second parameter is not a function and also not an object', async (t) => { t.plan(5) const fastify = Fastify() try { fastify.get('/foo/1', '') - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/2', 1) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/3', []) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/4', undefined) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/5', null) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) -test('Should throw if there is handler function as the third parameter to the shortcut method and options as the second parameter is not an object', t => { +test('Should throw if there is handler function as the third parameter to the shortcut method and options as the second parameter is not an object', async (t) => { t.plan(5) const fastify = Fastify() try { fastify.get('/foo/1', '', (req, res) => {}) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/2', 1, (req, res) => {}) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/3', [], (req, res) => {}) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/4', undefined, (req, res) => {}) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } try { fastify.get('/foo/5', null, (req, res) => {}) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) -test('Should throw if found duplicate handler as the third parameter to the shortcut method and in options', t => { +test('Should throw if found duplicate handler as the third parameter to the shortcut method and in options', async (t) => { t.plan(1) const fastify = Fastify() @@ -356,8 +352,8 @@ test('Should throw if found duplicate handler as the third parameter to the shor fastify.get('/foo/abc', { handler: (req, res) => {} }, (req, res) => {}) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) } }) From 40ea7d8911698e86e188fcc50d3b26e9dc83bb93 Mon Sep 17 00:00:00 2001 From: _sss Date: Sun, 9 Mar 2025 23:59:15 +0700 Subject: [PATCH 0949/1295] docs(guides/ecosystem): update broken link to scalar (#6015) --- docs/Guides/Ecosystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f3deff062ee..003f1f5d7bb 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -218,7 +218,7 @@ section. A custom compact pino-base prettifier - [`@pybot/fastify-autoload`](https://github.com/kunal097/fastify-autoload) Plugin to generate routes automatically with valid json content -- [`@scalar/fastify-api-reference`](https://github.com/scalar/scalar/tree/main/packages/fastify-api-reference) +- [`@scalar/fastify-api-reference`](https://github.com/scalar/scalar/tree/main/integrations/fastify) Beautiful OpenAPI/Swagger API references for Fastify - [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs) SeaweedFS for Fastify From 0439dd128b9737aea66033e9d52ea80b24949863 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 9 Mar 2025 18:45:57 +0100 Subject: [PATCH 0950/1295] feat: add listen async callback warning (#6011) --- lib/server.js | 5 +++++ lib/warnings.js | 9 +++++++++ test/listen.5.test.js | 23 +++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/lib/server.js b/lib/server.js index dee3ced8c25..14399d6e3e0 100644 --- a/lib/server.js +++ b/lib/server.js @@ -6,6 +6,7 @@ const dns = require('node:dns') const os = require('node:os') const { kState, kOptions, kServerBindings } = require('./symbols') +const { FSTWRN003 } = require('./warnings') const { onListenHookRunner } = require('./hooks') const { FST_ERR_HTTP2_INVALID_VERSION, @@ -29,6 +30,10 @@ function createServer (options, httpHandler) { cb = undefined ) { if (typeof cb === 'function') { + if (cb.constructor.name === 'AsyncFunction') { + FSTWRN003('listen method') + } + listenOptions.cb = cb } if (listenOptions.signal) { diff --git a/lib/warnings.js b/lib/warnings.js index 3863b236fa3..a9cb27f9874 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -8,6 +8,7 @@ const { createWarning } = require('process-warning') * - FSTSEC001 * * Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT not be reused. + * Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT not be reused. */ const FSTWRN001 = createWarning({ @@ -17,6 +18,13 @@ const FSTWRN001 = createWarning({ unlimited: true }) +const FSTWRN003 = createWarning({ + name: 'FastifyWarning', + code: 'FSTWRN003', + message: 'The %s mixes async and callback styles that may lead to unhandled rejections. Please use only one of them.', + unlimited: true +}) + const FSTSEC001 = createWarning({ name: 'FastifySecurity', code: 'FSTSEC001', @@ -26,5 +34,6 @@ const FSTSEC001 = createWarning({ module.exports = { FSTWRN001, + FSTWRN003, FSTSEC001 } diff --git a/test/listen.5.test.js b/test/listen.5.test.js index 2cc00aa86f6..68aa23a1043 100644 --- a/test/listen.5.test.js +++ b/test/listen.5.test.js @@ -4,6 +4,7 @@ const { test } = require('node:test') const net = require('node:net') const Fastify = require('../fastify') const { once } = require('node:events') +const { FSTWRN003 } = require('../lib/warnings.js') function createDeferredPromise () { const promise = {} @@ -97,3 +98,25 @@ test('same port conflict and success should not fire callback multiple times - p await fastify.listen() await fastify.close() }) + +test('should emit a warning when using async callback', (t, done) => { + t.plan(2) + + process.on('warning', onWarning) + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN003.code) + } + + const fastify = Fastify() + + t.after(async () => { + await fastify.close() + process.removeListener('warning', onWarning) + FSTWRN003.emitted = false + }) + + fastify.listen({ port: 0 }, async function doNotUseAsyncCallback () { + done() + }) +}) From 8b37b861a3cd62ad145bc9d9a3cec010cf2f1485 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 10 Mar 2025 08:21:59 +0000 Subject: [PATCH 0951/1295] docs(readme): fix broken ci badges (#6016) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 262d29040aa..a7279c28a14 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ [![CI](https://github.com/fastify/fastify/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/ci.yml) [![Package Manager -CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml) +CI](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml) [![Web -SIte](https://github.com/fastify/fastify/workflows/website/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml) +site](https://github.com/fastify/fastify/actions/workflows/website.yml/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml) [![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585) From 5aa6789e4c13480e31c498948fb5173db70f73f9 Mon Sep 17 00:00:00 2001 From: Piotr Date: Thu, 13 Mar 2025 18:26:40 +0100 Subject: [PATCH 0952/1295] Remove --node-arg prefix (#6018) --- docs/Guides/Testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Guides/Testing.md b/docs/Guides/Testing.md index 3fb40816600..5fdf864787b 100644 --- a/docs/Guides/Testing.md +++ b/docs/Guides/Testing.md @@ -340,10 +340,10 @@ test('should ...', {only: true}, t => ...) ``` 2. Run `node --test` ```bash -> node --test --test-only --node-arg=--inspect-brk test/ +> node --test --test-only --inspect-brk test/ ``` - `--test-only` specifies to run tests with the `only` option enabled -- `--node-arg=--inspect-brk` will launch the node debugger +- `--inspect-brk` will launch the node debugger 3. In VS Code, create and launch a `Node.js: Attach` debug configuration. No modification should be necessary. From ae92b6e1c56e2bb75e449345c1870377c2c27a8d Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Fri, 14 Mar 2025 18:21:50 +0100 Subject: [PATCH 0953/1295] test: migrated listen.1.test.js from tap to node:test (#6014) --- test/listen.1.test.js | 71 ++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/test/listen.1.test.js b/test/listen.1.test.js index d47765b5f56..94862d5d83f 100644 --- a/test/listen.1.test.js +++ b/test/listen.1.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test, before } = require('tap') +const { test, before } = require('node:test') const Fastify = require('..') const helper = require('./helper') @@ -13,69 +13,70 @@ before(async function () { test('listen works without arguments', async t => { const doNotWarn = () => { - t.fail('should not be deprecated') + t.assert.fail('should not be deprecated') } process.on('warning', doNotWarn) const fastify = Fastify() - t.teardown(() => { + t.after(() => { fastify.close() process.removeListener('warning', doNotWarn) }) await fastify.listen() const address = fastify.server.address() - t.equal(address.address, localhost) - t.ok(address.port > 0) + t.assert.strictEqual(address.address, localhost) + t.assert.ok(address.port > 0) }) test('Async/await listen with arguments', async t => { const doNotWarn = () => { - t.fail('should not be deprecated') + t.assert.fail('should not be deprecated') } process.on('warning', doNotWarn) const fastify = Fastify() - t.teardown(() => { + t.after(() => { fastify.close() process.removeListener('warning', doNotWarn) }) const addr = await fastify.listen({ port: 0, host: '0.0.0.0' }) const address = fastify.server.address() - t.equal(addr, `http://127.0.0.1:${address.port}`) - t.same(address, { + t.assert.strictEqual(addr, `http://127.0.0.1:${address.port}`) + t.assert.deepEqual(address, { address: '0.0.0.0', family: 'IPv4', port: address.port }) }) -test('listen accepts a callback', t => { +test('listen accepts a callback', (t, done) => { t.plan(2) const doNotWarn = () => { - t.fail('should not be deprecated') + t.assert.fail('should not be deprecated') } process.on('warning', doNotWarn) const fastify = Fastify() - t.teardown(() => { + t.after(() => { fastify.close() process.removeListener('warning', doNotWarn) }) fastify.listen({ port: 0 }, (err) => { - t.equal(fastify.server.address().address, localhost) - t.error(err) + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + done() }) }) -test('listen accepts options and a callback', t => { +test('listen accepts options and a callback', (t, done) => { t.plan(1) const doNotWarn = () => { - t.fail('should not be deprecated') + t.assert.fail('should not be deprecated') } process.on('warning', doNotWarn) const fastify = Fastify() - t.teardown(() => { + t.after(() => { fastify.close() process.removeListener('warning', doNotWarn) }) @@ -88,40 +89,42 @@ test('listen accepts options and a callback', t => { writableAll: false, ipv6Only: false }, (err) => { - t.error(err) + t.assert.ifError(err) + done() }) }) -test('listen after Promise.resolve()', t => { +test('listen after Promise.resolve()', (t, done) => { t.plan(2) - const f = Fastify() - t.teardown(f.close.bind(f)) + const fastify = Fastify() + t.after(() => fastify.close()) Promise.resolve() .then(() => { - f.listen({ port: 0 }, (err, address) => { - f.server.unref() - t.equal(address, `http://${localhostForURL}:${f.server.address().port}`) - t.error(err) + fastify.listen({ port: 0 }, (err, address) => { + fastify.server.unref() + t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`) + t.assert.ifError(err) + done() }) }) }) test('listen works with undefined host', async t => { const doNotWarn = () => { - t.fail('should not be deprecated') + t.assert.fail('should not be deprecated') } process.on('warning', doNotWarn) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - t.teardown(() => { + t.after(() => fastify.close()) + t.after(() => { fastify.close() process.removeListener('warning', doNotWarn) }) await fastify.listen({ host: undefined, port: 0 }) const address = fastify.server.address() - t.equal(address.address, localhost) - t.ok(address.port > 0) + t.assert.strictEqual(address.address, localhost) + t.assert.ok(address.port > 0) }) test('listen works with null host', async t => { @@ -131,13 +134,13 @@ test('listen works with null host', async t => { process.on('warning', doNotWarn) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - t.teardown(() => { + t.after(() => fastify.close()) + t.after(() => { fastify.close() process.removeListener('warning', doNotWarn) }) await fastify.listen({ host: null, port: 0 }) const address = fastify.server.address() - t.equal(address.address, localhost) - t.ok(address.port > 0) + t.assert.strictEqual(address.address, localhost) + t.assert.ok(address.port > 0) }) From ce3811f5f718be278bbcd4392c615d64230065a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 17:28:42 +0000 Subject: [PATCH 0954/1295] chore: Bump typescript in the dev-dependencies-typescript group (#6000) Bumps the dev-dependencies-typescript group with 1 update: [typescript](https://github.com/microsoft/TypeScript). Updates `typescript` from 5.7.3 to 5.8.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.3...v5.8.2) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies-typescript ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91c1f96a130..d19593eb8de 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,7 @@ "split2": "^4.2.0", "tap": "^21.0.0", "tsd": "^0.31.0", - "typescript": "~5.7.2", + "typescript": "~5.8.2", "undici": "^6.13.0", "vary": "^1.1.2", "yup": "^1.4.0" From 97df2dfbd9411c3a68fc2d8107f13cf32f5f797f Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Fri, 21 Mar 2025 07:42:14 +0100 Subject: [PATCH 0955/1295] =?UTF-8?q?test:=20=20migrated=20upgrade.test.js?= =?UTF-8?q?=20from=20tap=20to=20node:test=20and=20update=20inde=E2=80=A6?= =?UTF-8?q?=20(#5917)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: migrated upgrade.test.js from tap to node:test and update indentation * test: update test * test: update test --- test/has-route.test.js | 4 +-- test/upgrade.test.js | 62 ++++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/test/has-route.test.js b/test/has-route.test.js index a00ee2cb31f..97761b1eb13 100644 --- a/test/has-route.test.js +++ b/test/has-route.test.js @@ -1,7 +1,7 @@ 'use strict' const { test, describe } = require('node:test') -const Fastify = require('../fastify') +const Fastify = require('..') const fastify = Fastify() @@ -10,9 +10,7 @@ describe('hasRoute', async t => { t.plan(3) t.assert.strictEqual(fastify.hasRoute({ }), false) - t.assert.strictEqual(fastify.hasRoute({ method: 'GET' }), false) - t.assert.strictEqual(fastify.hasRoute({ constraints: [] }), false) }) diff --git a/test/upgrade.test.js b/test/upgrade.test.js index fa54d4e90ac..194294b5321 100644 --- a/test/upgrade.test.js +++ b/test/upgrade.test.js @@ -1,53 +1,55 @@ 'use strict' -const { test, skip } = require('tap') +const { describe, test } = require('node:test') const Fastify = require('..') const { connect } = require('node:net') const { once } = require('node:events') const dns = require('node:dns').promises -async function setup () { +describe('upgrade to both servers', async t => { const localAddresses = await dns.lookup('localhost', { all: true }) if (localAddresses.length === 1) { - skip('requires both IPv4 and IPv6') + t.skip('requires both IPv4 and IPv6') return } - test('upgrade to both servers', async t => { + await test('upgrade IPv4 and IPv6', async t => { t.plan(2) - const app = Fastify() - app.server.on('upgrade', (req, socket, head) => { - t.pass(`upgrade event ${JSON.stringify(socket.address())}`) + + const fastify = Fastify() + fastify.server.on('upgrade', (req, socket, head) => { + t.assert.ok(`upgrade event ${JSON.stringify(socket.address())}`) socket.end() }) - app.get('/', (req, res) => { + + fastify.get('/', (req, res) => { + res.send() }) - await app.listen() - t.teardown(app.close.bind(app)) + + await fastify.listen() + t.after(() => fastify.close()) { - const client = connect(app.server.address().port, '127.0.0.1') - client.write('GET / HTTP/1.1\r\n') - client.write('Upgrade: websocket\r\n') - client.write('Connection: Upgrade\r\n') - client.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n') - client.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n') - client.write('Sec-WebSocket-Version: 13\r\n\r\n') - client.write('\r\n\r\n') - await once(client, 'close') + const clientIPv4 = connect(fastify.server.address().port, '127.0.0.1') + clientIPv4.write('GET / HTTP/1.1\r\n') + clientIPv4.write('Upgrade: websocket\r\n') + clientIPv4.write('Connection: Upgrade\r\n') + clientIPv4.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n') + clientIPv4.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n') + clientIPv4.write('Sec-WebSocket-Version: 13\r\n\r\n') + clientIPv4.write('\r\n\r\n') + await once(clientIPv4, 'close') } { - const client = connect(app.server.address().port, '::1') - client.write('GET / HTTP/1.1\r\n') - client.write('Upgrade: websocket\r\n') - client.write('Connection: Upgrade\r\n') - client.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n') - client.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n') - client.write('Sec-WebSocket-Version: 13\r\n\r\n') - await once(client, 'close') + const clientIPv6 = connect(fastify.server.address().port, '::1') + clientIPv6.write('GET / HTTP/1.1\r\n') + clientIPv6.write('Upgrade: websocket\r\n') + clientIPv6.write('Connection: Upgrade\r\n') + clientIPv6.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n') + clientIPv6.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n') + clientIPv6.write('Sec-WebSocket-Version: 13\r\n\r\n') + await once(clientIPv6, 'close') } }) -} - -setup() +}) From f2b4f16d5868867f2388760816ab181fde6984f7 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 22 Mar 2025 15:09:31 +0100 Subject: [PATCH 0956/1295] flaky (#6021) --- test/toolkit.js | 32 ++++++++++++++++++++++++++++ test/trust-proxy.test.js | 46 ++++++++++++++++++++-------------------- 2 files changed, 55 insertions(+), 23 deletions(-) create mode 100644 test/toolkit.js diff --git a/test/toolkit.js b/test/toolkit.js new file mode 100644 index 00000000000..a3c5fec7f15 --- /dev/null +++ b/test/toolkit.js @@ -0,0 +1,32 @@ +'use strict' + +exports.waitForCb = function (options) { + let count = null + let done = false + let iResolve + let iReject + + function stepIn () { + if (done) { + iReject(new Error('Unexpected done call')) + return + } + + if (--count) { + return + } + + done = true + iResolve() + } + + const patience = new Promise((resolve, reject) => { + iResolve = resolve + iReject = reject + }) + + count = options.steps || 1 + done = false + + return { stepIn, patience } +} diff --git a/test/trust-proxy.test.js b/test/trust-proxy.test.js index 7d108596065..d1b698ba14d 100644 --- a/test/trust-proxy.test.js +++ b/test/trust-proxy.test.js @@ -4,6 +4,7 @@ const { test, before } = require('node:test') const sget = require('simple-get').concat const fastify = require('..') const helper = require('./helper') +const { waitForCb } = require('./toolkit') const noop = () => {} @@ -69,18 +70,14 @@ test('trust proxy, not add properties to node req', (t, done) => { }) app.listen({ port: 0 }, (err) => { - app.server.unref() t.assert.ifError(err) - sgetForwardedRequest(app, '1.1.1.1', '/trustproxy', undefined, completed) - sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined, completed) + const completion = waitForCb({ steps: 2 }) - let pending = 2 - function completed () { - if (--pending === 0) { - done() - } - } + sgetForwardedRequest(app, '1.1.1.1', '/trustproxy', undefined, completion.stepIn) + sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined, completion.stepIn) + + completion.patience.then(done) }) }) @@ -89,6 +86,7 @@ test('trust proxy chain', (t, done) => { const app = fastify({ trustProxy: [localhost, '192.168.1.1'] }) + t.after(() => app.close()) app.get('/trustproxychain', function (req, reply) { testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) @@ -96,9 +94,7 @@ test('trust proxy chain', (t, done) => { }) app.listen({ port: 0 }, (err) => { - app.server.unref() t.assert.ifError(err) - t.after(() => app.close()) sgetForwardedRequest(app, '192.168.1.1, 1.1.1.1', '/trustproxychain', undefined, done) }) }) @@ -108,15 +104,15 @@ test('trust proxy function', (t, done) => { const app = fastify({ trustProxy: (address) => address === localhost }) + t.after(() => app.close()) + app.get('/trustproxyfunc', function (req, reply) { testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) app.listen({ port: 0 }, (err) => { - app.server.unref() t.assert.ifError(err) - t.after(() => app.close()) sgetForwardedRequest(app, '1.1.1.1', '/trustproxyfunc', undefined, done) }) }) @@ -126,15 +122,15 @@ test('trust proxy number', (t, done) => { const app = fastify({ trustProxy: 1 }) + t.after(() => app.close()) + app.get('/trustproxynumber', function (req, reply) { testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'example.com', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) app.listen({ port: 0 }, (err) => { - app.server.unref() t.assert.ifError(err) - t.after(() => app.close()) sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined, done) }) }) @@ -144,15 +140,15 @@ test('trust proxy IP addresses', (t, done) => { const app = fastify({ trustProxy: `${localhost}, 2.2.2.2` }) + t.after(() => app.close()) + app.get('/trustproxyipaddrs', function (req, reply) { testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'example.com', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) app.listen({ port: 0 }, (err) => { - app.server.unref() t.assert.ifError(err) - t.after(() => app.close()) sgetForwardedRequest(app, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs', undefined, done) }) }) @@ -162,6 +158,8 @@ test('trust proxy protocol', (t, done) => { const app = fastify({ trustProxy: true }) + t.after(() => app.close()) + app.get('/trustproxyprotocol', function (req, reply) { testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem', host: 'example.com', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) @@ -175,14 +173,16 @@ test('trust proxy protocol', (t, done) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - t.after(() => app.close()) - app.listen({ port: 0 }, (err) => { - app.server.unref() t.assert.ifError(err) - sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocol', 'lorem') - sgetForwardedRequest(app, '1.1.1.1', '/trustproxynoprotocol') + + const completion = waitForCb({ steps: 3 }) + sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocol', 'lorem', completion.stepIn) + sgetForwardedRequest(app, '1.1.1.1', '/trustproxynoprotocol', undefined, completion.stepIn) + // Allow for sgetForwardedRequest requests above to finish - setTimeout(() => sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor', done)) + setTimeout(() => sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor', completion.stepIn)) + + completion.patience.then(done) }) }) From 664204f231020b5230778996bf149dfd1aa507a0 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 23 Mar 2025 15:42:09 +0100 Subject: [PATCH 0957/1295] test: migrated listen.3.test.js from tap to node:test (#6022) --- test/listen.3.test.js | 60 ++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/test/listen.3.test.js b/test/listen.3.test.js index f68cba76fcd..3f536232d41 100644 --- a/test/listen.3.test.js +++ b/test/listen.3.test.js @@ -3,7 +3,7 @@ const os = require('node:os') const path = require('node:path') const fs = require('node:fs') -const { test, before } = require('tap') +const { test, before } = require('node:test') const Fastify = require('..') const helper = require('./helper') @@ -15,73 +15,69 @@ before(async function () { // https://nodejs.org/api/net.html#net_ipc_support if (os.platform() !== 'win32') { - test('listen on socket', t => { - t.plan(3) + test('listen on socket', async t => { + t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').slice(2, 10)}-server.sock`) try { fs.unlinkSync(sockFile) } catch (e) { } - fastify.listen({ path: sockFile }, (err, address) => { - t.error(err) - t.strictSame(fastify.addresses(), [sockFile]) - t.equal(address, sockFile) - }) + await fastify.listen({ path: sockFile }) + t.assert.deepStrictEqual(fastify.addresses(), [sockFile]) + t.assert.strictEqual(fastify.server.address(), sockFile) }) } else { - test('listen on socket', t => { - t.plan(3) + test('listen on socket', async t => { + t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const sockFile = `\\\\.\\pipe\\${(Math.random().toString(16) + '0000000').slice(2, 10)}-server-sock` - fastify.listen({ path: sockFile }, (err, address) => { - t.error(err) - t.strictSame(fastify.addresses(), [sockFile]) - t.equal(address, sockFile) - }) + await fastify.listen({ path: sockFile }) + t.assert.deepStrictEqual(fastify.addresses(), [sockFile]) + t.assert.strictEqual(fastify.server.address(), sockFile) }) } -test('listen without callback with (address)', t => { +test('listen without callback with (address)', async t => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - fastify.listen({ port: 0 }) - .then(address => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - }) + t.after(() => fastify.close()) + const address = await fastify.listen({ port: 0 }) + t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`) }) -test('double listen without callback rejects', t => { +test('double listen without callback rejects', (t, done) => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }) .then(() => { fastify.listen({ port: 0 }) .catch(err => { - t.ok(err) + t.assert.ok(err) + done() }) }) - .catch(err => t.error(err)) + .catch(err => t.assert.ifError(err)) }) -test('double listen without callback with (address)', t => { +test('double listen without callback with (address)', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }) .then(address => { - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) + t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`) fastify.listen({ port: 0 }) .catch(err => { - t.ok(err) + t.assert.ok(err) + done() }) }) - .catch(err => t.error(err)) + .catch(err => t.assert.ifError(err)) }) From 6cbcfa7d82aaf25c7e53804e2a024c531d15c4d0 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 23 Mar 2025 15:43:42 +0100 Subject: [PATCH 0958/1295] test: migrated listen.4.test.js from tap to node:test (#6024) --- test/listen.4.test.js | 106 ++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 45 deletions(-) diff --git a/test/listen.4.test.js b/test/listen.4.test.js index f239bcef121..e1214b7e274 100644 --- a/test/listen.4.test.js +++ b/test/listen.4.test.js @@ -1,11 +1,12 @@ 'use strict' -const { test, before } = require('tap') +const { test, before } = require('node:test') const dns = require('node:dns').promises const dnsCb = require('node:dns') const sget = require('simple-get').concat const Fastify = require('../fastify') const helper = require('./helper') +const { waitForCb } = require('./toolkit') let localhostForURL @@ -22,64 +23,69 @@ before(async function () { [, localhostForURL] = await helper.getLoopbackHost() }) -test('listen twice on the same port without callback rejects', t => { +test('listen twice on the same port without callback rejects', (t, done) => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }) .then(() => { - const s2 = Fastify() - t.teardown(s2.close.bind(s2)) - s2.listen({ port: fastify.server.address().port }) + const server2 = Fastify() + t.after(() => server2.close()) + server2.listen({ port: fastify.server.address().port }) .catch(err => { - t.ok(err) + t.assert.ok(err) + done() }) }) - .catch(err => t.error(err)) + .catch(err => { + t.assert.ifError(err) + }) }) -test('listen twice on the same port without callback rejects with (address)', t => { +test('listen twice on the same port without callback rejects with (address)', (t, done) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }) .then(address => { - const s2 = Fastify() - t.teardown(s2.close.bind(s2)) - t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`) - s2.listen({ port: fastify.server.address().port }) + const server2 = Fastify() + t.after(() => server2.close()) + t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`) + + server2.listen({ port: fastify.server.address().port }) .catch(err => { - t.ok(err) + t.assert.ok(err) + done() }) }) - .catch(err => t.error(err)) + .catch(err => { + t.assert.ifError(err) + }) }) test('listen on invalid port without callback rejects', t => { const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) return fastify.listen({ port: -1 }) .catch(err => { - t.ok(err) + t.assert.ok(err) return true }) }) -test('listen logs the port as info', t => { +test('listen logs the port as info', async t => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const msgs = [] fastify.log.info = function (msg) { msgs.push(msg) } - fastify.listen({ port: 0 }) - .then(() => { - t.ok(/http:\/\//.test(msgs[0])) - }) + await fastify.listen({ port: 0 }) + t.assert.ok(/http:\/\//.test(msgs[0])) }) test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => { @@ -88,7 +94,7 @@ test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => { const app = Fastify() app.get('/', async () => 'hello localhost') - t.teardown(app.close.bind(app)) + t.after(() => app.close()) await app.listen({ port: 0, host: 'localhost' }) for (const lookup of localAddresses) { @@ -98,34 +104,43 @@ test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => { url: getUrl(app, lookup) }, (err, response, body) => { if (err) { return reject(err) } - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello localhost') + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), 'hello localhost') resolve() }) }) } }) -test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', t => { +test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', (t, done) => { dnsCb.lookup('localhost', { all: true }, (err, lookups) => { t.plan(2 + (3 * lookups.length)) - t.error(err) + t.assert.ifError(err) const app = Fastify() app.get('/', async () => 'hello localhost') app.listen({ port: 0, host: 'localhost' }, (err) => { - t.error(err) - t.teardown(app.close.bind(app)) - - for (const lookup of lookups) { - sget({ - method: 'GET', - url: getUrl(app, lookup) - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello localhost') - }) + t.assert.ifError(err) + t.after(() => app.close()) + + const { stepIn, patience } = waitForCb({ steps: lookups.length }) + + // Loop over each lookup and perform the assertions + if (lookups.length > 0) { + for (const lookup of lookups) { + sget({ + method: 'GET', + url: getUrl(app, lookup) + }, (err, response, body) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), 'hello localhost') + // Call stepIn to report that a request has been completed + stepIn() + }) + } + // When all requests have been completed, call done + patience.then(() => done()) } }) }) @@ -137,11 +152,12 @@ test('addresses getter', async t => { t.plan(4) const app = Fastify() app.get('/', async () => 'hello localhost') + t.after(() => app.close()) - t.same(app.addresses(), [], 'before ready') + t.assert.deepStrictEqual(app.addresses(), [], 'before ready') await app.ready() - t.same(app.addresses(), [], 'after ready') + t.assert.deepStrictEqual(app.addresses(), [], 'after ready') await app.listen({ port: 0, host: 'localhost' }) // fix citgm @@ -157,8 +173,8 @@ test('addresses getter', async t => { family: typeof a.family === 'number' ? 'IPv' + a.family : a.family })).sort() - t.same(appAddresses, localAddresses, 'after listen') + t.assert.deepStrictEqual(appAddresses, localAddresses, 'after listen') await app.close() - t.same(app.addresses(), [], 'after close') + t.assert.deepStrictEqual(app.addresses(), [], 'after close') }) From 0dfa46c466875ca1e2338c5ed0baf1359174fbcb Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 24 Mar 2025 19:07:23 +0100 Subject: [PATCH 0959/1295] fix: double hook execution (#6013) * fix: double hook execution * new approach * add: tests * fix * node 20 * handle missing socket Signed-off-by: Manuel Spigolon * slippery Signed-off-by: Manuel Spigolon * flaky * test: more assertions --------- Signed-off-by: Manuel Spigolon --- lib/wrapThenable.js | 9 ++++- test/issue-4959.test.js | 84 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 test/issue-4959.test.js diff --git a/lib/wrapThenable.js b/lib/wrapThenable.js index 8d3476557d2..8dddc72ac07 100644 --- a/lib/wrapThenable.js +++ b/lib/wrapThenable.js @@ -27,7 +27,14 @@ function wrapThenable (thenable, reply, store) { // the request may be terminated during the reply. in this situation, // it require an extra checking of request.aborted to see whether // the request is killed by client. - if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) { + if (payload !== undefined || // + (reply.sent === false && // + reply.raw.headersSent === false && + reply.request.raw.aborted === false && + reply.request.socket && + !reply.request.socket.destroyed + ) + ) { // we use a try-catch internally to avoid adding a catch to another // promise, increase promise perf by 10% try { diff --git a/test/issue-4959.test.js b/test/issue-4959.test.js new file mode 100644 index 00000000000..1ce50f50d25 --- /dev/null +++ b/test/issue-4959.test.js @@ -0,0 +1,84 @@ +'use strict' + +const { test } = require('node:test') +const http = require('node:http') +const Fastify = require('../fastify') + +function runBadClientCall (reqOptions, payload) { + let innerResolve, innerReject + const promise = new Promise((resolve, reject) => { + innerResolve = resolve + innerReject = reject + }) + + const postData = JSON.stringify(payload) + + const req = http.request({ + ...reqOptions, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData), + } + }, () => { + innerReject(new Error('Request should have failed')) + }) + + // Kill the socket immediately (before sending data) + req.on('socket', (socket) => { + setTimeout(() => { socket.destroy() }, 5) + }) + req.on('error', innerResolve) + req.write(postData) + req.end() + + return promise +} + +test('should handle a soket error', async (t) => { + t.plan(4) + const fastify = Fastify() + + function shouldNotHappen () { + t.assert.fail('This should not happen') + } + process.on('unhandledRejection', shouldNotHappen) + + t.after(() => { + fastify.close() + process.removeListener('unhandledRejection', shouldNotHappen) + }) + + fastify.addHook('onRequest', async (request, reply) => { + t.assert.ok('onRequest hook called') + }) + + fastify.addHook('onSend', async (request, reply, payload) => { + if (request.onSendCalled) { + t.assert.fail('onSend hook called more than once') + return + } + + t.assert.ok('onSend hook called') + request.onSendCalled = true + + // Introduce a delay + await new Promise(resolve => setTimeout(resolve, 5)) + return payload + }) + + // The handler must be async to trigger the error + fastify.put('/', async (request, reply) => { + t.assert.ok('PUT handler called') + return reply.send({ hello: 'world' }) + }) + + await fastify.listen({ port: 0 }) + + const err = await runBadClientCall({ + hostname: 'localhost', + port: fastify.server.address().port, + path: '/', + method: 'PUT', + }, { test: 'me' }) + t.assert.equal(err.code, 'ECONNRESET') +}) From 980c3a626505d6fbca01b7129c6a755ae1d5a0e2 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 25 Mar 2025 09:00:56 +0000 Subject: [PATCH 0960/1295] test(content-type-parser): proxy toad-cache (#6025) Signed-off-by: Frazer Smith --- test/internals/content-type-parser.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/internals/content-type-parser.test.js b/test/internals/content-type-parser.test.js index a6fc8a2cd3d..75bfd3f79c9 100644 --- a/test/internals/content-type-parser.test.js +++ b/test/internals/content-type-parser.test.js @@ -61,7 +61,7 @@ test('Should support Webpack and faux modules', t => { t.plan(2) const internals = proxyquire('../../lib/contentTypeParser', { - 'tiny-lru': { default: () => { } } + 'toad-cache': { default: () => { } } })[kTestInternals] const body = Buffer.from('你好 世界') From 1b6ddaaa888833f7192fe721e9ecc292e410d344 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 25 Mar 2025 21:21:36 +0100 Subject: [PATCH 0961/1295] Bumped v5.2.2 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 6894d36a85d..b6fb5c0bbf0 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.2.1' +const VERSION = '5.2.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index d19593eb8de..577de9fb5e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.2.1", + "version": "5.2.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 2c0be02b396bdd37bc34ee49356003be3c5a3f95 Mon Sep 17 00:00:00 2001 From: Hoang Date: Thu, 27 Mar 2025 23:30:08 +0700 Subject: [PATCH 0962/1295] fix: wrong reply return type (#6026) --- test/types/type-provider.test-d.ts | 40 ++++++++++++++++++++++++++++++ types/type-provider.d.ts | 15 ++++++++--- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index b885f478581..faa59b66a86 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -1009,6 +1009,46 @@ expectAssignable(server.withTypeProvider().get<{ Reply: } )) +// ------------------------------------------------------------------- +// RouteGeneric Reply Type Return (Different Status Codes) +// ------------------------------------------------------------------- + +expectAssignable(server.get<{ + Reply: { + 200: string | { msg: string } + 400: number + '5xx': { error: string } + } +}>( + '/', + async (_, res) => { + const option = 1 as 1 | 2 | 3 | 4 + switch (option) { + case 1: return 'hello' + case 2: return { msg: 'hello' } + case 3: return 400 + case 4: return { error: 'error' } + } + } +)) + +// ------------------------------------------------------------------- +// RouteGeneric Reply Type Return: Non Assignable (Different Status Codes) +// ------------------------------------------------------------------- + +expectError(server.get<{ + Reply: { + 200: string | { msg: string } + 400: number + '5xx': { error: string } + } +}>( + '/', + async (_, res) => { + return true + } +)) + // ------------------------------------------------------------------- // FastifyPlugin: Auxiliary // ------------------------------------------------------------------- diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index 1c789f51a73..b877d3ebfeb 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -1,6 +1,6 @@ import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' -import { RecordKeysToLowercase } from './utils' +import { HttpKeys, RecordKeysToLowercase } from './utils' // ----------------------------------------------------------------------------------------------- // TypeProvider @@ -80,6 +80,11 @@ export type ResolveFastifyReplyType = RouteGeneric extends { Reply: infer Return } + ? keyof Return extends HttpKeys ? Return[keyof Return] | Return : Return + : unknown + // The target reply return type. This type is inferenced on fastify 'routes' via generic argument assignment export type ResolveFastifyReplyReturnType< TypeProvider extends FastifyTypeProvider, @@ -89,8 +94,12 @@ export type ResolveFastifyReplyReturnType< TypeProvider, SchemaCompiler, RouteGeneric -> extends infer Return ? - (Return | void | Promise) +> extends infer ReplyType + ? RouteGeneric['Reply'] extends ReplyType + ? ResolveReplyReturnTypeFromRouteGeneric extends infer Return + ? Return | void | Promise + : unknown + : ReplyType | void | Promise // review: support both async and sync return types // (Promise | Return | Promise | void) : unknown From c2754bf78bd674ff0ba9206529926b8b3e02bae5 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Sat, 29 Mar 2025 08:19:19 +0100 Subject: [PATCH 0963/1295] feat: allow to access decorators (#5768) --- docs/Reference/Decorators.md | 199 ++++++++++++++++++++++++++++++++++ docs/Reference/Errors.md | 2 + fastify.js | 1 + lib/decorate.js | 21 +++- lib/errors.js | 4 + lib/reply.js | 19 +++- lib/request.js | 30 ++++- test/decorator.test.js | 178 +++++++++++++++++++++++++++++- test/internals/errors.test.js | 15 ++- test/types/instance.test-d.ts | 3 + test/types/reply.test-d.ts | 1 + test/types/request.test-d.ts | 4 + types/instance.d.ts | 2 + types/reply.d.ts | 1 + types/request.d.ts | 2 + 15 files changed, 470 insertions(+), 12 deletions(-) diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index f7b6a04a714..ee2e2f2b406 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -365,3 +365,202 @@ Will define the `foo` property on the Fastify instance: ```js console.log(fastify.foo) // 'a getter' ``` + +### `getDecorator` API + +Fastify's `getDecorator` API retrieves an existing decorator from the +Fastify instance, `Request`, or `Reply`. If the decorator is not defined, an +`FST_ERR_DEC_UNDECLARED` error is thrown. + +#### Use cases + +**Early Plugin Dependency Validation** + +`getDecorator` on Fastify instance verifies that required decorators are +available at registration time. + +For example: + +```js +fastify.register(async function (fastify) { + const usersRepository = fastify.getDecorator('usersRepository') + + fastify.get('/users', async function (request, reply) { + // We are sure `usersRepository` exists at runtime + return usersRepository.findAll() + }) +}) +``` + +**Handling Missing Decorators** + +Directly accessing a decorator may lead to unexpected behavior if it is not declared: + +```ts +const user = request.user; +if (user && user.isAdmin) { + // Execute admin tasks. +} +``` + +If `request.user` doesn't exist, then `user` will be set to `undefined`. +This makes it unclear whether the user is unauthenticated or the decorator is missing. + +Using `getDecorator` enforces runtime safety: + +```ts +// If the decorator is missing, an explicit `FST_ERR_DEC_UNDECLARED` +// error is thrown immediately. +const user = request.getDecorator('user'); +if (user && user.isAdmin) { + // Execute admin tasks. +} +``` + +**Alternative to Module Augmentation** + +Decorators are typically typed via module augmentation: + +```ts +declare module 'fastify' { + interface FastifyInstance { + usersRepository: IUsersRepository + } + interface FastifyRequest { + session: ISession + } + interface FastifyReply { + sendSuccess: SendSuccessFn + } +} +``` + +This approach modifies the Fastify instance globally, which may lead to +conflicts and inconsistent behavior in multi-server setups or with plugin +encapsulation. + +Using `getDecorator` allows to limit types scope: + +```ts +serverOne.register(async function (fastify) { + const usersRepository = fastify.getDecorator( + 'usersRepository' + ) + + fastify.decorateRequest('session', null) + fastify.addHook('onRequest', async (req, reply) => { + // Yes, the request object has a setDecorator method. + // More information will be provided soon. + req.setDecorator('session', { user: 'Jean' }) + }) + + fastify.get('/me', (request, reply) => { + const session = request.getDecorator('session') + reply.send(session) + }) +}) + +serverTwo.register(async function (fastify) { + const usersRepository = fastify.getDecorator( + 'usersRepository' + ) + + fastify.decorateReply('sendSuccess', function (data) { + return this.send({ success: true }) + }) + + fastify.get('/success', async (request, reply) => { + const sendSuccess = reply.getDecorator('sendSuccess') + await sendSuccess() + }) +}) +``` + +#### Bound functions inference + +To save time, it's common to infer function types instead of +writing them manually: + +```ts +function sendSuccess (this: FastifyReply) { + return this.send({ success: true }) +} + +export type SendSuccess = typeof sendSuccess +``` + +However, `getDecorator` returns functions with the `this` +context already **bound**, meaning the `this` parameter disappears +from the function signature. + +To correctly type it, you should use `OmitThisParameter` utility: + +```ts +function sendSuccess (this: FastifyReply) { + return this.send({ success: true }) +} + +type BoundSendSuccess = OmitThisParameter + +fastify.decorateReply('sendSuccess', sendSuccess) +fastify.get('/success', async (request, reply) => { + const sendSuccess = reply.getDecorator('sendSuccess') + await sendSuccess() +}) +``` + +### `Request.setDecorator` Method + +The `setDecorator` method provides a safe and convenient way to +update the value of a `Request` decorator. +If the decorator does not exist, a `FST_ERR_DEC_UNDECLARED` error +is thrown. + +#### Use Cases + +**Runtime Safety** + +A typical way to set a `Request` decorator looks like this: + +```ts +fastify.decorateRequest('user', '') +fastify.addHook('preHandler', async (req, reply) => { + req.user = 'Bob Dylan' +}) +``` + +However, there is no guarantee that the decorator actually exists +unless you manually check beforehand. +Additionally, typos are common, e.g. `account`, `acount`, or `accout`. + +By using `setDecorator`, you are always sure that the decorator exists: + +```ts +fastify.decorateRequest('user', '') +fastify.addHook('preHandler', async (req, reply) => { + // Throws FST_ERR_DEC_UNDECLARED if the decorator does not exist + req.setDecorator('user-with-typo', 'Bob Dylan') +}) +``` + +--- + +**Type Safety** + +If the `FastifyRequest` interface does not declare the decorator, you +would typically need to use type assertions: + +```ts +fastify.addHook('preHandler', async (req, reply) => { + (req as typeof req & { user: string }).user = 'Bob Dylan' +}) +``` + +The `setDecorator` method eliminates the need for explicit type +assertions while allowing type safety: + +```ts +fastify.addHook('preHandler', async (req, reply) => { + req.setDecorator('user', 'Bob Dylan') +}) +``` diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 038c3d83edf..af940449161 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -36,6 +36,7 @@ - [FST_ERR_DEC_MISSING_DEPENDENCY](#fst_err_dec_missing_dependency) - [FST_ERR_DEC_AFTER_START](#fst_err_dec_after_start) - [FST_ERR_DEC_REFERENCE_TYPE](#fst_err_dec_reference_type) + - [FST_ERR_DEC_UNDECLARED](#fst_err_dec_undeclared) - [FST_ERR_HOOK_INVALID_TYPE](#fst_err_hook_invalid_type) - [FST_ERR_HOOK_INVALID_HANDLER](#fst_err_hook_invalid_handler) - [FST_ERR_HOOK_INVALID_ASYNC_HANDLER](#fst_err_hook_invalid_async_handler) @@ -306,6 +307,7 @@ Below is a table with all the error codes used by Fastify. | FST_ERR_DEC_MISSING_DEPENDENCY | The decorator cannot be registered due to a missing dependency. | Register the missing dependency. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_DEC_AFTER_START | The decorator cannot be added after start. | Add the decorator before starting the server. | [#2128](https://github.com/fastify/fastify/pull/2128) | | FST_ERR_DEC_REFERENCE_TYPE | The decorator cannot be a reference type. | Define the decorator with a getter/setter interface or an empty decorator with a hook. | [#5462](https://github.com/fastify/fastify/pull/5462) | +| FST_ERR_DEC_UNDECLARED | An attempt was made to access a decorator that has not been declared. | Declare the decorator before using it. | [#](https://github.com/fastify/fastify/pull/) | FST_ERR_HOOK_INVALID_TYPE | The hook name must be a string. | Use a string for the hook name. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_HOOK_INVALID_HANDLER | The hook callback must be a function. | Use a function for the hook callback. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_HOOK_INVALID_ASYNC_HANDLER | Async function has too many arguments. Async hooks should not use the `done` argument. | Remove the `done` argument from the async hook. | [#4367](https://github.com/fastify/fastify/pull/4367) | diff --git a/fastify.js b/fastify.js index b6fb5c0bbf0..7c2070cd3cd 100644 --- a/fastify.js +++ b/fastify.js @@ -343,6 +343,7 @@ function fastify (options) { decorateRequest: decorator.decorateRequest, hasRequestDecorator: decorator.existRequest, hasReplyDecorator: decorator.existReply, + getDecorator: decorator.getInstanceDecorator, addHttpMethod, // fake http injection inject, diff --git a/lib/decorate.js b/lib/decorate.js index 70ac94febef..7494589d691 100644 --- a/lib/decorate.js +++ b/lib/decorate.js @@ -4,7 +4,7 @@ const { kReply, kRequest, kState, - kHasBeenDecorated + kHasBeenDecorated, } = require('./symbols.js') const { @@ -12,7 +12,8 @@ const { FST_ERR_DEC_MISSING_DEPENDENCY, FST_ERR_DEC_AFTER_START, FST_ERR_DEC_REFERENCE_TYPE, - FST_ERR_DEC_DEPENDENCY_INVALID_TYPE + FST_ERR_DEC_DEPENDENCY_INVALID_TYPE, + FST_ERR_DEC_UNDECLARED, } = require('./errors') function decorate (instance, name, fn, dependencies) { @@ -32,6 +33,18 @@ function decorate (instance, name, fn, dependencies) { } } +function getInstanceDecorator (name) { + if (!checkExistence(this, name)) { + throw new FST_ERR_DEC_UNDECLARED(name, 'instance') + } + + if (typeof this[name] === 'function') { + return this[name].bind(this) + } + + return this[name] +} + function decorateConstructor (konstructor, name, fn, dependencies) { const instance = konstructor.prototype if (Object.hasOwn(instance, name) || hasKey(konstructor, name)) { @@ -133,5 +146,7 @@ module.exports = { existReply: checkReplyExistence, dependencies: checkDependencies, decorateReply, - decorateRequest + decorateRequest, + getInstanceDecorator, + hasKey } diff --git a/lib/errors.js b/lib/errors.js index 06bb7c9741c..d9cede1d53f 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -149,6 +149,10 @@ const codes = { 'FST_ERR_DEC_REFERENCE_TYPE', "The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead." ), + FST_ERR_DEC_UNDECLARED: createError( + 'FST_ERR_DEC_UNDECLARED', + "No decorator '%s' has been declared on %s." + ), /** * hooks diff --git a/lib/reply.js b/lib/reply.js index 777972ab8e9..d4a925e402d 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -22,7 +22,7 @@ const { kReplyCacheSerializeFns, kSchemaController, kOptions, - kRouteContext + kRouteContext, } = require('./symbols.js') const { onSendHookRunner, @@ -52,8 +52,10 @@ const { FST_ERR_BAD_TRAILER_NAME, FST_ERR_BAD_TRAILER_VALUE, FST_ERR_MISSING_SERIALIZATION_FN, - FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN + FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN, + FST_ERR_DEC_UNDECLARED, } = require('./errors') +const decorators = require('./decorate') const toString = Object.prototype.toString @@ -475,6 +477,19 @@ Reply.prototype.then = function (fulfilled, rejected) { }) } +Reply.prototype.getDecorator = function (name) { + if (!decorators.hasKey(this, name) && !decorators.exist(this, name)) { + throw new FST_ERR_DEC_UNDECLARED(name, 'reply') + } + + const decorator = this[name] + if (typeof decorator === 'function') { + return decorator.bind(this) + } + + return decorator +} + function preSerializationHook (reply, payload) { if (reply[kRouteContext].preSerialization !== null) { preSerializationHookRunner( diff --git a/lib/request.js b/lib/request.js index d47c87cfa99..f68f97c78f6 100644 --- a/lib/request.js +++ b/lib/request.js @@ -11,9 +11,10 @@ const { kOptions, kRequestCacheValidateFns, kRouteContext, - kRequestOriginalUrl + kRequestOriginalUrl, } = require('./symbols') -const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors') +const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors') +const decorators = require('./decorate') const HTTP_PART_SYMBOL_MAP = { body: kSchemaBody, @@ -141,6 +142,12 @@ function buildRequestWithTrustProxy (R, trustProxy) { return _Request } +function assertsRequestDecoration (request, name) { + if (!decorators.hasKey(request, name) && !decorators.exist(request, name)) { + throw new FST_ERR_DEC_UNDECLARED(name, 'request') + } +} + Object.defineProperties(Request.prototype, { server: { get () { @@ -343,6 +350,25 @@ Object.defineProperties(Request.prototype, { return validate(input) } + }, + getDecorator: { + value: function (name) { + assertsRequestDecoration(this, name) + + const decorator = this[name] + if (typeof decorator === 'function') { + return decorator.bind(this) + } + + return decorator + } + }, + setDecorator: { + value: function (name, value) { + assertsRequestDecoration(this, name) + + this[name] = value + } } }) diff --git a/test/decorator.test.js b/test/decorator.test.js index 5e4006fba95..942a7e48097 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -925,9 +925,9 @@ test('Request/reply decorators should be able to access the server instance', as server.decorateRequest('assert', rootAssert) server.decorateReply('assert', rootAssert) - server.get('/root-assert', async (req, rep) => { + server.get('/root-assert', async (req, res) => { req.assert() - rep.assert() + res.assert() return 'done' }) @@ -936,9 +936,9 @@ test('Request/reply decorators should be able to access the server instance', as instance.decorateReply('assert', nestedAssert) instance.decorate('foo', 'bar') - instance.get('/nested-assert', async (req, rep) => { + instance.get('/nested-assert', async (req, res) => { req.assert() - rep.assert() + res.assert() return 'done' }) }) @@ -1260,3 +1260,173 @@ test('chain of decorators on Reply', async (t) => { t.equal(response.body, 'tata') } }) + +test('getDecorator should return the decorator', t => { + t.plan(12) + const fastify = Fastify() + + fastify.decorate('root', 'from_root') + fastify.decorateRequest('root', 'from_root_request') + fastify.decorateReply('root', 'from_root_reply') + + t.equal(fastify.getDecorator('root'), 'from_root') + fastify.get('/', async (req, res) => { + t.equal(req.getDecorator('root'), 'from_root_request') + t.equal(res.getDecorator('root'), 'from_root_reply') + + res.send() + }) + + fastify.register((child) => { + child.decorate('child', 'from_child') + + t.equal(child.getDecorator('child'), 'from_child') + t.equal(child.getDecorator('root'), 'from_root') + + child.get('/child', async (req, res) => { + t.equal(req.getDecorator('root'), 'from_root_request') + t.equal(res.getDecorator('root'), 'from_root_reply') + + res.send() + }) + }) + + fastify.ready((err) => { + t.error(err) + fastify.inject({ url: '/' }, (err, res) => { + t.error(err) + t.pass() + }) + + fastify.inject({ url: '/child' }, (err, res) => { + t.error(err) + t.pass() + }) + }) +}) + +test('getDecorator should return function decorators with expected binded context', t => { + t.plan(12) + const fastify = Fastify() + + fastify.decorate('a', function () { + return this + }) + fastify.decorateRequest('b', function () { + return this + }) + fastify.decorateReply('c', function () { + return this + }) + + fastify.register((child) => { + child.decorate('a', function () { + return this + }) + + t.same(child.getDecorator('a')(), child) + child.get('/child', async (req, res) => { + t.same(req.getDecorator('b')(), req) + t.same(res.getDecorator('c')(), res) + + res.send() + }) + }) + + t.same(fastify.getDecorator('a')(), fastify) + fastify.get('/', async (req, res) => { + t.same(req.getDecorator('b')(), req) + t.same(res.getDecorator('c')(), res) + + res.send() + }) + + fastify.ready((err) => { + t.error(err) + fastify.inject({ url: '/' }, (err, res) => { + t.error(err) + t.pass() + }) + + fastify.inject({ url: '/child' }, (err, res) => { + t.error(err) + t.pass() + }) + + t.pass() + }) +}) + +test('getDecorator should only return decorators existing in the scope', t => { + t.plan(9) + + function assertsThrowOnUndeclaredDecorator (notDecorated, instanceType) { + try { + notDecorated.getDecorator('foo') + t.fail() + } catch (e) { + t.same(e.code, 'FST_ERR_DEC_UNDECLARED') + t.same(e.message, `No decorator 'foo' has been declared on ${instanceType}.`) + } + } + + const fastify = Fastify() + fastify.register(child => { + child.decorate('foo', true) + child.decorateRequest('foo', true) + child.decorateReply('foo', true) + }) + + fastify.get('/', async (req, res) => { + assertsThrowOnUndeclaredDecorator(req, 'request') + assertsThrowOnUndeclaredDecorator(res, 'reply') + + return { hello: 'world' } + }) + + fastify.ready((err) => { + t.error(err) + + assertsThrowOnUndeclaredDecorator(fastify, 'instance') + fastify.inject({ url: '/' }, (err, res) => { + t.error(err) + t.pass() + }) + }) +}) + +test('Request.setDecorator should update an existing decorator', t => { + t.plan(7) + const fastify = Fastify() + + fastify.decorateRequest('session', null) + fastify.decorateRequest('utility', null) + fastify.addHook('onRequest', async (req, reply) => { + req.setDecorator('session', { user: 'Jean' }) + req.setDecorator('utility', function () { + return this + }) + try { + req.setDecorator('foo', { user: 'Jean' }) + t.fail() + } catch (e) { + t.same(e.code, 'FST_ERR_DEC_UNDECLARED') + t.same(e.message, "No decorator 'foo' has been declared on request.") + } + }) + + fastify.get('/', async (req, res) => { + t.strictSame(req.getDecorator('session'), { user: 'Jean' }) + t.same(req.getDecorator('utility')(), req) + + res.send() + }) + + fastify.ready((err) => { + t.error(err) + fastify.inject({ url: '/' }, (err, res) => { + t.error(err) + t.pass() + }) + }) +}) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 06f4041c1f7..a4f83c5dad2 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -const expectedErrors = 84 +const expectedErrors = 85 test(`should expose ${expectedErrors} errors`, t => { t.plan(1) @@ -21,6 +21,7 @@ test(`should expose ${expectedErrors} errors`, t => { test('ensure name and codes of Errors are identical', t => { t.plan(expectedErrors) + const exportedKeys = Object.keys(errors) for (const key of exportedKeys) { if (errors[key].name === 'FastifyError') { @@ -249,6 +250,16 @@ test('FST_ERR_DEC_REFERENCE_TYPE', t => { t.assert.ok(error instanceof Error) }) +test('FST_ERR_DEC_UNDECLARED', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_UNDECLARED('myDecorator', 'request') + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_UNDECLARED') + t.assert.strictEqual(error.message, "No decorator 'myDecorator' has been declared on request.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + test('FST_ERR_HOOK_INVALID_TYPE', t => { t.plan(5) const error = new errors.FST_ERR_HOOK_INVALID_TYPE() @@ -881,6 +892,7 @@ test('FST_ERR_ERROR_HANDLER_NOT_FN', t => { test('Ensure that all errors are in Errors.md TOC', t => { t.plan(expectedErrors) + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const exportedKeys = Object.keys(errors) @@ -918,6 +930,7 @@ test('Ensure that all errors are in Errors.md documented', t => { test('Ensure that non-existing errors are not in Errors.md documented', t => { t.plan(expectedErrors) + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') const matchRE = /([0-9a-zA-Z_]+)<\/a>/g diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 11058d8a457..fdd7369d118 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -522,6 +522,9 @@ expectError(server.decorateReply('typedTestReplyMethod', async function (x) { return 'foo' })) +const foo = server.getDecorator('foo') +expectType(foo) + const versionConstraintStrategy = { name: 'version', storage: () => ({ diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 3cc88ed1cbb..e58fa064a43 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -45,6 +45,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectAssignable<((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown)>(reply.serializeInput) expectAssignable<((input: { [key: string]: unknown }, httpStatus: string) => unknown)>(reply.serializeInput) expectType(reply.routeOptions.config) + expectType(reply.getDecorator('foo')) } interface ReplyPayload { diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 720ca60d021..2642b675ed7 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -92,6 +92,10 @@ const getHandler: RouteHandler = function (request, _reply) { expectAssignable<(schema: { [key: string]: unknown }) => ExpectedGetValidationFunction>(request.getValidationFunction) expectAssignable<(input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) expectAssignable<(input: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) + expectType(request.getDecorator('foo')) + expectType(request.setDecorator('foo', 'hello')) + expectType(request.setDecorator('foo', 'hello')) + expectError(request.setDecorator('foo', true)) } const getHandlerWithCustomLogger: RouteHandlerMethod = function (request, _reply) { diff --git a/types/instance.d.ts b/types/instance.d.ts index 3de0c76e019..bb04d51c096 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -152,6 +152,8 @@ export interface FastifyInstance< decorateRequest: DecorationMethod>; decorateReply: DecorationMethod>; + getDecorator(name: string | symbol): T; + hasDecorator(decorator: string | symbol): boolean; hasRequestDecorator(decorator: string | symbol): boolean; hasReplyDecorator(decorator: string | symbol): boolean; diff --git a/types/reply.d.ts b/types/reply.d.ts index 8c43cc74921..0491d8a02be 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -77,4 +77,5 @@ export interface FastifyReply< ) => FastifyReply; hasTrailer(key: string): boolean; removeTrailer(key: string): FastifyReply; + getDecorator(name: string | symbol): T; } diff --git a/types/request.d.ts b/types/request.d.ts index c63e386ab2c..0ea1adfb7c3 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -87,4 +87,6 @@ export interface FastifyRequest(name: string | symbol): T; + setDecorator(name: string | symbol, value: T): void; } From 7868e47194bcfb34cab2e51ea36bf10f764b538e Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 30 Mar 2025 10:38:01 +0200 Subject: [PATCH 0964/1295] ci: continue-on-error on alternative runtime (#6031) Signed-off-by: Manuel Spigolon --- .github/workflows/ci-alternative-runtime.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index 19691dac88f..f0f4c531489 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -48,6 +48,7 @@ jobs: npm install --ignore-scripts - name: Run tests + continue-on-error: true run: | npm run unit From 9dc6486b659420bb213fe93fde2432f74e1905ec Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Sun, 30 Mar 2025 19:19:39 +0900 Subject: [PATCH 0965/1295] fix: clear `[kState].readyPromise` for garbage collection (#6030) --- fastify.js | 2 +- test/fastify-instance.test.js | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 7c2070cd3cd..6715e8af69f 100644 --- a/fastify.js +++ b/fastify.js @@ -640,7 +640,7 @@ function fastify (options) { resolveReady(fastify) fastify[kState].booting = false fastify[kState].ready = true - fastify[kState].promise = null + fastify[kState].readyPromise = null } } diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index 079b3502818..5d4942f4c37 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -7,7 +7,8 @@ const os = require('node:os') const { kOptions, kErrorHandler, - kChildLoggerFactory + kChildLoggerFactory, + kState } = require('../lib/symbols') test('root fastify instance is an object', t => { @@ -288,3 +289,10 @@ test('fastify instance should contains listeningOrigin property (IPv6)', async t t.assert.deepStrictEqual(fastify.listeningOrigin, `http://[::1]:${port}`) await fastify.close() }) + +test('fastify instance should ensure ready promise cleanup on ready', async t => { + t.plan(1) + const fastify = Fastify() + await fastify.ready() + t.assert.strictEqual(fastify[kState].readyPromise, null) +}) From 22c716fad7cbb0125336420493527692d5e14206 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 1 Apr 2025 07:37:08 +0100 Subject: [PATCH 0966/1295] ci: set workflow permissions to read-only by default (#6035) --- .github/workflows/backport.yml | 6 ++++-- .github/workflows/benchmark-parser.yml | 3 +++ .github/workflows/benchmark.yml | 3 +++ .github/workflows/ci-alternative-runtime.yml | 3 +++ .github/workflows/ci.yml | 3 +++ .github/workflows/citgm-package.yml | 4 ++++ .github/workflows/citgm.yml | 3 +++ .github/workflows/coverage-nix.yml | 3 +++ .github/workflows/coverage-win.yml | 3 +++ .github/workflows/integration-alternative-runtimes.yml | 3 +++ .github/workflows/integration.yml | 3 +++ .github/workflows/labeler.yml | 3 +++ .github/workflows/links-check.yml | 5 +++++ .github/workflows/lint-ecosystem-order.yml | 4 +++- .github/workflows/lock-threads.yml | 6 ++++-- .github/workflows/md-lint.yml | 4 +++- .github/workflows/missing_types.yml | 6 ++++-- .github/workflows/package-manager-ci.yml | 4 ++++ .github/workflows/pull-request-title.yml | 5 +++++ .github/workflows/test-compare.yml | 3 +++ 20 files changed, 69 insertions(+), 8 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 4621cf7e510..675555fd3a2 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -6,12 +6,14 @@ on: - labeled permissions: - pull-requests: write - contents: write + contents: read jobs: backport: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write if: > github.event.pull_request.merged && ( diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 886c069c7c0..5cd7eafd465 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -4,6 +4,9 @@ on: pull_request_target: types: [labeled] +permissions: + contents: read + jobs: benchmark: if: ${{ github.event.label.name == 'benchmark' }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 108988f0504..7b7a8d35ffa 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -4,6 +4,9 @@ on: pull_request_target: types: [labeled] +permissions: + contents: read + jobs: benchmark: if: ${{ github.event.label.name == 'benchmark' }} diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index f0f4c531489..2160c85a73c 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -19,6 +19,9 @@ concurrency: group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" cancel-in-progress: true +permissions: + contents: read + jobs: test-unit: runs-on: ${{ matrix.os }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1685ddce538..080dd087fd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,9 @@ concurrency: group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" cancel-in-progress: true +permissions: + contents: read + jobs: dependency-review: name: Dependency Review diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index fe6dd6705c1..ee988866aa6 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -37,6 +37,10 @@ on: required: false type: string default: 'ubuntu-latest' + +permissions: + contents: read + jobs: core-plugins: name: CITGM diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml index c4f51a63634..05a63ad7612 100644 --- a/.github/workflows/citgm.yml +++ b/.github/workflows/citgm.yml @@ -4,6 +4,9 @@ on: pull_request: types: [labeled] +permissions: + contents: read + jobs: core-plugins: name: CITGM diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 000306ca515..fc1de26d458 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -3,6 +3,9 @@ name: Code Coverage (*nix) on: workflow_call: +permissions: + contents: read + jobs: check-coverage: runs-on: ubuntu-latest diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index df96691ea1d..869ed3ab53d 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -3,6 +3,9 @@ name: Code Coverage (win) on: workflow_call: +permissions: + contents: read + jobs: check-coverage: runs-on: windows-latest diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index da0df012956..732751ba75b 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -14,6 +14,9 @@ on: - 'docs/**' - '*.md' +permissions: + contents: read + jobs: install-production: runs-on: ${{ matrix.os }} diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ed4b10b1a29..1ededa5f65b 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -14,6 +14,9 @@ on: - 'docs/**' - '*.md' +permissions: + contents: read + jobs: install-production: runs-on: ${{ matrix.os }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index d310ecf26bc..9c82959696d 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,6 +1,9 @@ name: "Pull Request Labeler" on: pull_request_target +permissions: + contents: read + jobs: label: permissions: diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index aa9d04f9fef..f75a9d54a58 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -6,9 +6,14 @@ on: - 'docs/**' - '*.md' +permissions: + contents: read + jobs: linkChecker: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Check out repo uses: actions/checkout@v4 diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index 2f2c053bb6a..405fffd94f3 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -8,12 +8,14 @@ on: - "**/Ecosystem.md" permissions: - contents: read # to fetch code (actions/checkout) + contents: read jobs: build: name: Lint Ecosystem Order runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout Code diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml index 416efa98817..e7e8026fad4 100644 --- a/.github/workflows/lock-threads.yml +++ b/.github/workflows/lock-threads.yml @@ -6,8 +6,7 @@ on: workflow_dispatch: permissions: - issues: write - pull-requests: write + contents: read concurrency: group: lock @@ -15,6 +14,9 @@ concurrency: jobs: action: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write steps: - uses: jsumners/lock-threads@b27edac0ac998d42b2815e122b6c24b32b568321 with: diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index d1f60ce91c6..9ae9dfd8716 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -13,12 +13,14 @@ on: - "**/*.md" permissions: - contents: read # to fetch code (actions/checkout) + contents: read jobs: build: name: Lint Markdown runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout Code diff --git a/.github/workflows/missing_types.yml b/.github/workflows/missing_types.yml index b6d77acb2f5..a182f14bd45 100644 --- a/.github/workflows/missing_types.yml +++ b/.github/workflows/missing_types.yml @@ -5,12 +5,14 @@ on: types: [closed] permissions: - issues: write - pull-requests: read + contents: read jobs: create_issue: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: read if: | github.event.pull_request.merged && contains(github.event.pull_request.labels.*.name, 'missing-types') diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index ac7e14f2090..2d8223c5238 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -11,6 +11,8 @@ permissions: jobs: pnpm: runs-on: ${{ matrix.os }} + permissions: + contents: read strategy: matrix: @@ -45,6 +47,8 @@ jobs: yarn: runs-on: ${{ matrix.os }} + permissions: + contents: read strategy: matrix: diff --git a/.github/workflows/pull-request-title.yml b/.github/workflows/pull-request-title.yml index 62df7a1c07e..bfa000db945 100644 --- a/.github/workflows/pull-request-title.yml +++ b/.github/workflows/pull-request-title.yml @@ -3,9 +3,14 @@ on: pull_request: types: [opened, edited, synchronize, reopened] +permissions: + contents: read + jobs: pull-request-title-check: runs-on: ubuntu-latest + permissions: + pull-requests: read steps: - uses: fastify/action-pr-title@v0 with: diff --git a/.github/workflows/test-compare.yml b/.github/workflows/test-compare.yml index f3342246198..16b494d62a3 100644 --- a/.github/workflows/test-compare.yml +++ b/.github/workflows/test-compare.yml @@ -3,6 +3,9 @@ on: pull_request: types: [opened, reopened, synchronize, labeled] +permissions: + contents: read + jobs: run: if: contains(github.event.pull_request.labels.*.name, 'test-compare') From 2f40e19823c6bbbf26e024b1cf0073ddf87ddbe8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 14:13:21 +0000 Subject: [PATCH 0967/1295] chore: Bump the dependencies-major group with 2 updates (#6036) Bumps the dependencies-major group with 2 updates: [process-warning](https://github.com/fastify/process-warning) and [secure-json-parse](https://github.com/fastify/secure-json-parse). Updates `process-warning` from 4.0.1 to 5.0.0 - [Release notes](https://github.com/fastify/process-warning/releases) - [Commits](https://github.com/fastify/process-warning/compare/v4.0.1...v5.0.0) Updates `secure-json-parse` from 3.0.2 to 4.0.0 - [Release notes](https://github.com/fastify/secure-json-parse/releases) - [Commits](https://github.com/fastify/secure-json-parse/compare/v3.0.2...v4.0.0) --- updated-dependencies: - dependency-name: process-warning dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies-major - dependency-name: secure-json-parse dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 577de9fb5e8..2db654d2e3b 100644 --- a/package.json +++ b/package.json @@ -209,9 +209,9 @@ "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^9.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "rfdc": "^1.3.1", - "secure-json-parse": "^3.0.1", + "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" }, From c0292bac9b49a155f3c25d93f95b789ebb6d5e94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 14:46:32 +0000 Subject: [PATCH 0968/1295] chore: Bump lycheeverse/lychee-action from 2.3.0 to 2.4.0 (#6037) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/f613c4a64e50d792e0b31ec34bbcbba12263c6a6...1d97d84f0bc547f7b25f4c2170d87d810dc2fb2c) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index f75a9d54a58..62ee7d16ead 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -24,7 +24,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 + uses: lycheeverse/lychee-action@1d97d84f0bc547f7b25f4c2170d87d810dc2fb2c with: fail: true # As external links behavior is not predictable, we check only internal links From 2209b4cd4742725b4f4af28627113d70fbe90cae Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 3 Apr 2025 21:09:39 +0200 Subject: [PATCH 0969/1295] chore: remove sponsor (#6040) Signed-off-by: Manuel Spigolon --- SPONSORS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/SPONSORS.md b/SPONSORS.md index 8f641f9f1de..dad01462b5f 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -16,7 +16,6 @@ _Be the first!_ - [Mercedes-Benz Group](https://github.com/mercedes-benz) - [Val Town, Inc.](https://opencollective.com/valtown) - [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) -- [Jspreadsheet](https://jspreadsheet.com/) - [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship) ## Tier 2 From beaf4677d674e02dbd1aa20f68df202737249865 Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Mon, 7 Apr 2025 14:32:47 +0900 Subject: [PATCH 0970/1295] test: fix skip in upgrade test (#6044) --- test/upgrade.test.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/upgrade.test.js b/test/upgrade.test.js index 194294b5321..8f1bf8c290d 100644 --- a/test/upgrade.test.js +++ b/test/upgrade.test.js @@ -6,14 +6,11 @@ const { connect } = require('node:net') const { once } = require('node:events') const dns = require('node:dns').promises -describe('upgrade to both servers', async t => { +describe('upgrade to both servers', async () => { const localAddresses = await dns.lookup('localhost', { all: true }) - if (localAddresses.length === 1) { - t.skip('requires both IPv4 and IPv6') - return - } + const skip = localAddresses.length === 1 && 'requires both IPv4 and IPv6' - await test('upgrade IPv4 and IPv6', async t => { + await test('upgrade IPv4 and IPv6', { skip }, async t => { t.plan(2) const fastify = Fastify() From b9ef28ef99702afb7a76f29ab43b70463ddc277c Mon Sep 17 00:00:00 2001 From: Matthew Mallimo Date: Mon, 7 Apr 2025 05:19:58 -0400 Subject: [PATCH 0971/1295] chore: migrate custom-parser.4.test.js to node:test (#6042) --- test/custom-parser.4.test.js | 93 +++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/test/custom-parser.4.test.js b/test/custom-parser.4.test.js index 58e0a12a3ae..67100fd8fbd 100644 --- a/test/custom-parser.4.test.js +++ b/test/custom-parser.4.test.js @@ -1,19 +1,18 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../fastify') const jsonParser = require('fast-json-body') const { getServerUrl } = require('./helper') +const { waitForCb } = require('./toolkit') process.removeAllListeners('warning') -test('should prefer string content types over RegExp ones', t => { +test('should prefer string content types over RegExp ones', (t, testDone) => { t.plan(7) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) - + t.after(() => { fastify.close() }) fastify.post('/', (req, reply) => { reply.send(req.body) }) @@ -33,7 +32,8 @@ test('should prefer string content types over RegExp ones', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'POST', @@ -43,9 +43,10 @@ test('should prefer string content types over RegExp ones', t => { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ k1: 'myValue', k2: 'myValue' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.equal(body.toString(), JSON.stringify({ k1: 'myValue', k2: 'myValue' })) + completion.stepIn() }) sget({ @@ -56,18 +57,21 @@ test('should prefer string content types over RegExp ones', t => { 'Content-Type': 'application/javascript' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'javascript') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.equal(body.toString(), 'javascript') + completion.stepIn() }) + + completion.patience.then(testDone) }) }) -test('removeContentTypeParser should support arrays of content types to remove', t => { +test('removeContentTypeParser should support arrays of content types to remove', (t, testDone) => { t.plan(8) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.addContentTypeParser('application/xml', function (req, payload, done) { payload.on('data', () => {}) @@ -90,7 +94,8 @@ test('removeContentTypeParser should support arrays of content types to remove', }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 3 }) sget({ method: 'POST', @@ -100,9 +105,10 @@ test('removeContentTypeParser should support arrays of content types to remove', 'Content-Type': 'application/xml' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'xml') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.equal(body.toString(), 'xml') + completion.stepIn() }) sget({ @@ -113,8 +119,9 @@ test('removeContentTypeParser should support arrays of content types to remove', 'Content-Type': 'image/png' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + completion.stepIn() }) sget({ @@ -125,16 +132,19 @@ test('removeContentTypeParser should support arrays of content types to remove', 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('removeContentTypeParser should support encapsulation', t => { +test('removeContentTypeParser should support encapsulation', (t, testDone) => { t.plan(6) const fastify = Fastify() + t.after(() => fastify.close()) fastify.addContentTypeParser('application/xml', function (req, payload, done) { payload.on('data', () => {}) @@ -158,7 +168,8 @@ test('removeContentTypeParser should support encapsulation', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'POST', @@ -168,8 +179,9 @@ test('removeContentTypeParser should support encapsulation', t => { 'Content-Type': 'application/xml' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + completion.stepIn() }) sget({ @@ -180,18 +192,20 @@ test('removeContentTypeParser should support encapsulation', t => { 'Content-Type': 'application/xml' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'xml') - fastify.close() + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.equal(body.toString(), 'xml') + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('removeAllContentTypeParsers should support encapsulation', t => { +test('removeAllContentTypeParsers should support encapsulation', (t, testDone) => { t.plan(6) const fastify = Fastify() + t.after(() => fastify.close()) fastify.post('/', (req, reply) => { reply.send(req.body) @@ -208,7 +222,8 @@ test('removeAllContentTypeParsers should support encapsulation', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'POST', @@ -218,8 +233,9 @@ test('removeAllContentTypeParsers should support encapsulation', t => { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + completion.stepIn() }) sget({ @@ -230,10 +246,11 @@ test('removeAllContentTypeParsers should support encapsulation', t => { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body.toString()).test, 1) - fastify.close() + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.equal(JSON.parse(body.toString()).test, 1) + completion.stepIn() }) + completion.patience.then(testDone) }) }) From e73939b843307228b64131c9cefecf8cd5fd3c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Aliprandi?= Date: Thu, 10 Apr 2025 05:42:56 -0300 Subject: [PATCH 0972/1295] docs: add fastify-lm to Ecosystem.md (#6032) * Add fastify-lm to Ecosystem.md * Update fastify-lm plugin description to better reflect supported LM providers and fix to 80ch --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 003f1f5d7bb..76ba7d9ba6a 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -460,6 +460,8 @@ middlewares into Fastify plugins Lightweight cache plugin - [`fastify-list-routes`](https://github.com/chuongtrh/fastify-list-routes) A simple plugin for Fastify to list all available routes. +- [`fastify-lm`](https://github.com/galiprandi/fastify-lm#readme) + Use OpenAI, Claude, Google, Deepseek, and others LMs with one Fastify plugin. - [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from a directory and inject the Fastify instance in each file. - [`fastify-log-controller`](https://github.com/Eomm/fastify-log-controller/) From a155457fc7dc42c91ed60b21621e089359346ebb Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Fri, 11 Apr 2025 15:55:04 +0900 Subject: [PATCH 0973/1295] test: skip IPv6 tests if its support is not present (#6048) --- test/fastify-instance.test.js | 4 +++- test/hooks.on-listen.test.js | 31 +++++++++++++++++-------------- test/listen.2.test.js | 5 ++++- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index 5d4942f4c37..13992008379 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -11,6 +11,8 @@ const { kState } = require('../lib/symbols') +const isIPv6Missing = !Object.values(os.networkInterfaces()).flat().some(({ family }) => family === 'IPv6') + test('root fastify instance is an object', t => { t.plan(1) t.assert.strictEqual(typeof Fastify(), 'object') @@ -280,7 +282,7 @@ test('fastify instance should contains listeningOrigin property (unix socket)', await fastify.close() }) -test('fastify instance should contains listeningOrigin property (IPv6)', async t => { +test('fastify instance should contains listeningOrigin property (IPv6)', { skip: isIPv6Missing }, async t => { t.plan(1) const port = 3000 const host = '::1' diff --git a/test/hooks.on-listen.test.js b/test/hooks.on-listen.test.js index f13159fc123..1a09f306c2c 100644 --- a/test/hooks.on-listen.test.js +++ b/test/hooks.on-listen.test.js @@ -6,6 +6,9 @@ const fp = require('fastify-plugin') const split = require('split2') const helper = require('./helper') const { kState } = require('../lib/symbols') +const { networkInterfaces } = require('node:os') + +const isIPv6Missing = !Object.values(networkInterfaces()).flat().some(({ family }) => family === 'IPv6') let localhost before(async function () { @@ -405,7 +408,7 @@ test('localhost onListen encapsulation should be called in order and should log }) }) -test('non-localhost onListen should be called in order', t => { +test('non-localhost onListen should be called in order', { skip: isIPv6Missing }, t => { t.plan(2) const fastify = Fastify() @@ -428,7 +431,7 @@ test('non-localhost onListen should be called in order', t => { }) }) -test('non-localhost async onListen should be called in order', async t => { +test('non-localhost async onListen should be called in order', { skip: isIPv6Missing }, async t => { t.plan(2) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) @@ -448,7 +451,7 @@ test('non-localhost async onListen should be called in order', async t => { }) }) -test('non-localhost sync onListen should log errors as warnings and continue', t => { +test('non-localhost sync onListen should log errors as warnings and continue', { skip: isIPv6Missing }, t => { t.plan(4) const stream = split(JSON.parse) const fastify = Fastify({ @@ -488,7 +491,7 @@ test('non-localhost sync onListen should log errors as warnings and continue', t }) }) -test('non-localhost async onListen should log errors as warnings and continue', async t => { +test('non-localhost async onListen should log errors as warnings and continue', { skip: isIPv6Missing }, async t => { t.plan(6) const stream = split(JSON.parse) const fastify = Fastify({ @@ -529,7 +532,7 @@ test('non-localhost async onListen should log errors as warnings and continue', }) }) -test('non-localhost Register onListen hook after a plugin inside a plugin', t => { +test('non-localhost Register onListen hook after a plugin inside a plugin', { skip: isIPv6Missing }, t => { t.plan(3) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) @@ -562,7 +565,7 @@ test('non-localhost Register onListen hook after a plugin inside a plugin', t => }) }) -test('non-localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', t => { +test('non-localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', { skip: isIPv6Missing }, t => { t.plan(6) const stream = split(JSON.parse) const fastify = Fastify({ @@ -608,7 +611,7 @@ test('non-localhost Register onListen hook after a plugin inside a plugin should }) }) -test('non-localhost onListen encapsulation should be called in order', t => { +test('non-localhost onListen encapsulation should be called in order', { skip: isIPv6Missing }, t => { t.plan(6) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) @@ -640,7 +643,7 @@ test('non-localhost onListen encapsulation should be called in order', t => { }) }) -test('non-localhost onListen encapsulation should be called in order and should log errors as warnings and continue', t => { +test('non-localhost onListen encapsulation should be called in order and should log errors as warnings and continue', { skip: isIPv6Missing }, t => { t.plan(7) const stream = split(JSON.parse) const fastify = Fastify({ @@ -874,7 +877,7 @@ test('onListen localhost with callback encapsulation should be called in order', }) }) -test('onListen non-localhost should work in order with callback in sync', t => { +test('onListen non-localhost should work in order with callback in sync', { skip: isIPv6Missing }, t => { t.plan(4) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) @@ -896,7 +899,7 @@ test('onListen non-localhost should work in order with callback in sync', t => { }) }) -test('onListen non-localhost should work in order with callback in async', t => { +test('onListen non-localhost should work in order with callback in async', { skip: isIPv6Missing }, t => { t.plan(4) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) @@ -916,7 +919,7 @@ test('onListen non-localhost should work in order with callback in async', t => }) }) -test('onListen non-localhost sync with callback should log errors as warnings and continue', t => { +test('onListen non-localhost sync with callback should log errors as warnings and continue', { skip: isIPv6Missing }, t => { t.plan(8) const stream = split(JSON.parse) @@ -960,7 +963,7 @@ test('onListen non-localhost sync with callback should log errors as warnings an }) }) -test('onListen non-localhost async with callback should log errors as warnings and continue', t => { +test('onListen non-localhost async with callback should log errors as warnings and continue', { skip: isIPv6Missing }, t => { t.plan(8) const stream = split(JSON.parse) @@ -1002,7 +1005,7 @@ test('onListen non-localhost async with callback should log errors as warnings a }) }) -test('Register onListen hook non-localhost with callback after a plugin inside a plugin', t => { +test('Register onListen hook non-localhost with callback after a plugin inside a plugin', { skip: isIPv6Missing }, t => { t.plan(5) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) @@ -1035,7 +1038,7 @@ test('Register onListen hook non-localhost with callback after a plugin inside a }) }) -test('onListen non-localhost with callback encapsulation should be called in order', t => { +test('onListen non-localhost with callback encapsulation should be called in order', { skip: isIPv6Missing }, t => { t.plan(8) const fastify = Fastify() t.teardown(fastify.close.bind(fastify)) diff --git a/test/listen.2.test.js b/test/listen.2.test.js index fc9023c9dbe..3c27a5a73ef 100644 --- a/test/listen.2.test.js +++ b/test/listen.2.test.js @@ -3,6 +3,9 @@ const { test, before } = require('node:test') const Fastify = require('..') const helper = require('./helper') +const { networkInterfaces } = require('node:os') + +const isIPv6Missing = !Object.values(networkInterfaces()).flat().some(({ family }) => family === 'IPv6') let localhostForURL @@ -59,7 +62,7 @@ test('double listen errors callback with (err, address)', (t, done) => { }) }) -test('nonlocalhost double listen errors callback with (err, address)', (t, done) => { +test('nonlocalhost double listen errors callback with (err, address)', { skip: isIPv6Missing }, (t, done) => { t.plan(4) const fastify = Fastify() t.after(() => fastify.close()) From 1b56b8bf75f6ed5ba2ca28a631f86b26a063f26a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 12 Apr 2025 14:27:56 +0200 Subject: [PATCH 0974/1295] Bumped v5.3.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- lib/error-serializer.js | 26 +++++++++++++------------- package.json | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/fastify.js b/fastify.js index 6715e8af69f..71d3bb1b8dc 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.2.2' +const VERSION = '5.3.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index 5fb7b8146e9..71fb87b9ce1 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -24,15 +24,15 @@ const JSON_STR_EMPTY_ARRAY = JSON_STR_BEGIN_ARRAY + JSON_STR_END_ARRAY const JSON_STR_EMPTY_STRING = JSON_STR_QUOTE + JSON_STR_QUOTE const JSON_STR_NULL = 'null' - - - + + + // # function anonymous0 (input) { const obj = (input && typeof input.toJSON === 'function') ? input.toJSON() : input - + if (obj === null) return JSON_STR_EMPTY_OBJECT let value @@ -50,7 +50,7 @@ let addComma = false if (value !== undefined) { !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"code\":" - + if (typeof value !== 'string') { if (value === null) { json += JSON_STR_EMPTY_STRING @@ -64,14 +64,14 @@ let addComma = false } else { json += serializer.asString(value) } - + } value = obj["error"] if (value !== undefined) { !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"error\":" - + if (typeof value !== 'string') { if (value === null) { json += JSON_STR_EMPTY_STRING @@ -85,14 +85,14 @@ let addComma = false } else { json += serializer.asString(value) } - + } value = obj["message"] if (value !== undefined) { !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"message\":" - + if (typeof value !== 'string') { if (value === null) { json += JSON_STR_EMPTY_STRING @@ -106,15 +106,15 @@ let addComma = false } else { json += serializer.asString(value) } - + } return json + JSON_STR_END_OBJECT - + } - + const main = anonymous0 return main - + }(validator, serializer) /* c8 ignore stop */ diff --git a/package.json b/package.json index 2db654d2e3b..816943ef89d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.2.2", + "version": "5.3.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 9c7e75e002ea727fa3c9f64671ec24d529e67174 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 17 Apr 2025 09:17:45 +0200 Subject: [PATCH 0975/1295] test: migrate logger options to node test runner (#6059) --- test/logger/options.test.js | 196 ++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 99 deletions(-) diff --git a/test/logger/options.test.js b/test/logger/options.test.js index 07b6eeff724..46b1338ee52 100644 --- a/test/logger/options.test.js +++ b/test/logger/options.test.js @@ -2,47 +2,45 @@ const stream = require('node:stream') -const t = require('tap') +const t = require('node:test') const split = require('split2') const pino = require('pino') const Fastify = require('../../fastify') const { on } = stream -t.test('logger options', (t) => { - t.setTimeout(60000) - +t.test('logger options', { timeout: 60000 }, async (t) => { t.plan(16) - t.test('logger can be silenced', (t) => { + await t.test('logger can be silenced', (t) => { t.plan(17) const fastify = Fastify({ logger: false }) - t.teardown(fastify.close.bind(fastify)) - t.ok(fastify.log) - t.equal(typeof fastify.log, 'object') - t.equal(typeof fastify.log.fatal, 'function') - t.equal(typeof fastify.log.error, 'function') - t.equal(typeof fastify.log.warn, 'function') - t.equal(typeof fastify.log.info, 'function') - t.equal(typeof fastify.log.debug, 'function') - t.equal(typeof fastify.log.trace, 'function') - t.equal(typeof fastify.log.child, 'function') + t.after(() => fastify.close()) + t.assert.ok(fastify.log) + t.assert.deepEqual(typeof fastify.log, 'object') + t.assert.deepEqual(typeof fastify.log.fatal, 'function') + t.assert.deepEqual(typeof fastify.log.error, 'function') + t.assert.deepEqual(typeof fastify.log.warn, 'function') + t.assert.deepEqual(typeof fastify.log.info, 'function') + t.assert.deepEqual(typeof fastify.log.debug, 'function') + t.assert.deepEqual(typeof fastify.log.trace, 'function') + t.assert.deepEqual(typeof fastify.log.child, 'function') const childLog = fastify.log.child() - t.equal(typeof childLog, 'object') - t.equal(typeof childLog.fatal, 'function') - t.equal(typeof childLog.error, 'function') - t.equal(typeof childLog.warn, 'function') - t.equal(typeof childLog.info, 'function') - t.equal(typeof childLog.debug, 'function') - t.equal(typeof childLog.trace, 'function') - t.equal(typeof childLog.child, 'function') + t.assert.deepEqual(typeof childLog, 'object') + t.assert.deepEqual(typeof childLog.fatal, 'function') + t.assert.deepEqual(typeof childLog.error, 'function') + t.assert.deepEqual(typeof childLog.warn, 'function') + t.assert.deepEqual(typeof childLog.info, 'function') + t.assert.deepEqual(typeof childLog.debug, 'function') + t.assert.deepEqual(typeof childLog.trace, 'function') + t.assert.deepEqual(typeof childLog.child, 'function') }) - t.test('Should set a custom logLevel for a plugin', async (t) => { + await t.test('Should set a custom logLevel for a plugin', async (t) => { const lines = ['incoming request', 'Hello', 'request completed'] t.plan(lines.length + 2) @@ -53,7 +51,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { req.log.info('Not Exist') // we should not see this log @@ -73,22 +71,22 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body.hello, 'world') } { const response = await fastify.inject({ method: 'GET', url: '/plugin' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body.hello, 'world') } for await (const [line] of on(stream, 'data')) { - t.same(line.msg, lines.shift()) + t.assert.deepEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should set a custom logSerializers for a plugin', async (t) => { + await t.test('Should set a custom logSerializers for a plugin', async (t) => { const lines = ['incoming request', 'XHello', 'request completed'] t.plan(lines.length + 1) @@ -99,7 +97,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(function (instance, opts, done) { instance.get('/plugin', (req, reply) => { @@ -114,17 +112,17 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/plugin' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body.hello, 'world') } for await (const [line] of on(stream, 'data')) { // either test or msg - t.equal(line.test || line.msg, lines.shift()) + t.assert.deepEqual(line.test || line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should set a custom logLevel for every plugin', async (t) => { + await t.test('Should set a custom logLevel for every plugin', async (t) => { const lines = ['incoming request', 'info', 'request completed', 'incoming request', 'debug', 'request completed'] t.plan(lines.length * 2 + 3) @@ -135,7 +133,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { req.log.warn('Hello') // we should not see this log @@ -165,29 +163,29 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } { const response = await fastify.inject({ method: 'GET', url: '/info' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } { const response = await fastify.inject({ method: 'GET', url: '/debug' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.ok(line.level === 30 || line.level === 20) - t.equal(line.msg, lines.shift()) + t.assert.ok(line.level === 30 || line.level === 20) + t.assert.deepEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should set a custom logSerializers for every plugin', async (t) => { + await t.test('Should set a custom logSerializers for every plugin', async (t) => { const lines = ['incoming request', 'Hello', 'request completed', 'incoming request', 'XHello', 'request completed', 'incoming request', 'ZHello', 'request completed'] t.plan(lines.length + 3) @@ -197,7 +195,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { req.log.warn({ test: 'Hello' }) @@ -225,28 +223,28 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } { const response = await fastify.inject({ method: 'GET', url: '/test1' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } { const response = await fastify.inject({ method: 'GET', url: '/test2' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.equal(line.test || line.msg, lines.shift()) + t.assert.deepEqual(line.test || line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should override serializers from route', async (t) => { + await t.test('Should override serializers from route', async (t) => { const lines = ['incoming request', 'ZHello', 'request completed'] t.plan(lines.length + 1) @@ -256,7 +254,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(function (instance, opts, done) { instance.get('/', { @@ -275,16 +273,16 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.equal(line.test || line.msg, lines.shift()) + t.assert.deepEqual(line.test || line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should override serializers from plugin', async (t) => { + await t.test('Should override serializers from plugin', async (t) => { const lines = ['incoming request', 'ZHello', 'request completed'] t.plan(lines.length + 1) @@ -294,7 +292,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(function (instance, opts, done) { instance.register(context1, { @@ -318,16 +316,16 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.equal(line.test || line.msg, lines.shift()) + t.assert.deepEqual(line.test || line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should increase the log level for a specific plugin', async (t) => { + await t.test('Should increase the log level for a specific plugin', async (t) => { const lines = ['Hello'] t.plan(lines.length * 2 + 1) @@ -338,7 +336,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(function (instance, opts, done) { instance.get('/', (req, reply) => { @@ -353,17 +351,17 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.equal(line.level, 50) - t.equal(line.msg, lines.shift()) + t.assert.deepEqual(line.level, 50) + t.assert.deepEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should set the log level for the customized 404 handler', async (t) => { + await t.test('Should set the log level for the customized 404 handler', async (t) => { const lines = ['Hello'] t.plan(lines.length * 2 + 1) @@ -374,7 +372,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(function (instance, opts, done) { instance.setNotFoundHandler(function (req, reply) { @@ -388,17 +386,17 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) - t.equal(response.statusCode, 404) + t.assert.deepEqual(response.statusCode, 404) } for await (const [line] of on(stream, 'data')) { - t.equal(line.level, 50) - t.equal(line.msg, lines.shift()) + t.assert.deepEqual(line.level, 50) + t.assert.deepEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should set the log level for the customized 500 handler', async (t) => { + await t.test('Should set the log level for the customized 500 handler', async (t) => { const lines = ['Hello'] t.plan(lines.length * 2 + 1) @@ -409,7 +407,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(function (instance, opts, done) { instance.get('/', (req, reply) => { @@ -428,17 +426,17 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) - t.equal(response.statusCode, 500) + t.assert.deepEqual(response.statusCode, 500) } for await (const [line] of on(stream, 'data')) { - t.equal(line.level, 60) - t.equal(line.msg, lines.shift()) + t.assert.deepEqual(line.level, 60) + t.assert.deepEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('Should set a custom log level for a specific route', async (t) => { + await t.test('Should set a custom log level for a specific route', async (t) => { const lines = ['incoming request', 'Hello', 'request completed'] t.plan(lines.length + 2) @@ -449,7 +447,7 @@ t.test('logger options', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/log', { logLevel: 'info' }, (req, reply) => { req.log.info('Hello') @@ -466,39 +464,39 @@ t.test('logger options', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/log' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } { const response = await fastify.inject({ method: 'GET', url: '/no-log' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.equal(line.msg, lines.shift()) + t.assert.deepEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('should pass when using unWritable props in the logger option', (t) => { + await t.test('should pass when using unWritable props in the logger option', (t) => { t.plan(8) const fastify = Fastify({ logger: Object.defineProperty({}, 'level', { value: 'info' }) }) - t.teardown(fastify.close.bind(fastify)) - - t.equal(typeof fastify.log, 'object') - t.equal(typeof fastify.log.fatal, 'function') - t.equal(typeof fastify.log.error, 'function') - t.equal(typeof fastify.log.warn, 'function') - t.equal(typeof fastify.log.info, 'function') - t.equal(typeof fastify.log.debug, 'function') - t.equal(typeof fastify.log.trace, 'function') - t.equal(typeof fastify.log.child, 'function') + t.after(() => fastify.close()) + + t.assert.deepEqual(typeof fastify.log, 'object') + t.assert.deepEqual(typeof fastify.log.fatal, 'function') + t.assert.deepEqual(typeof fastify.log.error, 'function') + t.assert.deepEqual(typeof fastify.log.warn, 'function') + t.assert.deepEqual(typeof fastify.log.info, 'function') + t.assert.deepEqual(typeof fastify.log.debug, 'function') + t.assert.deepEqual(typeof fastify.log.trace, 'function') + t.assert.deepEqual(typeof fastify.log.child, 'function') }) - t.test('Should throw an error if logger instance is passed to `logger`', async (t) => { + await t.test('Should throw an error if logger instance is passed to `logger`', async (t) => { t.plan(2) const stream = split(JSON.parse) @@ -507,22 +505,22 @@ t.test('logger options', (t) => { try { Fastify({ logger }) } catch (err) { - t.ok(err) - t.equal(err.code, 'FST_ERR_LOG_INVALID_LOGGER_CONFIG') + t.assert.ok(err) + t.assert.deepEqual(err.code, 'FST_ERR_LOG_INVALID_LOGGER_CONFIG') } }) - t.test('Should throw an error if options are passed to `loggerInstance`', async (t) => { + await t.test('Should throw an error if options are passed to `loggerInstance`', async (t) => { t.plan(2) try { Fastify({ loggerInstance: { level: 'log' } }) } catch (err) { - t.ok(err) - t.equal(err.code, 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE') + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE') } }) - t.test('If both `loggerInstance` and `logger` are provided, an error should be thrown', async (t) => { + await t.test('If both `loggerInstance` and `logger` are provided, an error should be thrown', async (t) => { t.plan(2) const loggerInstanceStream = split(JSON.parse) const loggerInstance = pino({ level: 'error' }, loggerInstanceStream) @@ -536,12 +534,12 @@ t.test('logger options', (t) => { loggerInstance }) } catch (err) { - t.ok(err) - t.equal(err.code, 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED') + t.assert.ok(err) + t.assert.deepEqual(err.code, 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED') } }) - t.test('`logger` should take pino configuration and create a pino logger', async (t) => { + await t.test('`logger` should take pino configuration and create a pino logger', async (t) => { const lines = ['hello', 'world'] t.plan(2 * lines.length + 2) const loggerStream = split(JSON.parse) @@ -551,7 +549,7 @@ t.test('logger options', (t) => { level: 'error' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/hello', (req, reply) => { req.log.error('hello') reply.code(404).send() @@ -565,16 +563,16 @@ t.test('logger options', (t) => { await fastify.ready() { const response = await fastify.inject({ method: 'GET', url: '/hello' }) - t.equal(response.statusCode, 404) + t.assert.deepEqual(response.statusCode, 404) } { const response = await fastify.inject({ method: 'GET', url: '/world' }) - t.equal(response.statusCode, 201) + t.assert.deepEqual(response.statusCode, 201) } for await (const [line] of on(loggerStream, 'data')) { - t.equal(line.level, 50) - t.equal(line.msg, lines.shift()) + t.assert.deepEqual(line.level, 50) + t.assert.deepEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) From 57f894d01ae787fbb549d09684cb34888529743c Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 17 Apr 2025 09:36:24 +0200 Subject: [PATCH 0976/1295] test: migrate logger logging to node test runner (#6060) --- test/logger/logging.test.js | 236 ++++++++++++++++++------------------ test/toolkit.js | 31 +++++ 2 files changed, 147 insertions(+), 120 deletions(-) diff --git a/test/logger/logging.test.js b/test/logger/logging.test.js index 36007d9af1e..5d9c5300f40 100644 --- a/test/logger/logging.test.js +++ b/test/logger/logging.test.js @@ -2,7 +2,7 @@ const stream = require('node:stream') -const t = require('tap') +const t = require('node:test') const split = require('split2') const pino = require('pino') @@ -10,10 +10,9 @@ const Fastify = require('../../fastify') const helper = require('../helper') const { once, on } = stream const { request } = require('./logger-test-utils') +const { partialDeepStrictEqual } = require('../toolkit') -t.test('logging', (t) => { - t.setTimeout(60000) - +t.test('logging', { timeout: 60000 }, async (t) => { let localhost let localhostForURL @@ -23,7 +22,7 @@ t.test('logging', (t) => { [localhost, localhostForURL] = await helper.getLoopbackHost() }) - t.test('The default 404 handler logs the incoming request', async (t) => { + await t.test('The default 404 handler logs the incoming request', async (t) => { const lines = ['incoming request', 'Route GET:/not-found not found', 'request completed'] t.plan(lines.length + 1) @@ -34,29 +33,22 @@ t.test('logging', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) await fastify.ready() { const response = await fastify.inject({ method: 'GET', url: '/not-found' }) - t.equal(response.statusCode, 404) + t.assert.strictEqual(response.statusCode, 404) } for await (const [line] of on(stream, 'data')) { - t.equal(line.msg, lines.shift()) + t.assert.strictEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('should not rely on raw request to log errors', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { level: 30, msg: 'incoming request' }, - { res: { statusCode: 415 }, msg: 'something happened' }, - { res: { statusCode: 415 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) + await t.test('should not rely on raw request to log errors', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -64,32 +56,31 @@ t.test('logging', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/error', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.status(415).send(new Error('something happened')) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { level: 30, msg: 'incoming request' }, + { res: { statusCode: 415 }, msg: 'something happened' }, + { res: { statusCode: 415 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('should log the error if no error handler is defined', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { msg: 'incoming request' }, - { level: 50, msg: 'a generic error' }, - { res: { statusCode: 500 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) - + await t.test('should log the error if no error handler is defined', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -97,32 +88,32 @@ t.test('logging', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/error', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send(new Error('a generic error')) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { msg: 'incoming request' }, + { level: 50, msg: 'a generic error' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('should log as info if error status code >= 400 and < 500 if no error handler is defined', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { msg: 'incoming request' }, - { level: 30, msg: 'a 400 error' }, - { res: { statusCode: 400 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) + await t.test('should log as info if error status code >= 400 and < 500 if no error handler is defined', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -130,36 +121,36 @@ t.test('logging', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/400', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send(Object.assign(new Error('a 400 error'), { statusCode: 400 })) }) fastify.get('/503', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { msg: 'incoming request' }, + { level: 30, msg: 'a 400 error' }, + { res: { statusCode: 400 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) await request(`http://${localhostForURL}:` + fastify.server.address().port + '/400') for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('should log as error if error status code >= 500 if no error handler is defined', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { msg: 'incoming request' }, - { level: 50, msg: 'a 503 error' }, - { res: { statusCode: 503 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) + await t.test('should log as error if error status code >= 500 if no error handler is defined', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -167,30 +158,31 @@ t.test('logging', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/503', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { msg: 'incoming request' }, + { level: 50, msg: 'a 503 error' }, + { res: { statusCode: 503 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) await request(`http://${localhostForURL}:` + fastify.server.address().port + '/503') for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('should not log the error if error handler is defined and it does not error', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { level: 30, msg: 'incoming request' }, - { res: { statusCode: 200 }, msg: 'request completed' } - ] - t.plan(lines.length + 2) + await t.test('should not log the error if error handler is defined and it does not error', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -198,28 +190,34 @@ t.test('logging', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/error', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send(new Error('something happened')) }) fastify.setErrorHandler((err, req, reply) => { - t.ok(err) + t.assert.ok(err) reply.send('something bad happened') }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { level: 30, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 2) await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('reply.send logs an error if called twice in a row', async (t) => { + await t.test('reply.send logs an error if called twice in a row', async (t) => { const lines = [ 'incoming request', 'request completed', @@ -234,7 +232,7 @@ t.test('logging', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { reply.send({ hello: 'world' }) @@ -244,19 +242,19 @@ t.test('logging', (t) => { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.ok(partialDeepStrictEqual(body, { hello: 'world' })) for await (const [line] of on(stream, 'data')) { - t.same(line.msg, lines.shift()) + t.assert.strictEqual(line.msg, lines.shift()) if (lines.length === 0) break } }) - t.test('should not log incoming request and outgoing response when disabled', async (t) => { + await t.test('should not log incoming request and outgoing response when disabled', async (t) => { t.plan(1) const stream = split(JSON.parse) const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/500', (req, reply) => { reply.code(500).send(Error('500 error')) @@ -267,27 +265,27 @@ t.test('logging', (t) => { await fastify.inject({ method: 'GET', url: '/500' }) // no more readable data - t.equal(stream.readableLength, 0) + t.assert.strictEqual(stream.readableLength, 0) }) - t.test('should not log incoming request, outgoing response and route not found for 404 onBadUrl when disabled', async (t) => { + await t.test('should not log incoming request, outgoing response and route not found for 404 onBadUrl when disabled', async (t) => { t.plan(1) const stream = split(JSON.parse) const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) await fastify.ready() await fastify.inject({ method: 'GET', url: '/%c0' }) // no more readable data - t.equal(stream.readableLength, 0) + t.assert.strictEqual(stream.readableLength, 0) }) - t.test('defaults to info level', async (t) => { + await t.test('defaults to info level', async (t) => { const lines = [ - { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, - { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } + { req: { method: 'GET' }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } ] t.plan(lines.length * 2 + 1) const stream = split(JSON.parse) @@ -296,10 +294,10 @@ t.test('logging', (t) => { stream } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send({ hello: 'world' }) }) @@ -313,20 +311,13 @@ t.test('logging', (t) => { // we skip the non-request log if (typeof line.reqId !== 'string') continue if (id === undefined && line.reqId) id = line.reqId - if (id !== undefined && line.reqId) t.equal(line.reqId, id) - t.match(line, lines.shift()) + if (id !== undefined && line.reqId) t.assert.strictEqual(line.reqId, id) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('test log stream', async (t) => { - const lines = [ - { msg: /^Server listening at / }, - { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, - { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } - ] - t.plan(lines.length + 3) - + await t.test('test log stream', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -334,36 +325,34 @@ t.test('logging', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send({ hello: 'world' }) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { method: 'GET' }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) await request(`http://${localhostForURL}:` + fastify.server.address().port) let id for await (const [line] of on(stream, 'data')) { if (id === undefined && line.reqId) id = line.reqId - if (id !== undefined && line.reqId) t.equal(line.reqId, id) - t.match(line, lines.shift()) + if (id !== undefined && line.reqId) t.assert.strictEqual(line.reqId, id) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('test error log stream', async (t) => { - const lines = [ - { msg: /^Server listening at / }, - { reqId: /req-/, req: { method: 'GET' }, msg: 'incoming request' }, - { reqId: /req-/, res: { statusCode: 500 }, msg: 'kaboom' }, - { reqId: /req-/, res: { statusCode: 500 }, msg: 'request completed' } - ] - t.plan(lines.length + 4) - + await t.test('test error log stream', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -371,28 +360,35 @@ t.test('logging', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/error', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send(new Error('kaboom')) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { method: 'GET' }, msg: 'incoming request' }, + { res: { statusCode: 500 }, msg: 'kaboom' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 4) await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') let id for await (const [line] of on(stream, 'data')) { if (id === undefined && line.reqId) id = line.reqId - if (id !== undefined && line.reqId) t.equal(line.reqId, id) - t.match(line, lines.shift()) + if (id !== undefined && line.reqId) t.assert.strictEqual(line.reqId, id) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('should not log the error if request logging is disabled', async (t) => { + await t.test('should not log the error if request logging is disabled', async (t) => { t.plan(4) const stream = split(JSON.parse) @@ -403,10 +399,10 @@ t.test('logging', (t) => { }, disableRequestLogging: true }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/error', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send(new Error('a generic error')) }) @@ -417,11 +413,11 @@ t.test('logging', (t) => { { const [line] = await once(stream, 'data') - t.type(line.msg, 'string') - t.ok(line.msg.startsWith('Server listening at'), 'message is set') + t.assert.ok(typeof line.msg === 'string') + t.assert.ok(line.msg.startsWith('Server listening at'), 'message is set') } // no more readable data - t.equal(stream.readableLength, 0) + t.assert.strictEqual(stream.readableLength, 0) }) }) diff --git a/test/toolkit.js b/test/toolkit.js index a3c5fec7f15..96ff21e0b0d 100644 --- a/test/toolkit.js +++ b/test/toolkit.js @@ -30,3 +30,34 @@ exports.waitForCb = function (options) { return { stepIn, patience } } + +exports.partialDeepStrictEqual = function partialDeepStrictEqual (actual, expected) { + if (typeof expected !== 'object' || expected === null) { + return actual === expected + } + + if (typeof actual !== 'object' || actual === null) { + return false + } + + if (Array.isArray(expected)) { + if (!Array.isArray(actual)) return false + if (expected.length > actual.length) return false + + for (let i = 0; i < expected.length; i++) { + if (!partialDeepStrictEqual(actual[i], expected[i])) { + return false + } + } + return true + } + + for (const key of Object.keys(expected)) { + if (!(key in actual)) return false + if (!partialDeepStrictEqual(actual[key], expected[key])) { + return false + } + } + + return true +} From 3f3ba85240a716c850d89bf344cb84a8411153c2 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 17 Apr 2025 09:51:11 +0200 Subject: [PATCH 0977/1295] test: convert custom parser 1 to node test runner (#6053) --- test/custom-parser.1.test.js | 140 +++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 63 deletions(-) diff --git a/test/custom-parser.1.test.js b/test/custom-parser.1.test.js index 29ffe4d9cb2..71ef9fdaaca 100644 --- a/test/custom-parser.1.test.js +++ b/test/custom-parser.1.test.js @@ -1,15 +1,15 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../fastify') const jsonParser = require('fast-json-body') const { getServerUrl } = require('./helper') +const { waitForCb } = require('./toolkit') process.removeAllListeners('warning') -test('Should have typeof body object with no custom parser defined, null body and content type = \'text/plain\'', t => { +test('Should have typeof body object with no custom parser defined, null body and content type = \'text/plain\'', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -18,7 +18,7 @@ test('Should have typeof body object with no custom parser defined, null body an }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -28,15 +28,16 @@ test('Should have typeof body object with no custom parser defined, null body an 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(typeof body, 'object') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(typeof body, 'object') fastify.close() + testDone() }) }) }) -test('Should have typeof body object with no custom parser defined, undefined body and content type = \'text/plain\'', t => { +test('Should have typeof body object with no custom parser defined, undefined body and content type = \'text/plain\'', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -45,7 +46,7 @@ test('Should have typeof body object with no custom parser defined, undefined bo }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -55,15 +56,16 @@ test('Should have typeof body object with no custom parser defined, undefined bo 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(typeof body, 'object') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(typeof body, 'object') fastify.close() + testDone() }) }) }) -test('Should get the body as string /1', t => { +test('Should get the body as string /1', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -72,8 +74,8 @@ test('Should get the body as string /1', t => { }) fastify.addContentTypeParser('text/plain', { parseAs: 'string' }, function (req, body, done) { - t.ok('called') - t.ok(typeof body === 'string') + t.assert.ok('called') + t.assert.ok(typeof body === 'string') try { const plainText = body done(null, plainText) @@ -84,7 +86,7 @@ test('Should get the body as string /1', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -94,15 +96,16 @@ test('Should get the body as string /1', t => { 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello world') fastify.close() + testDone() }) }) }) -test('Should get the body as string /2', t => { +test('Should get the body as string /2', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -111,8 +114,8 @@ test('Should get the body as string /2', t => { }) fastify.addContentTypeParser('text/plain/test', { parseAs: 'string' }, function (req, body, done) { - t.ok('called') - t.ok(typeof body === 'string') + t.assert.ok('called') + t.assert.ok(typeof body === 'string') try { const plainText = body done(null, plainText) @@ -123,7 +126,7 @@ test('Should get the body as string /2', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -133,15 +136,16 @@ test('Should get the body as string /2', t => { 'Content-Type': ' text/plain/test ' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello world') fastify.close() + testDone() }) }) }) -test('Should get the body as buffer', t => { +test('Should get the body as buffer', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -150,8 +154,8 @@ test('Should get the body as buffer', t => { }) fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, function (req, body, done) { - t.ok('called') - t.ok(body instanceof Buffer) + t.assert.ok('called') + t.assert.ok(body instanceof Buffer) try { const json = JSON.parse(body) done(null, json) @@ -162,7 +166,7 @@ test('Should get the body as buffer', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -172,15 +176,16 @@ test('Should get the body as buffer', t => { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '{"hello":"world"}') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), '{"hello":"world"}') fastify.close() + testDone() }) }) }) -test('Should get the body as buffer', t => { +test('Should get the body as buffer', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -189,8 +194,8 @@ test('Should get the body as buffer', t => { }) fastify.addContentTypeParser('text/plain', { parseAs: 'buffer' }, function (req, body, done) { - t.ok('called') - t.ok(body instanceof Buffer) + t.assert.ok('called') + t.assert.ok(body instanceof Buffer) try { const plainText = body done(null, plainText) @@ -201,7 +206,7 @@ test('Should get the body as buffer', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -211,20 +216,21 @@ test('Should get the body as buffer', t => { 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello world') fastify.close() + testDone() }) }) }) -test('Should parse empty bodies as a string', t => { +test('Should parse empty bodies as a string', (t) => { t.plan(9) const fastify = Fastify() fastify.addContentTypeParser('text/plain', { parseAs: 'string' }, (req, body, done) => { - t.equal(body, '') + t.assert.strictEqual(body, '') done(null, body) }) @@ -236,9 +242,11 @@ test('Should parse empty bodies as a string', t => { } }) + const completion = waitForCb({ steps: 2 }) + fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'POST', @@ -248,9 +256,10 @@ test('Should parse empty bodies as a string', t => { 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), '') + completion.stepIn() }) sget({ @@ -262,14 +271,17 @@ test('Should parse empty bodies as a string', t => { 'Content-Length': '0' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), '') + completion.stepIn() }) }) + + return completion.patience }) -test('Should parse empty bodies as a buffer', t => { +test('Should parse empty bodies as a buffer', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -278,13 +290,13 @@ test('Should parse empty bodies as a buffer', t => { }) fastify.addContentTypeParser('text/plain', { parseAs: 'buffer' }, function (req, body, done) { - t.ok(body instanceof Buffer) - t.equal(body.length, 0) + t.assert.ok(body instanceof Buffer) + t.assert.strictEqual(body.length, 0) done(null, body) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -294,15 +306,16 @@ test('Should parse empty bodies as a buffer', t => { 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.length, 0) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.length, 0) fastify.close() + testDone() }) }) }) -test('The charset should not interfere with the content type handling', t => { +test('The charset should not interfere with the content type handling', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -311,14 +324,14 @@ test('The charset should not interfere with the content type handling', t => { }) fastify.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') jsonParser(payload, function (err, body) { done(err, body) }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -328,10 +341,11 @@ test('The charset should not interfere with the content type handling', t => { 'Content-Type': 'application/json; charset=utf-8' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '{"hello":"world"}') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), '{"hello":"world"}') fastify.close() + testDone() }) }) }) From 417b1f8722333d6b4c67354900cd532cef17cafc Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 17 Apr 2025 09:52:47 +0200 Subject: [PATCH 0978/1295] test: custom querystring parser (#6054) --- test/custom-querystring-parser.test.js | 74 ++++++++++++++++---------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/test/custom-querystring-parser.test.js b/test/custom-querystring-parser.test.js index fd88dfe7f18..724d052dab6 100644 --- a/test/custom-querystring-parser.test.js +++ b/test/custom-querystring-parser.test.js @@ -1,49 +1,55 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const querystring = require('node:querystring') const sget = require('simple-get').concat const Fastify = require('..') +const { waitForCb } = require('./toolkit') test('Custom querystring parser', t => { t.plan(9) const fastify = Fastify({ querystringParser: function (str) { - t.equal(str, 'foo=bar&baz=faz') + t.assert.strictEqual(str, 'foo=bar&baz=faz') return querystring.parse(str) } }) fastify.get('/', (req, reply) => { - t.same(req.query, { + t.assert.deepEqual(req.query, { foo: 'bar', baz: 'faz' }) reply.send({ hello: 'world' }) }) + const completion = waitForCb({ steps: 2 }) + fastify.listen({ port: 0 }, (err, address) => { - t.error(err) - t.teardown(() => fastify.close()) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: `${address}?foo=bar&baz=faz` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completion.stepIn() }) fastify.inject({ method: 'GET', url: `${address}?foo=bar&baz=faz` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completion.stepIn() }) }) + + return completion.patience }) test('Custom querystring parser should be called also if there is nothing to parse', t => { @@ -51,36 +57,42 @@ test('Custom querystring parser should be called also if there is nothing to par const fastify = Fastify({ querystringParser: function (str) { - t.equal(str, '') + t.assert.strictEqual(str, '') return querystring.parse(str) } }) fastify.get('/', (req, reply) => { - t.same(req.query, {}) + t.assert.deepEqual(req.query, {}) reply.send({ hello: 'world' }) }) + const completion = waitForCb({ steps: 2 }) + fastify.listen({ port: 0 }, (err, address) => { - t.error(err) - t.teardown(() => fastify.close()) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: address }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completion.stepIn() }) fastify.inject({ method: 'GET', url: address }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completion.stepIn() }) }) + + return completion.patience }) test('Querystring without value', t => { @@ -88,36 +100,42 @@ test('Querystring without value', t => { const fastify = Fastify({ querystringParser: function (str) { - t.equal(str, 'foo') + t.assert.strictEqual(str, 'foo') return querystring.parse(str) } }) fastify.get('/', (req, reply) => { - t.same(req.query, { foo: '' }) + t.assert.deepEqual(req.query, { foo: '' }) reply.send({ hello: 'world' }) }) + const completion = waitForCb({ steps: 2 }) + fastify.listen({ port: 0 }, (err, address) => { - t.error(err) - t.teardown(() => fastify.close()) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: `${address}?foo` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completion.stepIn() }) fastify.inject({ method: 'GET', url: `${address}?foo` }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completion.stepIn() }) }) + + return completion.patience }) test('Custom querystring parser should be a function', t => { @@ -127,9 +145,9 @@ test('Custom querystring parser should be a function', t => { Fastify({ querystringParser: 10 }) - t.fail('Should throw') + t.assert.fail('Should throw') } catch (err) { - t.equal( + t.assert.strictEqual( err.message, "querystringParser option should be a function, instead got 'number'" ) From 0a9aa87d3577cda8b5f24f3be92ae781f43d220c Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 17 Apr 2025 09:54:35 +0200 Subject: [PATCH 0979/1295] test: migrate stream 4 to node test runner (#6062) --- test/stream.4.test.js | 71 +++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/test/stream.4.test.js b/test/stream.4.test.js index 0a6101b845f..04231993e1a 100644 --- a/test/stream.4.test.js +++ b/test/stream.4.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const errors = require('http-errors') const JSONStream = require('JSONStream') @@ -10,7 +9,7 @@ const split = require('split2') const Fastify = require('..') const { kDisableRequestLogging } = require('../lib/symbols.js') -test('Destroying streams prematurely should call abort method', t => { +test('Destroying streams prematurely should call abort method', (t, testDone) => { t.plan(7) let fastify = null @@ -23,7 +22,7 @@ test('Destroying streams prematurely should call abort method', t => { } }) } catch (e) { - t.fail() + t.assert.fail() } const stream = require('node:stream') const http = require('node:http') @@ -31,13 +30,14 @@ test('Destroying streams prematurely should call abort method', t => { // Test that "premature close" errors are logged with level warn logStream.on('data', line => { if (line.res) { - t.equal(line.msg, 'stream closed prematurely') - t.equal(line.level, 30) + t.assert.strictEqual(line.msg, 'stream closed prematurely') + t.assert.strictEqual(line.level, 30) + testDone() } }) fastify.get('/', function (request, reply) { - t.pass('Received request') + t.assert.ok('Received request') let sent = false const reallyLongStream = new stream.Readable({ @@ -50,30 +50,30 @@ test('Destroying streams prematurely should call abort method', t => { }) reallyLongStream.destroy = undefined reallyLongStream.close = undefined - reallyLongStream.abort = () => t.ok('called') + reallyLongStream.abort = () => t.assert.ok('called') reply.send(reallyLongStream) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) const port = fastify.server.address().port http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) response.on('readable', function () { response.destroy() }) // Node bug? Node never emits 'close' here. response.on('aborted', function () { - t.pass('Response closed') + t.assert.ok('Response closed') }) }) }) }) -test('Destroying streams prematurely, log is disabled', t => { +test('Destroying streams prematurely, log is disabled', (t, testDone) => { t.plan(4) let fastify = null @@ -82,7 +82,7 @@ test('Destroying streams prematurely, log is disabled', t => { logger: false }) } catch (e) { - t.fail() + t.assert.fail() } const stream = require('node:stream') const http = require('node:http') @@ -100,30 +100,33 @@ test('Destroying streams prematurely, log is disabled', t => { } }) reallyLongStream.destroy = true - reallyLongStream.close = () => t.ok('called') + reallyLongStream.close = () => { + t.assert.ok('called') + testDone() + } reply.send(reallyLongStream) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) const port = fastify.server.address().port http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) response.on('readable', function () { response.destroy() }) // Node bug? Node never emits 'close' here. response.on('aborted', function () { - t.pass('Response closed') + t.assert.ok('Response closed') }) }) }) }) -test('should respond with a stream1', t => { +test('should respond with a stream1', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -135,25 +138,26 @@ test('should respond with a stream1', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget(`http://localhost:${fastify.server.address().port}`, function (err, response, body) { - t.error(err) - t.equal(response.headers['content-type'], 'application/json') - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), [{ hello: 'world' }, { a: 42 }]) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/json') + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), [{ hello: 'world' }, { a: 42 }]) + testDone() }) }) }) -test('return a 404 if the stream emits a 404 error', t => { +test('return a 404 if the stream emits a 404 error', (t, testDone) => { t.plan(5) const fastify = Fastify() fastify.get('/', function (request, reply) { - t.pass('Received request') + t.assert.ok('Received request') const reallyLongStream = new Readable({ read: function () { @@ -167,15 +171,16 @@ test('return a 404 if the stream emits a 404 error', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) const port = fastify.server.address().port sget(`http://localhost:${port}`, function (err, response) { - t.error(err) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.equal(response.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') + t.assert.strictEqual(response.statusCode, 404) + testDone() }) }) }) From f30973cc6da9060fdd429edcc930a6fa0880fef1 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 17 Apr 2025 09:59:41 +0200 Subject: [PATCH 0980/1295] test: migrate request logger to node test runner (#6058) --- test/logger/request.test.js | 132 ++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/test/logger/request.test.js b/test/logger/request.test.js index a1702a4fe9e..212b8d5296f 100644 --- a/test/logger/request.test.js +++ b/test/logger/request.test.js @@ -2,17 +2,16 @@ const stream = require('node:stream') -const t = require('tap') +const t = require('node:test') const split = require('split2') const Fastify = require('../../fastify') const helper = require('../helper') const { on } = stream const { request } = require('./logger-test-utils') +const { partialDeepStrictEqual } = require('../toolkit') -t.test('request', (t) => { - t.setTimeout(60000) - +t.test('request', { timeout: 60000 }, async (t) => { let localhost t.plan(7) @@ -20,7 +19,7 @@ t.test('request', (t) => { [localhost] = await helper.getLoopbackHost() }) - t.test('The request id header key can be customized', async (t) => { + await t.test('The request id header key can be customized', async (t) => { const lines = ['incoming request', 'some log message', 'request completed'] t.plan(lines.length * 2 + 2) const REQUEST_ID = '42' @@ -30,26 +29,26 @@ t.test('request', (t) => { logger: { stream, level: 'info' }, requestIdHeader: 'my-custom-request-id' }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) req.log.info('some log message') reply.send({ id: req.id }) }) const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } }) const body = await response.json() - t.equal(body.id, REQUEST_ID) + t.assert.strictEqual(body.id, REQUEST_ID) for await (const [line] of on(stream, 'data')) { - t.equal(line.reqId, REQUEST_ID) - t.equal(line.msg, lines.shift(), 'message is set') + t.assert.strictEqual(line.reqId, REQUEST_ID) + t.assert.strictEqual(line.msg, lines.shift(), 'message is set') if (lines.length === 0) break } }) - t.test('The request id header key can be ignored', async (t) => { + await t.test('The request id header key can be ignored', async (t) => { const lines = ['incoming request', 'some log message', 'request completed'] t.plan(lines.length * 2 + 2) const REQUEST_ID = 'ignore-me' @@ -59,33 +58,33 @@ t.test('request', (t) => { logger: { stream, level: 'info' }, requestIdHeader: false }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { - t.equal(req.id, 'req-1') + t.assert.strictEqual(req.id, 'req-1') req.log.info('some log message') reply.send({ id: req.id }) }) const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'request-id': REQUEST_ID } }) const body = await response.json() - t.equal(body.id, 'req-1') + t.assert.strictEqual(body.id, 'req-1') for await (const [line] of on(stream, 'data')) { - t.equal(line.reqId, 'req-1') - t.equal(line.msg, lines.shift(), 'message is set') + t.assert.strictEqual(line.reqId, 'req-1') + t.assert.strictEqual(line.msg, lines.shift(), 'message is set') if (lines.length === 0) break } }) - t.test('The request id header key can be customized along with a custom id generator', async (t) => { + await t.test('The request id header key can be customized along with a custom id generator', async (t) => { const REQUEST_ID = '42' const matches = [ - { reqId: REQUEST_ID, msg: /incoming request/ }, - { reqId: REQUEST_ID, msg: /some log message/ }, - { reqId: REQUEST_ID, msg: /request completed/ }, - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message 2/ }, - { reqId: 'foo', msg: /request completed/ } + { reqId: REQUEST_ID, msg: 'incoming request' }, + { reqId: REQUEST_ID, msg: 'some log message' }, + { reqId: REQUEST_ID, msg: 'request completed' }, + { reqId: 'foo', msg: 'incoming request' }, + { reqId: 'foo', msg: 'some log message 2' }, + { reqId: 'foo', msg: 'request completed' } ] t.plan(matches.length + 4) @@ -97,16 +96,16 @@ t.test('request', (t) => { return 'foo' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/one', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) req.log.info('some log message') reply.send({ id: req.id }) }) fastify.get('/two', (req, reply) => { - t.equal(req.id, 'foo') + t.assert.strictEqual(req.id, 'foo') req.log.info('some log message 2') reply.send({ id: req.id }) }) @@ -114,30 +113,30 @@ t.test('request', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) const body = await response.json() - t.equal(body.id, REQUEST_ID) + t.assert.strictEqual(body.id, REQUEST_ID) } { const response = await fastify.inject({ method: 'GET', url: '/two' }) const body = await response.json() - t.equal(body.id, 'foo') + t.assert.strictEqual(body.id, 'foo') } for await (const [line] of on(stream, 'data')) { - t.match(line, matches.shift()) + t.assert.ok(partialDeepStrictEqual(line, matches.shift())) if (matches.length === 0) break } }) - t.test('The request id header key can be ignored along with a custom id generator', async (t) => { + await t.test('The request id header key can be ignored along with a custom id generator', async (t) => { const REQUEST_ID = 'ignore-me' const matches = [ - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message/ }, - { reqId: 'foo', msg: /request completed/ }, - { reqId: 'foo', msg: /incoming request/ }, - { reqId: 'foo', msg: /some log message 2/ }, - { reqId: 'foo', msg: /request completed/ } + { reqId: 'foo', msg: 'incoming request' }, + { reqId: 'foo', msg: 'some log message' }, + { reqId: 'foo', msg: 'request completed' }, + { reqId: 'foo', msg: 'incoming request' }, + { reqId: 'foo', msg: 'some log message 2' }, + { reqId: 'foo', msg: 'request completed' } ] t.plan(matches.length + 4) @@ -149,16 +148,16 @@ t.test('request', (t) => { return 'foo' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/one', (req, reply) => { - t.equal(req.id, 'foo') + t.assert.strictEqual(req.id, 'foo') req.log.info('some log message') reply.send({ id: req.id }) }) fastify.get('/two', (req, reply) => { - t.equal(req.id, 'foo') + t.assert.strictEqual(req.id, 'foo') req.log.info('some log message 2') reply.send({ id: req.id }) }) @@ -166,27 +165,27 @@ t.test('request', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'request-id': REQUEST_ID } }) const body = await response.json() - t.equal(body.id, 'foo') + t.assert.strictEqual(body.id, 'foo') } { const response = await fastify.inject({ method: 'GET', url: '/two' }) const body = await response.json() - t.equal(body.id, 'foo') + t.assert.strictEqual(body.id, 'foo') } for await (const [line] of on(stream, 'data')) { - t.match(line, matches.shift()) + t.assert.ok(partialDeepStrictEqual(line, matches.shift())) if (matches.length === 0) break } }) - t.test('The request id log label can be changed', async (t) => { + await t.test('The request id log label can be changed', async (t) => { const REQUEST_ID = '42' const matches = [ - { traceId: REQUEST_ID, msg: /incoming request/ }, - { traceId: REQUEST_ID, msg: /some log message/ }, - { traceId: REQUEST_ID, msg: /request completed/ } + { traceId: REQUEST_ID, msg: 'incoming request' }, + { traceId: REQUEST_ID, msg: 'some log message' }, + { traceId: REQUEST_ID, msg: 'request completed' } ] t.plan(matches.length + 2) @@ -196,10 +195,10 @@ t.test('request', (t) => { requestIdHeader: 'my-custom-request-id', requestIdLogLabel: 'traceId' }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/one', (req, reply) => { - t.equal(req.id, REQUEST_ID) + t.assert.strictEqual(req.id, REQUEST_ID) req.log.info('some log message') reply.send({ id: req.id }) }) @@ -207,22 +206,16 @@ t.test('request', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) const body = await response.json() - t.equal(body.id, REQUEST_ID) + t.assert.strictEqual(body.id, REQUEST_ID) } for await (const [line] of on(stream, 'data')) { - t.match(line, matches.shift()) + t.assert.ok(partialDeepStrictEqual(line, matches.shift())) if (matches.length === 0) break } }) - t.test('should redact the authorization header if so specified', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' }, - { res: { statusCode: 200 }, msg: 'request completed' } - ] - t.plan(lines.length + 3) + await t.test('should redact the authorization header if so specified', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -243,15 +236,22 @@ t.test('request', (t) => { } } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { - t.same(req.headers.authorization, 'Bearer abcde') + t.assert.deepStrictEqual(req.headers.authorization, 'Bearer abcde') reply.send({ hello: 'world' }) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) await request({ method: 'GET', @@ -262,17 +262,17 @@ t.test('request', (t) => { authorization: 'Bearer abcde' } }, function (response, body) { - t.equal(response.statusCode, 200) - t.same(body, JSON.stringify({ hello: 'world' })) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, JSON.stringify({ hello: 'world' })) }) for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('should not throw error when serializing custom req', (t) => { + await t.test('should not throw error when serializing custom req', (t) => { t.plan(1) const lines = [] @@ -283,10 +283,10 @@ t.test('request', (t) => { } }) const fastify = Fastify({ logger: { level: 'info', stream: dest } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.log.info({ req: {} }) - t.same(lines[0].req, {}) + t.assert.deepStrictEqual(lines[0].req, {}) }) }) From 2ce1ae1ddd46d4b3b8243dab59c2c3760c842e3b Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 17 Apr 2025 10:05:14 +0200 Subject: [PATCH 0981/1295] test: migrate custom parser 0 to node test runner (#6052) --- test/custom-parser.0.test.js | 289 +++++++++++++++++++---------------- 1 file changed, 160 insertions(+), 129 deletions(-) diff --git a/test/custom-parser.0.test.js b/test/custom-parser.0.test.js index 340f710f8f0..16aea6b73c7 100644 --- a/test/custom-parser.0.test.js +++ b/test/custom-parser.0.test.js @@ -1,22 +1,22 @@ 'use strict' const fs = require('node:fs') -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../fastify') const jsonParser = require('fast-json-body') const { getServerUrl, plainTextParser } = require('./helper') +const { waitForCb } = require('./toolkit') process.removeAllListeners('warning') test('contentTypeParser method should exist', t => { t.plan(1) const fastify = Fastify() - t.ok(fastify.addContentTypeParser) + t.assert.ok(fastify.addContentTypeParser) }) -test('contentTypeParser should add a custom parser', t => { +test('contentTypeParser should add a custom parser', (t, mainTestDone) => { t.plan(3) const fastify = Fastify() @@ -35,11 +35,13 @@ test('contentTypeParser should add a custom parser', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) - t.teardown(() => fastify.close()) + const completion = waitForCb({ steps: 2 }) - t.test('in POST', t => { + t.after(() => fastify.close()) + + t.test('in POST', (t, testDone) => { t.plan(3) sget({ @@ -50,13 +52,15 @@ test('contentTypeParser should add a custom parser', t => { 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + testDone() + completion.stepIn() }) }) - t.test('in OPTIONS', t => { + t.test('in OPTIONS', (t, testDone) => { t.plan(3) sget({ @@ -67,15 +71,18 @@ test('contentTypeParser should add a custom parser', t => { 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + testDone() + completion.stepIn() }) }) + completion.patience.then(mainTestDone) }) }) -test('contentTypeParser should handle multiple custom parsers', t => { +test('contentTypeParser should handle multiple custom parsers', (t, testDone) => { t.plan(7) const fastify = Fastify() @@ -97,8 +104,10 @@ test('contentTypeParser should handle multiple custom parsers', t => { fastify.addContentTypeParser('application/ffosj', customParser) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + const completion = waitForCb({ steps: 2 }) sget({ method: 'POST', @@ -108,9 +117,10 @@ test('contentTypeParser should handle multiple custom parsers', t => { 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + completion.stepIn() }) sget({ @@ -121,14 +131,16 @@ test('contentTypeParser should handle multiple custom parsers', t => { 'Content-Type': 'application/ffosj' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('contentTypeParser should handle an array of custom contentTypes', t => { +test('contentTypeParser should handle an array of custom contentTypes', (t, testDone) => { t.plan(7) const fastify = Fastify() @@ -149,8 +161,10 @@ test('contentTypeParser should handle an array of custom contentTypes', t => { fastify.addContentTypeParser(['application/jsoff', 'application/ffosj'], customParser) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + const completion = waitForCb({ steps: 2 }) sget({ method: 'POST', @@ -160,9 +174,10 @@ test('contentTypeParser should handle an array of custom contentTypes', t => { 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + completion.stepIn() }) sget({ @@ -173,14 +188,16 @@ test('contentTypeParser should handle an array of custom contentTypes', t => { 'Content-Type': 'application/ffosj' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('contentTypeParser should handle errors', t => { +test('contentTypeParser should handle errors', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -193,7 +210,7 @@ test('contentTypeParser should handle errors', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -203,25 +220,26 @@ test('contentTypeParser should handle errors', t => { 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) fastify.close() + testDone() }) }) }) -test('contentTypeParser should support encapsulation', t => { +test('contentTypeParser should support encapsulation', (t, testDone) => { t.plan(6) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.addContentTypeParser('application/jsoff', () => {}) - t.ok(instance.hasContentTypeParser('application/jsoff')) + t.assert.ok(instance.hasContentTypeParser('application/jsoff')) instance.register((instance, opts, done) => { instance.addContentTypeParser('application/ffosj', () => {}) - t.ok(instance.hasContentTypeParser('application/jsoff')) - t.ok(instance.hasContentTypeParser('application/ffosj')) + t.assert.ok(instance.hasContentTypeParser('application/jsoff')) + t.assert.ok(instance.hasContentTypeParser('application/ffosj')) done() }) @@ -229,13 +247,14 @@ test('contentTypeParser should support encapsulation', t => { }) fastify.ready(err => { - t.error(err) - t.notOk(fastify.hasContentTypeParser('application/jsoff')) - t.notOk(fastify.hasContentTypeParser('application/ffosj')) + t.assert.ifError(err) + t.assert.ok(!fastify.hasContentTypeParser('application/jsoff')) + t.assert.ok(!fastify.hasContentTypeParser('application/ffosj')) + testDone() }) }) -test('contentTypeParser should support encapsulation, second try', t => { +test('contentTypeParser should support encapsulation, second try', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -254,7 +273,7 @@ test('contentTypeParser should support encapsulation, second try', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -264,15 +283,16 @@ test('contentTypeParser should support encapsulation, second try', t => { 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) fastify.close() + testDone() }) }) }) -test('contentTypeParser shouldn\'t support request with undefined "Content-Type"', t => { +test('contentTypeParser shouldn\'t support request with undefined "Content-Type"', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -287,7 +307,7 @@ test('contentTypeParser shouldn\'t support request with undefined "Content-Type" }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -297,9 +317,10 @@ test('contentTypeParser shouldn\'t support request with undefined "Content-Type" // 'Content-Type': undefined } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) fastify.close() + testDone() }) }) }) @@ -310,10 +331,10 @@ test('the content type should be a string or RegExp', t => { try { fastify.addContentTypeParser(null, () => {}) - t.fail() + t.assert.fail() } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_INVALID_TYPE') - t.equal(err.message, 'The content type should be a string or a RegExp') + t.assert.strictEqual(err.code, 'FST_ERR_CTP_INVALID_TYPE') + t.assert.strictEqual(err.message, 'The content type should be a string or a RegExp') } }) @@ -323,10 +344,10 @@ test('the content type cannot be an empty string', t => { try { fastify.addContentTypeParser('', () => {}) - t.fail() + t.assert.fail() } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_EMPTY_TYPE') - t.equal(err.message, 'The content type cannot be an empty string') + t.assert.strictEqual(err.code, 'FST_ERR_CTP_EMPTY_TYPE') + t.assert.strictEqual(err.message, 'The content type cannot be an empty string') } }) @@ -336,14 +357,14 @@ test('the content type handler should be a function', t => { try { fastify.addContentTypeParser('aaa', null) - t.fail() + t.assert.fail() } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_INVALID_HANDLER') - t.equal(err.message, 'The content type handler should be a function') + t.assert.strictEqual(err.code, 'FST_ERR_CTP_INVALID_HANDLER') + t.assert.strictEqual(err.message, 'The content type handler should be a function') } }) -test('catch all content type parser', t => { +test('catch all content type parser', (t, testDone) => { t.plan(7) const fastify = Fastify() @@ -360,7 +381,7 @@ test('catch all content type parser', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -370,9 +391,9 @@ test('catch all content type parser', t => { 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello') sget({ method: 'POST', @@ -382,16 +403,17 @@ test('catch all content type parser', t => { 'Content-Type': 'very-weird-content-type' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello') fastify.close() + testDone() }) }) }) }) -test('catch all content type parser should not interfere with other conte type parsers', t => { +test('catch all content type parser should not interfere with other conte type parsers', (t, testDone) => { t.plan(7) const fastify = Fastify() @@ -414,7 +436,7 @@ test('catch all content type parser should not interfere with other conte type p }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -424,9 +446,9 @@ test('catch all content type parser should not interfere with other conte type p 'Content-Type': 'application/jsoff' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) sget({ method: 'POST', @@ -436,22 +458,23 @@ test('catch all content type parser should not interfere with other conte type p 'Content-Type': 'very-weird-content-type' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), 'hello') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello') fastify.close() + testDone() }) }) }) }) // Issue 492 https://github.com/fastify/fastify/issues/492 -test('\'*\' catch undefined Content-Type requests', t => { +test('\'*\' catch undefined Content-Type requests', (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.addContentTypeParser('*', function (req, payload, done) { let data = '' @@ -467,7 +490,7 @@ test('\'*\' catch undefined Content-Type requests', t => { }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) const fileStream = fs.createReadStream(__filename) @@ -476,37 +499,39 @@ test('\'*\' catch undefined Content-Type requests', t => { url: getServerUrl(fastify) + '/', body: fileStream }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body + '', fs.readFileSync(__filename).toString()) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body + '', fs.readFileSync(__filename).toString()) + testDone() }) }) }) -test('cannot add custom parser after binding', t => { +test('cannot add custom parser after binding', (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.post('/', (req, res) => { res.type('text/plain').send(req.body) }) fastify.listen({ port: 0 }, function (err) { - t.error(err) + t.assert.ifError(err) try { fastify.addContentTypeParser('*', () => {}) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) + testDone() } }) }) -test('Can override the default json parser', t => { +test('Can override the default json parser', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -515,14 +540,14 @@ test('Can override the default json parser', t => { }) fastify.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') jsonParser(payload, function (err, body) { done(err, body) }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -532,15 +557,16 @@ test('Can override the default json parser', t => { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '{"hello":"world"}') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), '{"hello":"world"}') fastify.close() + testDone() }) }) }) -test('Can override the default plain text parser', t => { +test('Can override the default plain text parser', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -549,14 +575,14 @@ test('Can override the default plain text parser', t => { }) fastify.addContentTypeParser('text/plain', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') plainTextParser(payload, function (err, body) { done(err, body) }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -566,21 +592,22 @@ test('Can override the default plain text parser', t => { 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello world') fastify.close() + testDone() }) }) }) -test('Can override the default json parser in a plugin', t => { +test('Can override the default json parser in a plugin', (t, testDone) => { t.plan(5) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') jsonParser(payload, function (err, body) { done(err, body) }) @@ -594,7 +621,7 @@ test('Can override the default json parser in a plugin', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -604,10 +631,11 @@ test('Can override the default json parser in a plugin', t => { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '{"hello":"world"}') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), '{"hello":"world"}') fastify.close() + testDone() }) }) }) @@ -624,14 +652,14 @@ test('Can\'t override the json parser multiple times', t => { try { fastify.addContentTypeParser('application/json', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') jsonParser(payload, function (err, body) { done(err, body) }) }) } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') - t.equal(err.message, 'Content type parser \'application/json\' already present.') + t.assert.strictEqual(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.assert.strictEqual(err.message, 'Content type parser \'application/json\' already present.') } }) @@ -647,18 +675,18 @@ test('Can\'t override the plain text parser multiple times', t => { try { fastify.addContentTypeParser('text/plain', function (req, payload, done) { - t.ok('called') + t.assert.ok('called') plainTextParser(payload, function (err, body) { done(err, body) }) }) } catch (err) { - t.equal(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') - t.equal(err.message, 'Content type parser \'text/plain\' already present.') + t.assert.strictEqual(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.assert.strictEqual(err.message, 'Content type parser \'text/plain\' already present.') } }) -test('Should get the body as string', t => { +test('Should get the body as string', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -667,8 +695,8 @@ test('Should get the body as string', t => { }) fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { - t.ok('called') - t.ok(typeof body === 'string') + t.assert.ok('called') + t.assert.ok(typeof body === 'string') try { const json = JSON.parse(body) done(null, json) @@ -679,7 +707,7 @@ test('Should get the body as string', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -689,15 +717,16 @@ test('Should get the body as string', t => { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '{"hello":"world"}') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), '{"hello":"world"}') fastify.close() + testDone() }) }) }) -test('Should return defined body with no custom parser defined and content type = \'text/plain\'', t => { +test('Should return defined body with no custom parser defined and content type = \'text/plain\'', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -706,7 +735,7 @@ test('Should return defined body with no custom parser defined and content type }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -716,15 +745,16 @@ test('Should return defined body with no custom parser defined and content type 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'hello world') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'hello world') fastify.close() + testDone() }) }) }) -test('Should have typeof body object with no custom parser defined, no body defined and content type = \'text/plain\'', t => { +test('Should have typeof body object with no custom parser defined, no body defined and content type = \'text/plain\'', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -733,7 +763,7 @@ test('Should have typeof body object with no custom parser defined, no body defi }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -742,10 +772,11 @@ test('Should have typeof body object with no custom parser defined, no body defi 'Content-Type': 'text/plain' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(typeof body, 'object') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(typeof body, 'object') fastify.close() + testDone() }) }) }) From ebbd4a694ffbd4261a3ced97f65ad81da00db7dc Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 17 Apr 2025 10:09:23 +0200 Subject: [PATCH 0982/1295] test: migrate logger instantiation to node test runner (#6061) --- test/logger/instantiation.test.js | 185 ++++++++++++++---------------- 1 file changed, 89 insertions(+), 96 deletions(-) diff --git a/test/logger/instantiation.test.js b/test/logger/instantiation.test.js index 9793e699264..3fd84421964 100644 --- a/test/logger/instantiation.test.js +++ b/test/logger/instantiation.test.js @@ -4,7 +4,7 @@ const stream = require('node:stream') const os = require('node:os') const fs = require('node:fs') -const t = require('tap') +const t = require('node:test') const split = require('split2') const { streamSym } = require('pino/lib/symbols') @@ -14,10 +14,9 @@ const helper = require('../helper') const { FST_ERR_LOG_INVALID_LOGGER } = require('../../lib/errors') const { once, on } = stream const { createTempFile, request } = require('./logger-test-utils') +const { partialDeepStrictEqual } = require('../toolkit') -t.test('logger instantiation', (t) => { - t.setTimeout(60000) - +t.test('logger instantiation', { timeout: 60000 }, async (t) => { let localhost let localhostForURL @@ -26,7 +25,7 @@ t.test('logger instantiation', (t) => { [localhost, localhostForURL] = await helper.getLoopbackHost() }) - t.test('can use external logger instance', async (t) => { + await t.test('can use external logger instance', async (t) => { const lines = [/^Server listening at /, /^incoming request$/, /^log success$/, /^request completed$/] t.plan(lines.length + 1) @@ -35,10 +34,10 @@ t.test('logger instantiation', (t) => { const loggerInstance = require('pino')(stream) const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/foo', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) req.log.info('log success') reply.send({ hello: 'world' }) }) @@ -49,30 +48,30 @@ t.test('logger instantiation', (t) => { for await (const [line] of on(stream, 'data')) { const regex = lines.shift() - t.ok(regex.test(line.msg), '"' + line.msg + '" does not match "' + regex + '"') + t.assert.ok(regex.test(line.msg), '"' + line.msg + '" does not match "' + regex + '"') if (lines.length === 0) break } }) - t.test('should create a default logger if provided one is invalid', (t) => { + await t.test('should create a default logger if provided one is invalid', (t) => { t.plan(8) const logger = new Date() const fastify = Fastify({ logger }) - t.teardown(fastify.close.bind(fastify)) - - t.equal(typeof fastify.log, 'object') - t.equal(typeof fastify.log.fatal, 'function') - t.equal(typeof fastify.log.error, 'function') - t.equal(typeof fastify.log.warn, 'function') - t.equal(typeof fastify.log.info, 'function') - t.equal(typeof fastify.log.debug, 'function') - t.equal(typeof fastify.log.trace, 'function') - t.equal(typeof fastify.log.child, 'function') + t.after(() => fastify.close()) + + t.assert.strictEqual(typeof fastify.log, 'object') + t.assert.strictEqual(typeof fastify.log.fatal, 'function') + t.assert.strictEqual(typeof fastify.log.error, 'function') + t.assert.strictEqual(typeof fastify.log.warn, 'function') + t.assert.strictEqual(typeof fastify.log.info, 'function') + t.assert.strictEqual(typeof fastify.log.debug, 'function') + t.assert.strictEqual(typeof fastify.log.trace, 'function') + t.assert.strictEqual(typeof fastify.log.child, 'function') }) - t.test('expose the logger', async (t) => { + await t.test('expose the logger', async (t) => { t.plan(2) const stream = split(JSON.parse) const fastify = Fastify({ @@ -81,49 +80,45 @@ t.test('logger instantiation', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) await fastify.ready() - t.ok(fastify.log) - t.same(typeof fastify.log, 'object') + t.assert.ok(fastify.log) + t.assert.strictEqual(typeof fastify.log, 'object') }) - t.test('Wrap IPv6 address in listening log message', async (t) => { + const interfaces = os.networkInterfaces() + const ipv6 = Object.keys(interfaces) + .filter(name => name.substr(0, 2) === 'lo') + .map(name => interfaces[name]) + .reduce((list, set) => list.concat(set), []) + .filter(info => info.family === 'IPv6') + .map(info => info.address) + .shift() + + await t.test('Wrap IPv6 address in listening log message', { skip: !ipv6 }, async (t) => { t.plan(1) - const interfaces = os.networkInterfaces() - const ipv6 = Object.keys(interfaces) - .filter(name => name.substr(0, 2) === 'lo') - .map(name => interfaces[name]) - .reduce((list, set) => list.concat(set), []) - .filter(info => info.family === 'IPv6') - .map(info => info.address) - .shift() - - if (ipv6 === undefined) { - t.pass('No IPv6 loopback interface') - } else { - const stream = split(JSON.parse) - const fastify = Fastify({ - logger: { - stream, - level: 'info' - } - }) - t.teardown(fastify.close.bind(fastify)) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) - await fastify.ready() - await fastify.listen({ port: 0, host: ipv6 }) + await fastify.ready() + await fastify.listen({ port: 0, host: ipv6 }) - { - const [line] = await once(stream, 'data') - t.same(line.msg, `Server listening at http://[${ipv6}]:${fastify.server.address().port}`) - } + { + const [line] = await once(stream, 'data') + t.assert.strictEqual(line.msg, `Server listening at http://[${ipv6}]:${fastify.server.address().port}`) } }) - t.test('Do not wrap IPv4 address', async (t) => { + await t.test('Do not wrap IPv4 address', async (t) => { t.plan(1) const stream = split(JSON.parse) const fastify = Fastify({ @@ -132,24 +127,18 @@ t.test('logger instantiation', (t) => { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) await fastify.ready() await fastify.listen({ port: 0, host: '127.0.0.1' }) { const [line] = await once(stream, 'data') - t.same(line.msg, `Server listening at http://127.0.0.1:${fastify.server.address().port}`) + t.assert.strictEqual(line.msg, `Server listening at http://127.0.0.1:${fastify.server.address().port}`) } }) - t.test('file option', async (t) => { - const lines = [ - { msg: /Server listening at/ }, - { reqId: /req-/, req: { method: 'GET', url: '/' }, msg: 'incoming request' }, - { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' } - ] - + await t.test('file option', async (t) => { const { file, cleanup } = createTempFile(t) // 0600 permissions (read/write for owner only) if (process.env.CITGM) { fs.writeFileSync(file, '', { mode: 0o600 }) } @@ -158,7 +147,7 @@ t.test('logger instantiation', (t) => { logger: { file } }) - t.teardown(async () => { + t.after(async () => { await helper.sleep(250) // may fail on win try { @@ -174,15 +163,20 @@ t.test('logger instantiation', (t) => { console.warn(err) } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send({ hello: 'world' }) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { method: 'GET', url: '/' }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] await request(`http://${localhostForURL}:` + fastify.server.address().port) await helper.sleep(250) @@ -195,26 +189,26 @@ t.test('logger instantiation', (t) => { for (let line of log) { line = JSON.parse(line) if (id === undefined && line.reqId) id = line.reqId - if (id !== undefined && line.reqId) t.equal(line.reqId, id) - t.match(line, lines.shift()) + if (id !== undefined && line.reqId) t.assert.strictEqual(line.reqId, id) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) } }) - t.test('should be able to use a custom logger', (t) => { + await t.test('should be able to use a custom logger', (t) => { t.plan(7) const loggerInstance = { - fatal: (msg) => { t.equal(msg, 'fatal') }, - error: (msg) => { t.equal(msg, 'error') }, - warn: (msg) => { t.equal(msg, 'warn') }, - info: (msg) => { t.equal(msg, 'info') }, - debug: (msg) => { t.equal(msg, 'debug') }, - trace: (msg) => { t.equal(msg, 'trace') }, + fatal: (msg) => { t.assert.strictEqual(msg, 'fatal') }, + error: (msg) => { t.assert.strictEqual(msg, 'error') }, + warn: (msg) => { t.assert.strictEqual(msg, 'warn') }, + info: (msg) => { t.assert.strictEqual(msg, 'info') }, + debug: (msg) => { t.assert.strictEqual(msg, 'debug') }, + trace: (msg) => { t.assert.strictEqual(msg, 'trace') }, child: () => loggerInstance } const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.log.fatal('fatal') fastify.log.error('error') @@ -223,17 +217,17 @@ t.test('logger instantiation', (t) => { fastify.log.debug('debug') fastify.log.trace('trace') const child = fastify.log.child() - t.equal(child, loggerInstance) + t.assert.strictEqual(child, loggerInstance) }) - t.test('should throw in case a partially matching logger is provided', async (t) => { + await t.test('should throw in case a partially matching logger is provided', async (t) => { t.plan(1) try { const fastify = Fastify({ logger: console }) await fastify.ready() } catch (err) { - t.equal( + t.assert.strictEqual( err instanceof FST_ERR_LOG_INVALID_LOGGER, true, "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'." @@ -241,7 +235,7 @@ t.test('logger instantiation', (t) => { } }) - t.test('can use external logger instance with custom serializer', async (t) => { + await t.test('can use external logger instance with custom serializer', async (t) => { const lines = [['level', 30], ['req', { url: '/foo' }], ['level', 30], ['res', { statusCode: 200 }]] t.plan(lines.length + 1) @@ -260,10 +254,10 @@ t.test('logger instantiation', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/foo', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) req.log.info('log success') reply.send({ hello: 'world' }) }) @@ -277,20 +271,12 @@ t.test('logger instantiation', (t) => { const check = lines.shift() const key = check[0] const value = check[1] - t.same(line[key], value) + t.assert.deepStrictEqual(line[key], value) if (lines.length === 0) break } }) - t.test('The logger should accept custom serializer', async (t) => { - const lines = [ - { msg: /^Server listening at / }, - { req: { url: '/custom' }, msg: 'incoming request' }, - { res: { statusCode: 500 }, msg: 'kaboom' }, - { res: { statusCode: 500 }, msg: 'request completed' } - ] - t.plan(lines.length + 1) - + await t.test('The logger should accept custom serializer', async (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { @@ -305,25 +291,32 @@ t.test('logger instantiation', (t) => { } } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/custom', function (req, reply) { - t.ok(req.log) + t.assert.ok(req.log) reply.send(new Error('kaboom')) }) await fastify.ready() - await fastify.listen({ port: 0, host: localhost }) + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { url: '/custom' }, msg: 'incoming request' }, + { res: { statusCode: 500 }, msg: 'kaboom' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) await request(`http://${localhostForURL}:` + fastify.server.address().port + '/custom') for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('should throw in case the external logger provided does not have a child method', async (t) => { + await t.test('should throw in case the external logger provided does not have a child method', async (t) => { t.plan(1) const loggerInstance = { info: console.info, @@ -338,7 +331,7 @@ t.test('logger instantiation', (t) => { const fastify = Fastify({ logger: loggerInstance }) await fastify.ready() } catch (err) { - t.equal( + t.assert.strictEqual( err instanceof FST_ERR_LOG_INVALID_LOGGER, true, "Invalid logger object provided. The logger instance should have these functions(s): 'child'." From 436da4c06dfbbb8c24adee3a64de0c51e4f47418 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:24:03 +0800 Subject: [PATCH 0983/1295] Merge commit from fork * fix: schema validation bypass * chore: update comments Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: KaKa <23028015+climba03003@users.noreply.github.com> --------- Signed-off-by: KaKa <23028015+climba03003@users.noreply.github.com> Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> --- lib/validation.js | 12 +++- test/schema-validation.test.js | 120 +++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/lib/validation.js b/lib/validation.js index 1e490cf137b..3fcb64fa317 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -155,7 +155,7 @@ function validate (context, request, execution) { validatorFunction = context[bodySchema] } else if (context[bodySchema]) { // TODO: add request.contentType and reuse it here - const contentType = request.headers['content-type']?.split(';', 1)[0] + const contentType = getEssenceMediaType(request.headers['content-type']) const contentSchema = context[bodySchema][contentType] if (contentSchema) { validatorFunction = contentSchema @@ -254,6 +254,16 @@ function wrapValidationError (result, dataVar, schemaErrorFormatter) { return error } +/** + * simple function to retrieve the essence media type + * @param {string} header + * @returns {string} Mimetype string. + */ +function getEssenceMediaType (header) { + if (!header) return '' + return header.split(';', 1)[0].trim().toLowerCase() +} + module.exports = { symbols: { bodySchema, querystringSchema, responseSchema, paramsSchema, headersSchema }, compileSchemasForValidation, diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index f4c8c0b9045..c0d93bb307f 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -1300,3 +1300,123 @@ test('Custom validator builder override by custom validator compiler in child in }) t.equal(two.statusCode, 200) }) + +test('Schema validation when no content type is provided', async t => { + // this case should not be happened in normal use-case, + // it is added for the completeness of code branch + const fastify = Fastify() + + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + foo: { type: 'string' } + }, + required: ['foo'], + additionalProperties: false + } + } + } + } + }, + preValidation: async (request) => { + request.headers['content-type'] = undefined + } + }, async () => 'ok') + + await fastify.ready() + + const invalid = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json' + }, + body: { invalid: 'string' } + }) + t.equal(invalid.statusCode, 200) +}) + +test('Schema validation will not be bypass by different content type', async t => { + t.plan(8) + + const fastify = Fastify() + + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + foo: { type: 'string' } + }, + required: ['foo'], + additionalProperties: false + } + } + } + } + } + }, async () => 'ok') + + await fastify.ready() + + const correct1 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json' + }, + body: { foo: 'string' } + }) + t.equal(correct1.statusCode, 200) + + const correct2 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json; charset=utf-8' + }, + body: { foo: 'string' } + }) + t.equal(correct2.statusCode, 200) + + const invalid1 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json ;' + }, + body: { invalid: 'string' } + }) + t.equal(invalid1.statusCode, 400) + t.equal(invalid1.json().code, 'FST_ERR_VALIDATION') + + const invalid2 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn;' + }, + body: { invalid: 'string' } + }) + t.equal(invalid2.statusCode, 400) + t.equal(invalid2.json().code, 'FST_ERR_VALIDATION') + + const invalid3 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn ;' + }, + body: { invalid: 'string' } + }) + t.equal(invalid3.statusCode, 400) + t.equal(invalid3.json().code, 'FST_ERR_VALIDATION') +}) From 644b68d788b4183af312f2d6fea8fec0e6e38151 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 18 Apr 2025 09:39:06 +0200 Subject: [PATCH 0984/1295] Bumped v5.3.1 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 71d3bb1b8dc..3181f017294 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.3.0' +const VERSION = '5.3.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 816943ef89d..6d5a9aa657b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.3.0", + "version": "5.3.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 9b1cf06987c1b00c90e27ed921a2779edd2f609b Mon Sep 17 00:00:00 2001 From: Kenny Null Date: Fri, 18 Apr 2025 11:04:37 +0300 Subject: [PATCH 0985/1295] docs: fix archived concurrently link to point to active repo (#6063) * Replaced old GitHub link (kimmobrunfeldt/concurrently) with the new official repo (open-cli-tools/concurrently) since the original has been archived. --- docs/Guides/Benchmarking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Benchmarking.md b/docs/Guides/Benchmarking.md index 9bdcf9c03ca..2aeac0a2736 100644 --- a/docs/Guides/Benchmarking.md +++ b/docs/Guides/Benchmarking.md @@ -12,7 +12,7 @@ The modules we will use: tool written in node. - [Branch-comparer](https://github.com/StarpTech/branch-comparer): Checkout multiple git branches, execute scripts, and log the results. -- [Concurrently](https://github.com/kimmobrunfeldt/concurrently): Run commands +- [Concurrently](https://github.com/open-cli-tools/concurrently): Run commands concurrently. - [Npx](https://github.com/npm/npx): NPM package runner used to run scripts against different Node.js Versions and execute local binaries. Shipped with From f3d2bcb3963cd570a582e5d39aab01a9ae692fe4 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 18 Apr 2025 22:23:54 +0200 Subject: [PATCH 0986/1295] fix: treat space as a delimiter in content-type parsing (#6064) * fix: treat space as a delimiter in content-type parsing Signed-off-by: Matteo Collina * Update lib/validation.js Co-authored-by: Manuel Spigolon Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Signed-off-by: Matteo Collina Co-authored-by: Manuel Spigolon --- lib/validation.js | 2 +- test/schema-validation.test.js | 46 +++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/lib/validation.js b/lib/validation.js index 3fcb64fa317..047d1c8a812 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -261,7 +261,7 @@ function wrapValidationError (result, dataVar, schemaErrorFormatter) { */ function getEssenceMediaType (header) { if (!header) return '' - return header.split(';', 1)[0].trim().toLowerCase() + return header.split(/[ ;]/, 1)[0].trim().toLowerCase() } module.exports = { diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index c0d93bb307f..8a632925dd4 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -2,6 +2,7 @@ const { test } = require('tap') const Fastify = require('..') +const { request } = require('undici') const AJV = require('ajv') const Schema = require('fluent-json-schema') @@ -1342,7 +1343,7 @@ test('Schema validation when no content type is provided', async t => { }) test('Schema validation will not be bypass by different content type', async t => { - t.plan(8) + t.plan(10) const fastify = Fastify() @@ -1365,58 +1366,73 @@ test('Schema validation will not be bypass by different content type', async t = } }, async () => 'ok') - await fastify.ready() + await fastify.listen({ port: 0 }) + t.teardown(() => fastify.close()) + const address = fastify.listeningOrigin - const correct1 = await fastify.inject({ + const correct1 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'application/json' }, - body: { foo: 'string' } + body: JSON.stringify({ foo: 'string' }) }) t.equal(correct1.statusCode, 200) + await correct1.body.dump() - const correct2 = await fastify.inject({ + const correct2 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'application/json; charset=utf-8' }, - body: { foo: 'string' } + body: JSON.stringify({ foo: 'string' }) }) t.equal(correct2.statusCode, 200) + await correct2.body.dump() - const invalid1 = await fastify.inject({ + const invalid1 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'application/json ;' }, - body: { invalid: 'string' } + body: JSON.stringify({ invalid: 'string' }) }) t.equal(invalid1.statusCode, 400) - t.equal(invalid1.json().code, 'FST_ERR_VALIDATION') + t.equal((await invalid1.body.json()).code, 'FST_ERR_VALIDATION') - const invalid2 = await fastify.inject({ + const invalid2 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'ApPlIcAtIoN/JsOn;' }, - body: { invalid: 'string' } + body: JSON.stringify({ invalid: 'string' }) }) t.equal(invalid2.statusCode, 400) - t.equal(invalid2.json().code, 'FST_ERR_VALIDATION') + t.equal((await invalid2.body.json()).code, 'FST_ERR_VALIDATION') - const invalid3 = await fastify.inject({ + const invalid3 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'ApPlIcAtIoN/JsOn ;' }, - body: { invalid: 'string' } + body: JSON.stringify({ invalid: 'string' }) }) t.equal(invalid3.statusCode, 400) - t.equal(invalid3.json().code, 'FST_ERR_VALIDATION') + t.equal((await invalid3.body.json()).code, 'FST_ERR_VALIDATION') + + const invalid4 = await request(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn foo;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.equal(invalid4.statusCode, 400) + t.equal((await invalid4.body.json()).code, 'FST_ERR_VALIDATION') }) From 32f7e1eb97b638bad3ff8b80948e5b2c07d1b8b5 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 18 Apr 2025 22:27:27 +0200 Subject: [PATCH 0987/1295] Bumped v5.3.2 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 3181f017294..d23e03f01e4 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.3.1' +const VERSION = '5.3.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 6d5a9aa657b..99665f655a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.3.1", + "version": "5.3.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 16a4f8206b2d9f90875308f8acaabb21b6666f4e Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Sun, 20 Apr 2025 03:57:52 -0500 Subject: [PATCH 0988/1295] docs: update Vercel section (#6046) --- docs/Guides/Serverless.md | 97 +++++++++++---------------------------- 1 file changed, 28 insertions(+), 69 deletions(-) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 17e44e5d08e..c675152e104 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -1,20 +1,20 @@

Serverless

Run serverless applications and REST APIs using your existing Fastify -application. By default, Fastify will not work on your serverless platform of -choice, you will need to make some small changes to fix this. This document -contains a small guide for the most popular serverless providers and how to use +application. You may need to make code changes to work on your +serverless platform of choice. This document contains a small guide +for the most popular serverless providers and how to use Fastify with them. #### Should you use Fastify in a serverless platform? -That is up to you! Keep in mind that functions as a service should always use +That is up to you! Keep in mind, functions as a service should always use small and focused functions, but you can also run an entire web application with them. It is important to remember that the bigger the application the slower the initial boot will be. The best way to run Fastify applications in serverless -environments is to use platforms like Google Cloud Run, AWS Fargate, and Azure -Container Instances, where the server can handle multiple requests at the same -time and make full use of Fastify's features. +environments is to use platforms like Google Cloud Run, AWS Fargate, Azure +Container Instances, and Vercel where the server can handle multiple requests +at the same time and make full use of Fastify's features. One of the best features of using Fastify in serverless applications is the ease of development. In your local environment, you will always run the Fastify @@ -136,7 +136,6 @@ of serverless applications to the cloud. [Genezio has a dedicated guide for deploying a Fastify application.](https://genezio.com/docs/frameworks/fastify/) - ## Google Cloud Functions ### Creation of Fastify instance @@ -238,14 +237,13 @@ npx @google-cloud/functions-framework --target=fastifyFunction Or add this command to your `package.json` scripts: ```json "scripts": { -... -"dev": "npx @google-cloud/functions-framework --target=fastifyFunction" -... + ... + "dev": "npx @google-cloud/functions-framework --target=fastifyFunction" + ... } ``` and run it with `npm run dev`. - ### Deploy ```bash gcloud functions deploy fastifyFunction \ @@ -326,7 +324,7 @@ async function registerRoutes (fastify) { }) // define your endpoints here... - fastify.post("/some-route-here", async (request, reply) => {} + fastify.post("/some-route-here", async (request, reply) => {}) fastify.get('/', async (request, reply) => { reply.send({message: 'Hello World!'}) @@ -377,7 +375,6 @@ firebase functions:log - [Fastify on Firebase Functions](https://github.com/lirantal/lemon-squeezy-firebase-webhook-fastify/blob/main/package.json) - [An article about HTTP webhooks on Firebase Functions and Fastify: A Practical Case Study with Lemon Squeezy](https://lirantal.com/blog/http-webhooks-firebase-functions-fastify-practical-case-study-lemon-squeezy) - ## Google Cloud Run Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless @@ -463,7 +460,7 @@ CMD [ "npm", "start" ] To keep build artifacts out of your container (which keeps it small and improves build times) add a `.dockerignore` file like the one below: -```.dockerignore +```dockerignore Dockerfile README.md node_modules @@ -490,12 +487,11 @@ gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed Your app will be accessible from the URL GCP provides. - ## netlify-lambda First, please perform all preparation steps related to **AWS Lambda**. -Create a folder called `functions`, then create `server.js` (and your endpoint +Create a folder called `functions`, then create `server.js` (and your endpoint path will be `server.js`) inside the `functions` folder. ### functions/server.js @@ -564,9 +560,9 @@ Add this command to your `package.json` *scripts* ```json "scripts": { -... -"build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js" -... + ... + "build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js" + ... } ``` @@ -574,54 +570,17 @@ Then it should work fine. ## Vercel -[Vercel](https://vercel.com) provides zero-configuration deployment for Node.js -applications. To use it now, it is as simple as configuring your `vercel.json` -file like the following: - -```json -{ - "rewrites": [ - { - "source": "/(.*)", - "destination": "/api/serverless.js" - } - ] -} -``` - -Then, write `api/serverless.js` like so: - -```js -"use strict"; - -// Read the .env file. -import * as dotenv from "dotenv"; -dotenv.config(); +[Vercel](https://vercel.com) fully supports deploying Fastify applications. +Additionally, with Vercel's +[Fluid compute](https://vercel.com/docs/functions/fluid-compute), you can combine +server-like concurrency with the autoscaling properties of traditional +serverless functions. -// Require the framework -import Fastify from "fastify"; +Get started with the +[Fastify Node.js template on Vercel]( +https://vercel.com/templates/other/fastify-serverless-function). -// Instantiate Fastify with some config -const app = Fastify({ - logger: true, -}); - -// Register your application as a normal plugin. -app.register(import("../src/app.js")); - -export default async (req, res) => { - await app.ready(); - app.server.emit('request', req, res); -} -``` - -In `src/app.js` define the plugin. -```js -async function routes (fastify, options) { - fastify.get('/', async (request, reply) => { - return { hello: 'world' } - }) -} - -export default routes; -``` +[Fluid compute](https://vercel.com/docs/functions/fluid-compute) currently +requires an explicit opt-in. Learn more about enabling Fluid compute +[here]( +https://vercel.com/docs/functions/fluid-compute#how-to-enable-fluid-compute). From 6b1d0d35bb0cd873356d5a2c029090b479f59459 Mon Sep 17 00:00:00 2001 From: Inaiat Henrique Date: Wed, 23 Apr 2025 20:07:28 -0300 Subject: [PATCH 0989/1295] docs(ecosystem): add fastify-papr plugin (#6051) * docs(ecosystem): add fastify-papr plugin * docs(ecosystem): fix lint for fastify-papr plugin --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 76ba7d9ba6a..573363e0888 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -200,6 +200,9 @@ section. to go! A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine on Fastify +- [`@inaiat/fastify-papr`](https://github.com/inaiat/fastify-papr) + A plugin to integrate [Papr](https://github.com/plexinc/papr), + the MongoDB ORM for TypeScript & MongoDB, with Fastify. - [`@jerome1337/fastify-enforce-routes-pattern`](https://github.com/Jerome1337/fastify-enforce-routes-pattern) A Fastify plugin that enforces naming pattern for routes path. - [`@joggr/fastify-prisma`](https://github.com/joggrdocs/fastify-prisma) From e54c087a8dc60368c1672e83a510945d1937c0a8 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 24 Apr 2025 18:24:20 +0200 Subject: [PATCH 0990/1295] test: migrated helper and input validation to node test runner (#6074) --- test/helper.js | 176 ++++++++++++++++++----------- test/input-validation.js | 116 ++++++++++--------- test/options.error-handler.test.js | 2 +- test/options.test.js | 2 +- test/patch.error-handler.test.js | 2 +- test/patch.test.js | 2 +- test/put.error-handler.test.js | 2 +- test/put.test.js | 2 +- 8 files changed, 176 insertions(+), 128 deletions(-) diff --git a/test/helper.js b/test/helper.js index f3aeecc0d43..eb7e7935af2 100644 --- a/test/helper.js +++ b/test/helper.js @@ -5,6 +5,8 @@ const dns = require('node:dns').promises const stream = require('node:stream') const { promisify } = require('node:util') const symbols = require('../lib/symbols') +const { waitForCb } = require('./toolkit') +const assert = require('node:assert') module.exports.sleep = promisify(setTimeout) @@ -19,8 +21,8 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { if (isSetErrorHandler) { fastify.setErrorHandler(function (err, request, reply) { - t.type(request, 'object') - t.type(request, fastify[symbols.kRequest].parent) + assert.ok(request instanceof fastify[symbols.kRequest].parent) + assert.strictEqual(typeof request, 'object') reply .code(err.statusCode) .type('application/json; charset=utf-8') @@ -52,9 +54,9 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { fastify[loMethod]('/', schema, function (req, reply) { reply.code(200).send(req.body) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -64,9 +66,9 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { fastify[loMethod]('/missing', function (req, reply) { reply.code(200).send(req.body) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -77,9 +79,9 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { req.body.hello = req.body.hello + req.query.foo reply.code(200).send(req.body) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -89,21 +91,21 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { fastify[loMethod]('/with-limit', { bodyLimit: 1 }, function (req, reply) { reply.send(req.body) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) fastify.listen({ port: 0 }, function (err) { if (err) { - t.error(err) + t.assert.ifError(err) return } - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) - test(`${upMethod} - correctly replies`, t => { + test(`${upMethod} - correctly replies`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -113,13 +115,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: 'world' }) + testDone() }) }) - test(`${upMethod} - correctly replies with very large body`, t => { + test(`${upMethod} - correctly replies with very large body`, (t, testDone) => { t.plan(3) const largeString = 'world'.repeat(13200) @@ -129,13 +132,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { body: { hello: largeString }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: largeString }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: largeString }) + testDone() }) }) - test(`${upMethod} - correctly replies if the content type has the charset`, t => { + test(`${upMethod} - correctly replies if the content type has the charset`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -145,13 +149,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { 'content-type': 'application/json; charset=utf-8' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body.toString(), JSON.stringify({ hello: 'world' })) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + testDone() }) }) - test(`${upMethod} without schema - correctly replies`, t => { + test(`${upMethod} without schema - correctly replies`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -161,13 +166,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: 'world' }) + testDone() }) }) - test(`${upMethod} with body and querystring - correctly replies`, t => { + test(`${upMethod} with body and querystring - correctly replies`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -177,23 +183,27 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 'worldhello' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: 'worldhello' }) + testDone() }) }) test(`${upMethod} with no body - correctly replies`, t => { t.plan(6) + const { stepIn, patience } = waitForCb({ steps: 2 }) + sget({ method: upMethod, url: 'http://localhost:' + fastify.server.address().port + '/missing', headers: { 'Content-Length': '0' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), '') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), '') + stepIn() }) // Must use inject to make a request without a Content-Length header @@ -201,13 +211,16 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { method: upMethod, url: '/missing' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload.toString(), '') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), '') + stepIn() }) + + return patience }) - test(`${upMethod} returns 415 - incorrect media type if body is not json`, t => { + test(`${upMethod} returns 415 - incorrect media type if body is not json`, (t, testDone) => { t.plan(2) sget({ method: upMethod, @@ -215,13 +228,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { body: 'hello world' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + testDone() }) }) if (loMethod === 'options') { - test('OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text', t => { + test('OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text', (t, testDone) => { t.plan(2) sget({ method: upMethod, @@ -231,8 +245,9 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { 'Content-Type': 'text/xml' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 415) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 415) + testDone() }) }) } @@ -240,6 +255,8 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { test(`${upMethod} returns 400 - Bad Request`, t => { t.plan(4) + const { stepIn, patience } = waitForCb({ steps: 2 }) + sget({ method: upMethod, url: 'http://localhost:' + fastify.server.address().port, @@ -248,8 +265,9 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { 'Content-Type': 'application/json' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + stepIn() }) sget({ @@ -260,13 +278,19 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { 'Content-Length': '0' } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + stepIn() }) + + return patience }) test(`${upMethod} returns 413 - Payload Too Large`, t => { - t.plan(upMethod === 'OPTIONS' ? 4 : 6) + const isOptions = upMethod === 'OPTIONS' + t.plan(isOptions ? 4 : 6) + + const { stepIn, patience } = waitForCb({ steps: isOptions ? 2 : 3 }) sget({ method: upMethod, @@ -276,12 +300,13 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { 'Content-Length': 1024 * 1024 + 1 } }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 413) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 413) + stepIn() }) // Node errors for OPTIONS requests with a stream body and no Content-Length header - if (upMethod !== 'OPTIONS') { + if (!isOptions) { let chunk = Buffer.alloc(1024 * 1024 + 1, 0) const largeStream = new stream.Readable({ read () { @@ -295,8 +320,9 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { headers: { 'Content-Type': 'application/json' }, body: largeStream }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 413) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 413) + stepIn() }) } @@ -307,16 +333,21 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { body: {}, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 413) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 413) + stepIn() }) + + return patience }) test(`${upMethod} should fail with empty body and application/json content-type`, t => { - if (upMethod === 'OPTIONS') return t.end() + if (upMethod === 'OPTIONS') return t.plan(12) + const { stepIn, patience } = waitForCb({ steps: 5 }) + fastify.inject({ method: `${upMethod}`, url: '/', @@ -324,8 +355,8 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { 'Content-Type': 'application/json' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', @@ -340,13 +371,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { 'Content-Type': 'application/json' } }, (err, res, body) => { - t.error(err) - t.same(JSON.parse(body.toString()), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body.toString()), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', statusCode: 400 }) + stepIn() }) fastify.inject({ @@ -357,13 +389,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }, payload: null }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', statusCode: 400 }) + stepIn() }) sget({ @@ -374,13 +407,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }, payload: null }, (err, res, body) => { - t.error(err) - t.same(JSON.parse(body.toString()), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body.toString()), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', statusCode: 400 }) + stepIn() }) fastify.inject({ @@ -391,13 +425,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }, payload: undefined }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', statusCode: 400 }) + stepIn() }) sget({ @@ -408,14 +443,17 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }, payload: undefined }, (err, res, body) => { - t.error(err) - t.same(JSON.parse(body.toString()), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body.toString()), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', statusCode: 400 }) + stepIn() }) + + return patience }) }) } diff --git a/test/input-validation.js b/test/input-validation.js index ce8a40574c2..b8bf94ee4f4 100644 --- a/test/input-validation.js +++ b/test/input-validation.js @@ -4,6 +4,7 @@ const sget = require('simple-get').concat const Ajv = require('ajv') const Joi = require('joi') const yup = require('yup') +const assert = require('node:assert') module.exports.payloadMethod = function (method, t) { const test = t.test @@ -127,28 +128,27 @@ module.exports.payloadMethod = function (method, t) { done() }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) fastify.listen({ port: 0 }, function (err) { - if (err) { - t.error(err) - } + assert.ifError(err) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) - test(`${upMethod} - correctly replies`, t => { + test(`${upMethod} - correctly replies`, (t, testDone) => { if (upMethod === 'HEAD') { t.plan(2) sget({ method: upMethod, url: 'http://localhost:' + fastify.server.address().port }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + testDone() }) } else { t.plan(3) @@ -160,14 +160,15 @@ module.exports.payloadMethod = function (method, t) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 42 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: 42 }) + testDone() }) } }) - test(`${upMethod} - 400 on bad parameters`, t => { + test(`${upMethod} - 400 on bad parameters`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -177,18 +178,19 @@ module.exports.payloadMethod = function (method, t) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(body, { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(body, { error: 'Bad Request', message: 'body/hello must be integer', statusCode: 400, code: 'FST_ERR_VALIDATION' }) + testDone() }) }) - test(`${upMethod} - input-validation coerce`, t => { + test(`${upMethod} - input-validation coerce`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -198,13 +200,14 @@ module.exports.payloadMethod = function (method, t) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 42 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: 42 }) + testDone() }) }) - test(`${upMethod} - input-validation custom schema compiler`, t => { + test(`${upMethod} - input-validation custom schema compiler`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -215,13 +218,14 @@ module.exports.payloadMethod = function (method, t) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 42 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: 42 }) + testDone() }) }) - test(`${upMethod} - input-validation joi schema compiler ok`, t => { + test(`${upMethod} - input-validation joi schema compiler ok`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -231,13 +235,14 @@ module.exports.payloadMethod = function (method, t) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 42 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: '42' }) + testDone() }) }) - test(`${upMethod} - input-validation joi schema compiler ko`, t => { + test(`${upMethod} - input-validation joi schema compiler ko`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -247,18 +252,19 @@ module.exports.payloadMethod = function (method, t) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(body, { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(body, { error: 'Bad Request', message: '"hello" must be a string', statusCode: 400, code: 'FST_ERR_VALIDATION' }) + testDone() }) }) - test(`${upMethod} - input-validation yup schema compiler ok`, t => { + test(`${upMethod} - input-validation yup schema compiler ok`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -268,13 +274,14 @@ module.exports.payloadMethod = function (method, t) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(body, { hello: 42 }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, { hello: '42' }) + testDone() }) }) - test(`${upMethod} - input-validation yup schema compiler ko`, t => { + test(`${upMethod} - input-validation yup schema compiler ko`, (t, testDone) => { t.plan(3) sget({ method: upMethod, @@ -284,52 +291,55 @@ module.exports.payloadMethod = function (method, t) { }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.match(body, { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(body, { error: 'Bad Request', - message: /body hello must be a `string` type, but the final value was: `44`./, + message: 'body hello must be a `string` type, but the final value was: `44`.', statusCode: 400, code: 'FST_ERR_VALIDATION' }) + testDone() }) }) - test(`${upMethod} - input-validation instance custom schema compiler encapsulated`, t => { + test(`${upMethod} - input-validation instance custom schema compiler encapsulated`, (t, testDone) => { t.plan(3) sget({ method: upMethod, url: 'http://localhost:' + fastify.server.address().port + '/plugin', - body: { }, + body: {}, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(body, { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(body, { error: 'Bad Request', message: 'From custom schema compiler!', - statusCode: '400', + statusCode: 400, code: 'FST_ERR_VALIDATION' }) + testDone() }) }) - test(`${upMethod} - input-validation custom schema compiler encapsulated`, t => { + test(`${upMethod} - input-validation custom schema compiler encapsulated`, (t, testDone) => { t.plan(3) sget({ method: upMethod, url: 'http://localhost:' + fastify.server.address().port + '/plugin/custom', - body: { }, + body: {}, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.same(body, { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(body, { error: 'Bad Request', message: 'Always fail!', - statusCode: '400', + statusCode: 400, code: 'FST_ERR_VALIDATION' }) + testDone() }) }) }) diff --git a/test/options.error-handler.test.js b/test/options.error-handler.test.js index efe6ed34c79..5dfb3987316 100644 --- a/test/options.error-handler.test.js +++ b/test/options.error-handler.test.js @@ -1,5 +1,5 @@ 'use strict' -const t = require('tap') +const t = require('node:test') require('./helper').payloadMethod('options', t, true) require('./input-validation').payloadMethod('options', t) diff --git a/test/options.test.js b/test/options.test.js index 4799468d1d3..0f891513acd 100644 --- a/test/options.test.js +++ b/test/options.test.js @@ -1,5 +1,5 @@ 'use strict' -const t = require('tap') +const t = require('node:test') require('./helper').payloadMethod('options', t) require('./input-validation').payloadMethod('options', t) diff --git a/test/patch.error-handler.test.js b/test/patch.error-handler.test.js index 7295bf79d64..fed052cf206 100644 --- a/test/patch.error-handler.test.js +++ b/test/patch.error-handler.test.js @@ -1,5 +1,5 @@ 'use strict' -const t = require('tap') +const t = require('node:test') require('./helper').payloadMethod('patch', t, true) require('./input-validation').payloadMethod('patch', t) diff --git a/test/patch.test.js b/test/patch.test.js index cec8a816cf8..aa61beec39b 100644 --- a/test/patch.test.js +++ b/test/patch.test.js @@ -1,5 +1,5 @@ 'use strict' -const t = require('tap') +const t = require('node:test') require('./helper').payloadMethod('patch', t) require('./input-validation').payloadMethod('patch', t) diff --git a/test/put.error-handler.test.js b/test/put.error-handler.test.js index 010430c64a0..8217a88bf74 100644 --- a/test/put.error-handler.test.js +++ b/test/put.error-handler.test.js @@ -1,5 +1,5 @@ 'use strict' -const t = require('tap') +const t = require('node:test') require('./helper').payloadMethod('put', t, true) require('./input-validation').payloadMethod('put', t) diff --git a/test/put.test.js b/test/put.test.js index c55197a3e39..808d275987f 100644 --- a/test/put.test.js +++ b/test/put.test.js @@ -1,5 +1,5 @@ 'use strict' -const t = require('tap') +const t = require('node:test') require('./helper').payloadMethod('put', t) require('./input-validation').payloadMethod('put', t) From 22817e4ed63860ee5397c63b9b611908b3ace47c Mon Sep 17 00:00:00 2001 From: Eugenio Ceschia Date: Thu, 24 Apr 2025 18:25:34 +0200 Subject: [PATCH 0991/1295] chore: no-comma-dangle lint rule (#6069) --- eslint.config.js | 26 +++++++++++++++++--------- lib/decorate.js | 4 ++-- lib/logger-factory.js | 2 +- lib/logger-pino.js | 4 ++-- lib/reply.js | 4 ++-- lib/request.js | 2 +- test/issue-4959.test.js | 4 ++-- test/types/plugin.test-d.ts | 2 +- test/types/register.test-d.ts | 2 +- test/use-semicolon-delimiter.test.js | 2 +- 10 files changed, 30 insertions(+), 22 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index beac860e1e3..cc4570e2960 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,11 +1,19 @@ 'use strict' +const neostandard = require('neostandard') -module.exports = require('neostandard')({ - ignores: [ - 'lib/configValidator.js', - 'lib/error-serializer.js', - 'test/same-shape.test.js', - 'test/types/import.js' - ], - ts: true -}) +module.exports = [ + ...neostandard({ + ignores: [ + 'lib/configValidator.js', + 'lib/error-serializer.js', + 'test/same-shape.test.js', + 'test/types/import.js' + ], + ts: true + }), + { + rules: { + 'comma-dangle': ['error', 'never'] + } + } +] diff --git a/lib/decorate.js b/lib/decorate.js index 7494589d691..12073bb31c0 100644 --- a/lib/decorate.js +++ b/lib/decorate.js @@ -4,7 +4,7 @@ const { kReply, kRequest, kState, - kHasBeenDecorated, + kHasBeenDecorated } = require('./symbols.js') const { @@ -13,7 +13,7 @@ const { FST_ERR_DEC_AFTER_START, FST_ERR_DEC_REFERENCE_TYPE, FST_ERR_DEC_DEPENDENCY_INVALID_TYPE, - FST_ERR_DEC_UNDECLARED, + FST_ERR_DEC_UNDECLARED } = require('./errors') function decorate (instance, name, fn, dependencies) { diff --git a/lib/logger-factory.js b/lib/logger-factory.js index 0592275c0c9..69592d7a077 100644 --- a/lib/logger-factory.js +++ b/lib/logger-factory.js @@ -132,5 +132,5 @@ module.exports = { defaultChildLoggerFactory, createLogger, validateLogger, - now, + now } diff --git a/lib/logger-pino.js b/lib/logger-pino.js index d5e662b2eea..19e92d22fdd 100644 --- a/lib/logger-pino.js +++ b/lib/logger-pino.js @@ -9,7 +9,7 @@ const pino = require('pino') const { serializersSym } = pino.symbols const { - FST_ERR_LOG_INVALID_DESTINATION, + FST_ERR_LOG_INVALID_DESTINATION } = require('./errors') function createPinoLogger (opts) { @@ -64,5 +64,5 @@ const serializers = { module.exports = { serializers, - createPinoLogger, + createPinoLogger } diff --git a/lib/reply.js b/lib/reply.js index d4a925e402d..bdee9e2629c 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -22,7 +22,7 @@ const { kReplyCacheSerializeFns, kSchemaController, kOptions, - kRouteContext, + kRouteContext } = require('./symbols.js') const { onSendHookRunner, @@ -53,7 +53,7 @@ const { FST_ERR_BAD_TRAILER_VALUE, FST_ERR_MISSING_SERIALIZATION_FN, FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN, - FST_ERR_DEC_UNDECLARED, + FST_ERR_DEC_UNDECLARED } = require('./errors') const decorators = require('./decorate') diff --git a/lib/request.js b/lib/request.js index f68f97c78f6..a0bf5dc8325 100644 --- a/lib/request.js +++ b/lib/request.js @@ -11,7 +11,7 @@ const { kOptions, kRequestCacheValidateFns, kRouteContext, - kRequestOriginalUrl, + kRequestOriginalUrl } = require('./symbols') const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors') const decorators = require('./decorate') diff --git a/test/issue-4959.test.js b/test/issue-4959.test.js index 1ce50f50d25..be297375aa9 100644 --- a/test/issue-4959.test.js +++ b/test/issue-4959.test.js @@ -17,7 +17,7 @@ function runBadClientCall (reqOptions, payload) { ...reqOptions, headers: { 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(postData), + 'Content-Length': Buffer.byteLength(postData) } }, () => { innerReject(new Error('Request should have failed')) @@ -78,7 +78,7 @@ test('should handle a soket error', async (t) => { hostname: 'localhost', port: fastify.server.address().port, path: '/', - method: 'PUT', + method: 'PUT' }, { test: 'me' }) t.assert.equal(err.code, 'ECONNRESET') }) diff --git a/test/types/plugin.test-d.ts b/test/types/plugin.test-d.ts index b1680151ef4..30358ff50ab 100644 --- a/test/types/plugin.test-d.ts +++ b/test/types/plugin.test-d.ts @@ -12,7 +12,7 @@ interface TestOptions extends FastifyPluginOptions { } const testOptions: TestOptions = { option1: 'a', - option2: false, + option2: false } const testPluginOpts: FastifyPluginCallback = function (instance, opts, done) { expectType(opts) diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index 82f13fa1f1a..3822763bfac 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -50,7 +50,7 @@ const testPluginWithHttp2WithType = (instance: ServerWithHttp2, opts: FastifyPlu const testPluginWithHttp2WithTypeAsync = async (instance: ServerWithHttp2, opts: FastifyPluginOptions) => { } const testOptions: TestOptions = { option1: 'a', - option2: false, + option2: false } expectAssignable(serverWithHttp2.register(testPluginCallback)) expectAssignable(serverWithHttp2.register(testPluginAsync)) diff --git a/test/use-semicolon-delimiter.test.js b/test/use-semicolon-delimiter.test.js index 3d44542c426..d3e37fb3c75 100644 --- a/test/use-semicolon-delimiter.test.js +++ b/test/use-semicolon-delimiter.test.js @@ -13,7 +13,7 @@ test('use semicolon delimiter default false', (t, done) => { reply.send(req.query) }) - fastify.listen({ port: 0, }, err => { + fastify.listen({ port: 0 }, err => { t.assert.ifError(err) sget({ method: 'GET', From 78be004361ce2b1d8a4bee70ede27334bc045b27 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 24 Apr 2025 18:28:39 +0200 Subject: [PATCH 0992/1295] test: migrate stream tests to node test runner (#6065) --- test/stream.1.test.js | 57 +++++++++++++++++++----------------- test/stream.2.test.js | 30 ++++++++++++------- test/stream.3.test.js | 68 +++++++++++++++++++++++-------------------- 3 files changed, 87 insertions(+), 68 deletions(-) diff --git a/test/stream.1.test.js b/test/stream.1.test.js index 95fedab6f5a..1efb8f61185 100644 --- a/test/stream.1.test.js +++ b/test/stream.1.test.js @@ -1,12 +1,11 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fs = require('node:fs') const Fastify = require('../fastify') -test('should respond with a stream', t => { +test('should respond with a stream', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -16,23 +15,24 @@ test('should respond with a stream', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget(`http://localhost:${fastify.server.address().port}`, function (err, response, data) { - t.error(err) - t.equal(response.headers['content-type'], undefined) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], undefined) + t.assert.strictEqual(response.statusCode, 200) fs.readFile(__filename, (err, expected) => { - t.error(err) - t.equal(expected.toString(), data.toString()) + t.assert.ifError(err) + t.assert.strictEqual(expected.toString(), data.toString()) + testDone() }) }) }) }) -test('should respond with a stream (error)', t => { +test('should respond with a stream (error)', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -42,17 +42,18 @@ test('should respond with a stream (error)', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + testDone() }) }) }) -test('should trigger the onSend hook', t => { +test('should trigger the onSend hook', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -61,7 +62,7 @@ test('should trigger the onSend hook', t => { }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.ok(payload._readableState) + t.assert.ok(payload._readableState) reply.header('Content-Type', 'application/javascript') done() }) @@ -69,14 +70,15 @@ test('should trigger the onSend hook', t => { fastify.inject({ url: '/' }, (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'application/javascript') - t.equal(res.payload, fs.readFileSync(__filename, 'utf8')) + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'application/javascript') + t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8')) fastify.close() + testDone() }) }) -test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', t => { +test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -87,22 +89,23 @@ test('should trigger the onSend hook only twice if pumping the stream fails, fir let counter = 0 fastify.addHook('onSend', (req, reply, payload, done) => { if (counter === 0) { - t.ok(payload._readableState) + t.assert.ok(payload._readableState) } else if (counter === 1) { const error = JSON.parse(payload) - t.equal(error.statusCode, 500) + t.assert.strictEqual(error.statusCode, 500) } counter++ done() }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget(`http://localhost:${fastify.server.address().port}`, function (err, response) { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + testDone() }) }) }) diff --git a/test/stream.2.test.js b/test/stream.2.test.js index 12f56f59a15..7132e78b28f 100644 --- a/test/stream.2.test.js +++ b/test/stream.2.test.js @@ -1,13 +1,13 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const proxyquire = require('proxyquire') const fs = require('node:fs') const resolve = require('node:path').resolve const zlib = require('node:zlib') const pipeline = require('node:stream').pipeline const Fastify = require('..') +const { waitForCb } = require('./toolkit') test('onSend hook stream', t => { t.plan(4) @@ -17,6 +17,8 @@ test('onSend hook stream', t => { reply.send({ hello: 'world' }) }) + const { stepIn, patience } = waitForCb({ steps: 2 }) + fastify.addHook('onSend', (req, reply, payload, done) => { const gzStream = zlib.createGzip() @@ -24,7 +26,10 @@ test('onSend hook stream', t => { pipeline( fs.createReadStream(resolve(__filename), 'utf8'), gzStream, - t.error + (err) => { + t.assert.ifError(err) + stepIn() + } ) done(null, gzStream) }) @@ -33,16 +38,19 @@ test('onSend hook stream', t => { url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.headers['content-encoding'], 'gzip') + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-encoding'], 'gzip') const file = fs.readFileSync(resolve(__filename), 'utf8') const payload = zlib.gunzipSync(res.rawPayload) - t.equal(payload.toString('utf-8'), file) + t.assert.strictEqual(payload.toString('utf-8'), file) fastify.close() + stepIn() }) + + return patience }) -test('onSend hook stream should work even if payload is not a proper stream', t => { +test('onSend hook stream should work even if payload is not a proper stream', (t, testDone) => { t.plan(1) const reply = proxyquire('../lib/reply', { @@ -59,8 +67,9 @@ test('onSend hook stream should work even if payload is not a proper stream', t fatal: () => { }, error: () => { }, warn: (message) => { - t.equal(message, 'stream payload does not end properly') + t.assert.strictEqual(message, 'stream payload does not end properly') fastify.close() + testDone() }, info: () => { }, debug: () => { }, @@ -83,7 +92,7 @@ test('onSend hook stream should work even if payload is not a proper stream', t }) }) -test('onSend hook stream should work on payload with "close" ending function', t => { +test('onSend hook stream should work on payload with "close" ending function', (t, testDone) => { t.plan(1) const reply = proxyquire('../lib/reply', { @@ -106,7 +115,8 @@ test('onSend hook stream should work on payload with "close" ending function', t pipe: () => { }, close: (cb) => { cb() - t.pass() + t.assert.ok('close callback called') + testDone() } } done(null, fakeStream) diff --git a/test/stream.3.test.js b/test/stream.3.test.js index 44cb666263d..5eb0148148b 100644 --- a/test/stream.3.test.js +++ b/test/stream.3.test.js @@ -1,11 +1,10 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const split = require('split2') const Fastify = require('..') -test('Destroying streams prematurely', t => { +test('Destroying streams prematurely', (t, testDone) => { t.plan(6) let fastify = null @@ -18,7 +17,7 @@ test('Destroying streams prematurely', t => { } }) } catch (e) { - t.fail() + t.assert.fail() } const stream = require('node:stream') const http = require('node:http') @@ -26,13 +25,14 @@ test('Destroying streams prematurely', t => { // Test that "premature close" errors are logged with level warn logStream.on('data', line => { if (line.res) { - t.equal(line.msg, 'stream closed prematurely') - t.equal(line.level, 30) + t.assert.strictEqual(line.msg, 'stream closed prematurely') + t.assert.strictEqual(line.level, 30) + testDone() } }) fastify.get('/', function (request, reply) { - t.pass('Received request') + t.assert.ok('Received request') let sent = false const reallyLongStream = new stream.Readable({ @@ -48,26 +48,26 @@ test('Destroying streams prematurely', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) const port = fastify.server.address().port http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) response.on('readable', function () { response.destroy() }) // Node bug? Node never emits 'close' here. response.on('aborted', function () { - t.pass('Response closed') + t.assert.ok('Response closed') }) }) }) }) -test('Destroying streams prematurely should call close method', t => { +test('Destroying streams prematurely should call close method', (t, testDone) => { t.plan(7) let fastify = null @@ -80,7 +80,7 @@ test('Destroying streams prematurely should call close method', t => { } }) } catch (e) { - t.fail() + t.assert.fail() } const stream = require('node:stream') const http = require('node:http') @@ -88,13 +88,13 @@ test('Destroying streams prematurely should call close method', t => { // Test that "premature close" errors are logged with level warn logStream.on('data', line => { if (line.res) { - t.equal(line.msg, 'stream closed prematurely') - t.equal(line.level, 30) + t.assert.strictEqual(line.msg, 'stream closed prematurely') + t.assert.strictEqual(line.level, 30) } }) fastify.get('/', function (request, reply) { - t.pass('Received request') + t.assert.ok('Received request') let sent = false const reallyLongStream = new stream.Readable({ @@ -106,30 +106,33 @@ test('Destroying streams prematurely should call close method', t => { } }) reallyLongStream.destroy = undefined - reallyLongStream.close = () => t.ok('called') + reallyLongStream.close = () => { + t.assert.ok('called') + testDone() + } reply.send(reallyLongStream) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) const port = fastify.server.address().port http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) response.on('readable', function () { response.destroy() }) // Node bug? Node never emits 'close' here. response.on('aborted', function () { - t.pass('Response closed') + t.assert.ok('Response closed') }) }) }) }) -test('Destroying streams prematurely should call close method when destroy is not a function', t => { +test('Destroying streams prematurely should call close method when destroy is not a function', (t, testDone) => { t.plan(7) let fastify = null @@ -142,7 +145,7 @@ test('Destroying streams prematurely should call close method when destroy is no } }) } catch (e) { - t.fail() + t.assert.fail() } const stream = require('node:stream') const http = require('node:http') @@ -150,13 +153,13 @@ test('Destroying streams prematurely should call close method when destroy is no // Test that "premature close" errors are logged with level warn logStream.on('data', line => { if (line.res) { - t.equal(line.msg, 'stream closed prematurely') - t.equal(line.level, 30) + t.assert.strictEqual(line.msg, 'stream closed prematurely') + t.assert.strictEqual(line.level, 30) } }) fastify.get('/', function (request, reply) { - t.pass('Received request') + t.assert.ok('Received request') let sent = false const reallyLongStream = new stream.Readable({ @@ -168,24 +171,27 @@ test('Destroying streams prematurely should call close method when destroy is no } }) reallyLongStream.destroy = true - reallyLongStream.close = () => t.ok('called') + reallyLongStream.close = () => { + t.assert.ok('called') + testDone() + } reply.send(reallyLongStream) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) const port = fastify.server.address().port http.get(`http://localhost:${port}`, function (response) { - t.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) response.on('readable', function () { response.destroy() }) // Node bug? Node never emits 'close' here. response.on('aborted', function () { - t.pass('Response closed') + t.assert.ok('Response closed') }) }) }) From 32d5a7405060c55a38c58217a145e77b50a63d82 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 24 Apr 2025 18:29:54 +0200 Subject: [PATCH 0993/1295] test: logger response (#6055) --- test/logger/response.test.js | 39 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/test/logger/response.test.js b/test/logger/response.test.js index 46adeec8486..227f5883fa3 100644 --- a/test/logger/response.test.js +++ b/test/logger/response.test.js @@ -2,19 +2,18 @@ const stream = require('node:stream') -const t = require('tap') +const t = require('node:test') const split = require('split2') const pino = require('pino') const Fastify = require('../../fastify') +const { partialDeepStrictEqual } = require('../toolkit') const { on } = stream -t.test('response serialization', (t) => { - t.setTimeout(60000) - +t.test('response serialization', { timeout: 60000 }, async (t) => { t.plan(4) - t.test('Should use serializers from plugin and route', async (t) => { + await t.test('Should use serializers from plugin and route', async (t) => { const lines = [ { msg: 'incoming request' }, { test: 'XHello', test2: 'ZHello' }, @@ -28,7 +27,7 @@ t.test('response serialization', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(context1, { logSerializers: { test: value => 'X' + value } @@ -51,16 +50,16 @@ t.test('response serialization', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepStrictEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('Should use serializers from instance fastify and route', async (t) => { + await t.test('Should use serializers from instance fastify and route', async (t) => { const lines = [ { msg: 'incoming request' }, { test: 'XHello', test2: 'ZHello' }, @@ -80,7 +79,7 @@ t.test('response serialization', (t) => { const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', { logSerializers: { @@ -96,16 +95,16 @@ t.test('response serialization', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepStrictEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('Should use serializers inherit from contexts', async (t) => { + await t.test('Should use serializers inherit from contexts', async (t) => { const lines = [ { msg: 'incoming request' }, { test: 'XHello', test2: 'YHello', test3: 'ZHello' }, @@ -123,7 +122,7 @@ t.test('response serialization', (t) => { }, stream) const fastify = Fastify({ loggerInstance }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } }) @@ -144,16 +143,16 @@ t.test('response serialization', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/' }) const body = await response.json() - t.same(body, { hello: 'world' }) + t.assert.deepStrictEqual(body, { hello: 'world' }) } for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) - t.test('should serialize request and response', async (t) => { + await t.test('should serialize request and response', async (t) => { const lines = [ { req: { method: 'GET', url: '/500' }, msg: 'incoming request' }, { req: { method: 'GET', url: '/500' }, msg: '500 error' }, @@ -163,7 +162,7 @@ t.test('response serialization', (t) => { const stream = split(JSON.parse) const fastify = Fastify({ logger: { level: 'info', stream } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/500', (req, reply) => { reply.code(500).send(Error('500 error')) @@ -173,11 +172,11 @@ t.test('response serialization', (t) => { { const response = await fastify.inject({ method: 'GET', url: '/500' }) - t.equal(response.statusCode, 500) + t.assert.strictEqual(response.statusCode, 500) } for await (const [line] of on(stream, 'data')) { - t.match(line, lines.shift()) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) if (lines.length === 0) break } }) From a422ff82592d3cadd7630f7b999dcd5a2c246c07 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 24 Apr 2025 18:31:33 +0200 Subject: [PATCH 0994/1295] test: migrate schema feature to node test runner (#6066) --- test/schema-feature.test.js | 547 ++++++++++++++++++++---------------- 1 file changed, 309 insertions(+), 238 deletions(-) diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index e526c37b0b2..c999bd41459 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -1,12 +1,13 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') const fp = require('fastify-plugin') const deepClone = require('rfdc')({ circles: true, proto: false }) const Ajv = require('ajv') const { kSchemaController } = require('../lib/symbols.js') const { FSTWRN001 } = require('../lib/warnings') +const { waitForCb } = require('./toolkit') const echoParams = (req, reply) => { reply.send(req.params) } const echoBody = (req, reply) => { reply.send(req.body) } @@ -15,22 +16,23 @@ const echoBody = (req, reply) => { reply.send(req.body) } test(`Should expose ${f} function`, t => { t.plan(1) const fastify = Fastify() - t.equal(typeof fastify[f], 'function') + t.assert.strictEqual(typeof fastify[f], 'function') }) }) ;['setValidatorCompiler', 'setSerializerCompiler'].forEach(f => { - test(`cannot call ${f} after binding`, t => { + test(`cannot call ${f} after binding`, (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) try { fastify[f](() => { }) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + t.assert.ok(true) + testDone() } }) }) @@ -41,7 +43,7 @@ test('The schemas should be added to an internal storage', t => { const fastify = Fastify() const schema = { $id: 'id', my: 'schema' } fastify.addSchema(schema) - t.same(fastify[kSchemaController].schemaBucket.store, { id: schema }) + t.assert.deepStrictEqual(fastify[kSchemaController].schemaBucket.store, { id: schema }) }) test('The schemas should be accessible via getSchemas', t => { @@ -55,10 +57,10 @@ test('The schemas should be accessible via getSchemas', t => { } Object.values(schemas).forEach(schema => { fastify.addSchema(schema) }) - t.same(fastify.getSchemas(), schemas) + t.assert.deepStrictEqual(fastify.getSchemas(), schemas) }) -test('The schema should be accessible by id via getSchema', t => { +test('The schema should be accessible by id via getSchema', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -68,41 +70,50 @@ test('The schema should be accessible by id via getSchema', t => { { $id: 'bcd', my: 'schema', properties: { a: 'a', b: 1 } } ] schemas.forEach(schema => { fastify.addSchema(schema) }) - t.same(fastify.getSchema('abc'), schemas[1]) - t.same(fastify.getSchema('id'), schemas[0]) - t.same(fastify.getSchema('foo'), undefined) + t.assert.deepStrictEqual(fastify.getSchema('abc'), schemas[1]) + t.assert.deepStrictEqual(fastify.getSchema('id'), schemas[0]) + t.assert.deepStrictEqual(fastify.getSchema('foo'), undefined) fastify.register((instance, opts, done) => { const pluginSchema = { $id: 'cde', my: 'schema' } instance.addSchema(pluginSchema) - t.same(instance.getSchema('cde'), pluginSchema) + t.assert.deepStrictEqual(instance.getSchema('cde'), pluginSchema) done() }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('Get validatorCompiler after setValidatorCompiler', t => { +test('Get validatorCompiler after setValidatorCompiler', (t, testDone) => { t.plan(2) const myCompiler = () => { } const fastify = Fastify() fastify.setValidatorCompiler(myCompiler) const sc = fastify.validatorCompiler - t.ok(Object.is(myCompiler, sc)) - fastify.ready(err => t.error(err)) + t.assert.ok(Object.is(myCompiler, sc)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('Get serializerCompiler after setSerializerCompiler', t => { +test('Get serializerCompiler after setSerializerCompiler', (t, testDone) => { t.plan(2) const myCompiler = () => { } const fastify = Fastify() fastify.setSerializerCompiler(myCompiler) const sc = fastify.serializerCompiler - t.ok(Object.is(myCompiler, sc)) - fastify.ready(err => t.error(err)) + t.assert.ok(Object.is(myCompiler, sc)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('Get compilers is empty when settle on routes', t => { +test('Get compilers is empty when settle on routes', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -130,9 +141,10 @@ test('Get compilers is empty when settle on routes', t => { payload: {}, url: '/' }, (err, res) => { - t.error(err) - t.equal(fastify.validatorCompiler, undefined) - t.equal(fastify.serializerCompiler, undefined) + t.assert.ifError(err) + t.assert.strictEqual(fastify.validatorCompiler, undefined) + t.assert.strictEqual(fastify.serializerCompiler, undefined) + testDone() }) }) @@ -141,9 +153,9 @@ test('Should throw if the $id property is missing', t => { const fastify = Fastify() try { fastify.addSchema({ type: 'string' }) - t.fail() + t.assert.fail() } catch (err) { - t.equal(err.code, 'FST_ERR_SCH_MISSING_ID') + t.assert.strictEqual(err.code, 'FST_ERR_SCH_MISSING_ID') } }) @@ -155,12 +167,12 @@ test('Cannot add multiple times the same id', t => { try { fastify.addSchema({ $id: 'id' }) } catch (err) { - t.equal(err.code, 'FST_ERR_SCH_ALREADY_PRESENT') - t.equal(err.message, 'Schema with id \'id\' already declared!') + t.assert.strictEqual(err.code, 'FST_ERR_SCH_ALREADY_PRESENT') + t.assert.strictEqual(err.message, 'Schema with id \'id\' already declared!') } }) -test('Cannot add schema for query and querystring', t => { +test('Cannot add schema for query and querystring', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -183,12 +195,13 @@ test('Cannot add schema for query and querystring', t => { }) fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_DUPLICATE') - t.equal(err.message, 'Schema with \'querystring\' already present!') + t.assert.strictEqual(err.code, 'FST_ERR_SCH_DUPLICATE') + t.assert.strictEqual(err.message, 'Schema with \'querystring\' already present!') + testDone() }) }) -test('Should throw of the schema does not exists in input', t => { +test('Should throw of the schema does not exists in input', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -205,12 +218,13 @@ test('Should throw of the schema does not exists in input', t => { }) fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.equal(err.message, "Failed building the validation schema for GET: /:id, due to error can't resolve reference #notExist from id #") + t.assert.strictEqual(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.assert.strictEqual(err.message, "Failed building the validation schema for GET: /:id, due to error can't resolve reference #notExist from id #") + testDone() }) }) -test('Should throw if schema is missing for content type', t => { +test('Should throw if schema is missing for content type', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -226,12 +240,13 @@ test('Should throw if schema is missing for content type', t => { }) fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') - t.equal(err.message, "Schema is missing for the content type 'application/json'") + t.assert.strictEqual(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + t.assert.strictEqual(err.message, "Schema is missing for the content type 'application/json'") + testDone() }) }) -test('Should throw of the schema does not exists in output', t => { +test('Should throw of the schema does not exists in output', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -250,12 +265,13 @@ test('Should throw of the schema does not exists in output', t => { }) fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') - t.match(err.message, /^Failed building the serialization schema for GET: \/:id, due to error Cannot find reference.*/) // error from fast-json-stringify + t.assert.strictEqual(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + t.assert.match(err.message, /^Failed building the serialization schema for GET: \/:id, due to error Cannot find reference.*/) // error from fast-json-stringify + testDone() }) }) -test('Should not change the input schemas', t => { +test('Should not change the input schemas', (t, testDone) => { t.plan(4) const theSchema = { @@ -294,24 +310,25 @@ test('Should not change the input schemas', t => { method: 'POST', payload: { name: 'Foo', surname: 'Bar' } }, (err, res) => { - t.error(err) - t.same(res.json(), { name: 'Foo' }) - t.ok(theSchema.$id, 'the $id is not removed') - t.same(fastify.getSchema('helloSchema'), theSchema) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { name: 'Foo' }) + t.assert.ok(theSchema.$id, 'the $id is not removed') + t.assert.deepStrictEqual(fastify.getSchema('helloSchema'), theSchema) + testDone() }) }) -test('Should emit warning if the schema headers is undefined', t => { +test('Should emit warning if the schema headers is undefined', (t, testDone) => { t.plan(4) const fastify = Fastify() process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, FSTWRN001.code) + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) } - t.teardown(() => { + t.after(() => { process.removeListener('warning', onWarning) FSTWRN001.emitted = false }) @@ -327,22 +344,23 @@ test('Should emit warning if the schema headers is undefined', t => { method: 'POST', url: '/123' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Should emit warning if the schema body is undefined', t => { +test('Should emit warning if the schema body is undefined', (t, testDone) => { t.plan(4) const fastify = Fastify() process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, FSTWRN001.code) + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) } - t.teardown(() => { + t.after(() => { process.removeListener('warning', onWarning) FSTWRN001.emitted = false }) @@ -358,22 +376,23 @@ test('Should emit warning if the schema body is undefined', t => { method: 'POST', url: '/123' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Should emit warning if the schema query is undefined', t => { +test('Should emit warning if the schema query is undefined', (t, testDone) => { t.plan(4) const fastify = Fastify() process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, FSTWRN001.code) + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) } - t.teardown(() => { + t.after(() => { process.removeListener('warning', onWarning) FSTWRN001.emitted = false }) @@ -389,22 +408,23 @@ test('Should emit warning if the schema query is undefined', t => { method: 'POST', url: '/123' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Should emit warning if the schema params is undefined', t => { +test('Should emit warning if the schema params is undefined', (t, testDone) => { t.plan(4) const fastify = Fastify() process.on('warning', onWarning) function onWarning (warning) { - t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, FSTWRN001.code) + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) } - t.teardown(() => { + t.after(() => { process.removeListener('warning', onWarning) FSTWRN001.emitted = false }) @@ -420,12 +440,13 @@ test('Should emit warning if the schema params is undefined', t => { method: 'POST', url: '/123' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Should emit a warning for every route with undefined schema', t => { +test('Should emit a warning for every route with undefined schema', (t, testDone) => { t.plan(16) const fastify = Fastify() @@ -436,13 +457,13 @@ test('Should emit a warning for every route with undefined schema', t => { // - 2 - GET and HEAD for /undefinedBody/:id // => 3 x 4 assertions = 12 assertions function onWarning (warning) { - t.equal(warning.name, 'FastifyWarning') - t.equal(warning.code, FSTWRN001.code) - t.equal(runs++, expectedWarningEmitted.shift()) + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) + t.assert.strictEqual(runs++, expectedWarningEmitted.shift()) } process.on('warning', onWarning) - t.teardown(() => { + t.after(() => { process.removeListener('warning', onWarning) FSTWRN001.emitted = false }) @@ -465,20 +486,21 @@ test('Should emit a warning for every route with undefined schema', t => { method: 'GET', url: '/undefinedParams/123' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject({ method: 'GET', url: '/undefinedBody/123' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('First level $ref', t => { +test('First level $ref', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -506,8 +528,9 @@ test('First level $ref', t => { method: 'GET', url: '/123' }, (err, res) => { - t.error(err) - t.same(res.json(), { id: 246 }) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { id: 246 }) + testDone() }) }) @@ -516,38 +539,38 @@ test('Customize validator compiler in instance and route', t => { const fastify = Fastify({ exposeHeadRoutes: false }) fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { - t.equal(method, 'POST') // run 4 times - t.equal(url, '/:id') // run 4 times + t.assert.strictEqual(method, 'POST') // run 4 times + t.assert.strictEqual(url, '/:id') // run 4 times switch (httpPart) { case 'body': - t.pass('body evaluated') + t.assert.ok('body evaluated') return body => { - t.same(body, { foo: ['bar', 'BAR'] }) + t.assert.deepStrictEqual(body, { foo: ['bar', 'BAR'] }) return true } case 'params': - t.pass('params evaluated') + t.assert.ok('params evaluated') return params => { - t.same(params, { id: 1234 }) + t.assert.strictEqual(params.id, '1234') return true } case 'querystring': - t.pass('querystring evaluated') + t.assert.ok('querystring evaluated') return query => { - t.same(query, { lang: 'en' }) + t.assert.strictEqual(query.lang, 'en') return true } case 'headers': - t.pass('headers evaluated') + t.assert.ok('headers evaluated') return headers => { - t.match(headers, { x: 'hello' }) + t.assert.strictEqual(headers.x, 'hello') return true } case '2xx': - t.fail('the validator doesn\'t process the response') + t.assert.fail('the validator doesn\'t process the response') break default: - t.fail(`unknown httpPart ${httpPart}`) + t.assert.fail(`unknown httpPart ${httpPart}`) } }) @@ -592,8 +615,8 @@ test('Customize validator compiler in instance and route', t => { fastify.get('/wow/:id', { handler: echoParams, validatorCompiler: ({ schema, method, url, httpPart }) => { - t.equal(method, 'GET') // run 3 times (params, headers, query) - t.equal(url, '/wow/:id') // run 4 times + t.assert.strictEqual(method, 'GET') // run 3 times (params, headers, query) + t.assert.strictEqual(url, '/wow/:id') // run 4 times return () => { return true } // ignore the validation }, schema: { @@ -626,6 +649,8 @@ test('Customize validator compiler in instance and route', t => { } }) + const { stepIn, patience } = waitForCb({ steps: 2 }) + fastify.inject({ url: '/1234', method: 'POST', @@ -633,9 +658,10 @@ test('Customize validator compiler in instance and route', t => { query: { lang: 'en' }, payload: { foo: ['bar', 'BAR'] } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { foo: ['bar', 'BAR'] }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: ['bar', 'BAR'] }) + stepIn() }) fastify.inject({ @@ -644,13 +670,16 @@ test('Customize validator compiler in instance and route', t => { headers: { x: 'hello' }, query: { lang: 'jp' } // not in the enum }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) // the validation is always true - t.same(res.json(), {}) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) // the validation is always true + t.assert.deepStrictEqual(res.json(), {}) + stepIn() }) + + return patience }) -test('Use the same schema across multiple routes', t => { +test('Use the same schema across multiple routes', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -694,20 +723,21 @@ test('Use the same schema across multiple routes', t => { method: 'GET', url: '/first/123' }, (err, res) => { - t.error(err) - t.equal(res.payload, 'number') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'number') }) fastify.inject({ method: 'GET', url: '/second/123' }, (err, res) => { - t.error(err) - t.equal(res.payload, 'number') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'number') + testDone() }) }) -test('Encapsulation should intervene', t => { +test('Encapsulation should intervene', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -738,12 +768,13 @@ test('Encapsulation should intervene', t => { }) fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') - t.equal(err.message, "Failed building the validation schema for GET: /:id, due to error can't resolve reference encapsulation#/properties/id from id #") + t.assert.strictEqual(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.assert.strictEqual(err.message, "Failed building the validation schema for GET: /:id, due to error can't resolve reference encapsulation#/properties/id from id #") + testDone() }) }) -test('Encapsulation isolation', t => { +test('Encapsulation isolation', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -757,10 +788,13 @@ test('Encapsulation isolation', t => { done() }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('Add schema after register', t => { +test('Add schema after register', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -784,8 +818,8 @@ test('Add schema after register', t => { try { instance.addSchema({ $id: 'test' }) } catch (err) { - t.equal(err.code, 'FST_ERR_SCH_ALREADY_PRESENT') - t.equal(err.message, 'Schema with id \'test\' already declared!') + t.assert.strictEqual(err.code, 'FST_ERR_SCH_ALREADY_PRESENT') + t.assert.strictEqual(err.message, 'Schema with id \'test\' already declared!') } done() }) @@ -794,13 +828,14 @@ test('Add schema after register', t => { method: 'GET', url: '/4242' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { id: 4242 }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { id: 4242 }) + testDone() }) }) -test('Encapsulation isolation for getSchemas', t => { +test('Encapsulation isolation for getSchemas', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -835,15 +870,16 @@ test('Encapsulation isolation for getSchemas', t => { }) fastify.ready(err => { - t.error(err) - t.same(fastify.getSchemas(), { z: schemas.z }) - t.same(pluginDeepOneSide.getSchemas(), { z: schemas.z, a: schemas.a }) - t.same(pluginDeepOne.getSchemas(), { z: schemas.z, b: schemas.b }) - t.same(pluginDeepTwo.getSchemas(), { z: schemas.z, b: schemas.b, c: schemas.c }) + t.assert.ifError(err) + t.assert.deepStrictEqual(fastify.getSchemas(), { z: schemas.z }) + t.assert.deepStrictEqual(pluginDeepOneSide.getSchemas(), { z: schemas.z, a: schemas.a }) + t.assert.deepStrictEqual(pluginDeepOne.getSchemas(), { z: schemas.z, b: schemas.b }) + t.assert.deepStrictEqual(pluginDeepTwo.getSchemas(), { z: schemas.z, b: schemas.b, c: schemas.c }) + testDone() }) }) -test('Use the same schema id in different places', t => { +test('Use the same schema id in different places', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -887,10 +923,13 @@ test('Use the same schema id in different places', t => { } }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('Get schema anyway should not add `properties` if allOf is present', t => { +test('Get schema anyway should not add `properties` if allOf is present', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -924,10 +963,13 @@ test('Get schema anyway should not add `properties` if allOf is present', t => { } }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('Get schema anyway should not add `properties` if oneOf is present', t => { +test('Get schema anyway should not add `properties` if oneOf is present', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -961,10 +1003,13 @@ test('Get schema anyway should not add `properties` if oneOf is present', t => { } }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('Get schema anyway should not add `properties` if anyOf is present', t => { +test('Get schema anyway should not add `properties` if anyOf is present', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -998,10 +1043,13 @@ test('Get schema anyway should not add `properties` if anyOf is present', t => { } }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('Shared schema should be ignored in string enum', t => { +test('Shared schema should be ignored in string enum', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -1021,12 +1069,13 @@ test('Shared schema should be ignored in string enum', t => { }) fastify.inject('/C%23', (err, res) => { - t.error(err) - t.same(res.json(), { lang: 'C#' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { lang: 'C#' }) + testDone() }) }) -test('Shared schema should NOT be ignored in != string enum', t => { +test('Shared schema should NOT be ignored in != string enum', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -1053,12 +1102,13 @@ test('Shared schema should NOT be ignored in != string enum', t => { method: 'POST', payload: { lang: 'C#' } }, (err, res) => { - t.error(err) - t.same(res.json(), { lang: 'C#' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { lang: 'C#' }) + testDone() }) }) -test('Case insensitive header validation', t => { +test('Case insensitive header validation', (t, testDone) => { t.plan(2) const fastify = Fastify() fastify.get('/', { @@ -1082,12 +1132,13 @@ test('Case insensitive header validation', t => { FooBar: 'Baz' } }, (err, res) => { - t.error(err) - t.equal(res.payload, 'Baz') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'Baz') + testDone() }) }) -test('Not evaluate json-schema $schema keyword', t => { +test('Not evaluate json-schema $schema keyword', (t, testDone) => { t.plan(2) const fastify = Fastify() fastify.post('/', { @@ -1110,19 +1161,20 @@ test('Not evaluate json-schema $schema keyword', t => { method: 'POST', body: { hello: 'world', foo: 'bar' } }, (err, res) => { - t.error(err) - t.same(res.json(), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() }) }) -test('Validation context in validation result', t => { +test('Validation context in validation result', (t, testDone) => { t.plan(5) const fastify = Fastify() // custom error handler to expose validation context in response, so we can test it later fastify.setErrorHandler((err, request, reply) => { - t.equal(err instanceof Error, true) - t.ok(err.validation, 'detailed errors') - t.equal(err.validationContext, 'body') + t.assert.strictEqual(err instanceof Error, true) + t.assert.ok(err.validation, 'detailed errors') + t.assert.strictEqual(err.validationContext, 'body') reply.code(400).send() }) fastify.post('/', { @@ -1142,12 +1194,13 @@ test('Validation context in validation result', t => { url: '/', payload: {} // body lacks required field, will fail validation }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + testDone() }) }) -test('The schema build should not modify the input', t => { +test('The schema build should not modify the input', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -1205,14 +1258,15 @@ test('The schema build should not modify the input', t => { } }) - t.ok(first.$id) + t.assert.ok(first.$id) fastify.ready(err => { - t.error(err) - t.ok(first.$id) + t.assert.ifError(err) + t.assert.ok(first.$id) + testDone() }) }) -test('Cross schema reference with encapsulation references', t => { +test('Cross schema reference with encapsulation references', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -1262,29 +1316,30 @@ test('Cross schema reference with encapsulation references', t => { fastify.get('/get', { schema: { params: refItem, response: { 200: refItem } } }, () => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('Check how many AJV instances are built #1', t => { +test('Check how many AJV instances are built #1', (t, testDone) => { t.plan(12) const fastify = Fastify() addRandomRoute(fastify) // this trigger the schema validation creation - t.notOk(fastify.validatorCompiler, 'validator not initialized') + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') const instances = [] fastify.register((instance, opts, done) => { - t.notOk(fastify.validatorCompiler, 'validator not initialized') + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') instances.push(instance) done() }) fastify.register((instance, opts, done) => { - t.notOk(fastify.validatorCompiler, 'validator not initialized') + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') addRandomRoute(instance) instances.push(instance) done() instance.register((instance, opts, done) => { - t.notOk(fastify.validatorCompiler, 'validator not initialized') + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') addRandomRoute(instance) instances.push(instance) done() @@ -1292,18 +1347,19 @@ test('Check how many AJV instances are built #1', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) - t.ok(fastify.validatorCompiler, 'validator initialized on preReady') + t.assert.ok(fastify.validatorCompiler, 'validator initialized on preReady') fastify.validatorCompiler.checkPointer = true instances.forEach(i => { - t.ok(i.validatorCompiler, 'validator initialized on preReady') - t.equal(i.validatorCompiler.checkPointer, true, 'validator is only one for all the instances') + t.assert.ok(i.validatorCompiler, 'validator initialized on preReady') + t.assert.strictEqual(i.validatorCompiler.checkPointer, true, 'validator is only one for all the instances') }) + testDone() }) }) -test('onReady hook has the compilers ready', t => { +test('onReady hook has the compilers ready', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -1317,16 +1373,16 @@ test('onReady hook has the compilers ready', t => { }) fastify.addHook('onReady', function (done) { - t.ok(this.validatorCompiler) - t.ok(this.serializerCompiler) + t.assert.ok(this.validatorCompiler) + t.assert.ok(this.serializerCompiler) done() }) let hookCallCounter = 0 fastify.register(async (i, o) => { i.addHook('onReady', function (done) { - t.ok(this.validatorCompiler) - t.ok(this.serializerCompiler) + t.assert.ok(this.validatorCompiler) + t.assert.ok(this.serializerCompiler) done() }) @@ -1339,44 +1395,45 @@ test('onReady hook has the compilers ready', t => { }) fastify.ready(err => { - t.error(err) - t.equal(hookCallCounter, 1, 'it is called once') + t.assert.ifError(err) + t.assert.strictEqual(hookCallCounter, 1, 'it is called once') + testDone() }) }) -test('Check how many AJV instances are built #2 - verify validatorPool', t => { +test('Check how many AJV instances are built #2 - verify validatorPool', (t, testDone) => { t.plan(13) const fastify = Fastify() - t.notOk(fastify.validatorCompiler, 'validator not initialized') + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') fastify.register(function sibling1 (instance, opts, done) { addRandomRoute(instance) - t.notOk(instance.validatorCompiler, 'validator not initialized') + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') instance.ready(() => { - t.ok(instance.validatorCompiler, 'validator is initialized') + t.assert.ok(instance.validatorCompiler, 'validator is initialized') instance.validatorCompiler.sharedPool = 1 }) instance.after(() => { - t.notOk(instance.validatorCompiler, 'validator not initialized') + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') }) done() }) fastify.register(function sibling2 (instance, opts, done) { addRandomRoute(instance) - t.notOk(instance.validatorCompiler, 'validator not initialized') + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') instance.ready(() => { - t.equal(instance.validatorCompiler.sharedPool, 1, 'this context must share the validator with the same schemas') + t.assert.strictEqual(instance.validatorCompiler.sharedPool, 1, 'this context must share the validator with the same schemas') instance.validatorCompiler.sharedPool = 2 }) instance.after(() => { - t.notOk(instance.validatorCompiler, 'validator not initialized') + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') }) instance.register((instance, opts, done) => { - t.notOk(instance.validatorCompiler, 'validator not initialized') + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') instance.ready(() => { - t.equal(instance.validatorCompiler.sharedPool, 2, 'this context must share the validator of the parent') + t.assert.strictEqual(instance.validatorCompiler.sharedPool, 2, 'this context must share the validator of the parent') }) done() }) @@ -1389,15 +1446,18 @@ test('Check how many AJV instances are built #2 - verify validatorPool', t => { // this trigger to don't reuse the same compiler pool instance.addSchema({ $id: 'diff', type: 'object' }) - t.notOk(instance.validatorCompiler, 'validator not initialized') + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') instance.ready(() => { - t.ok(instance.validatorCompiler, 'validator is initialized') - t.notOk(instance.validatorCompiler.sharedPool, 'this context has its own compiler') + t.assert.ok(instance.validatorCompiler, 'validator is initialized') + t.assert.ok(!instance.validatorCompiler.sharedPool, 'this context has its own compiler') }) done() }) - fastify.ready(err => { t.error(err) }) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) function addRandomRoute (server) { @@ -1407,7 +1467,7 @@ function addRandomRoute (server) { ) } -test('Add schema order should not break the startup', t => { +test('Add schema order should not break the startup', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -1433,10 +1493,13 @@ test('Add schema order should not break the startup', t => { } }, () => {}) - fastify.ready(err => { t.error(err) }) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('The schema compiler recreate itself if needed', t => { +test('The schema compiler recreate itself if needed', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -1463,23 +1526,26 @@ test('The schema compiler recreate itself if needed', t => { done() }) - fastify.ready(err => { t.error(err) }) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) test('Schema controller setter', t => { t.plan(2) Fastify({ schemaController: {} }) - t.pass('allow empty object') + t.assert.ok('allow empty object') try { Fastify({ schemaController: { bucket: {} } }) t.fail('the bucket option must be a function') } catch (err) { - t.equal(err.message, "schemaController.bucket option should be a function, instead got 'object'") + t.assert.strictEqual(err.message, "schemaController.bucket option should be a function, instead got 'object'") } }) -test('Schema controller bucket', t => { +test('Schema controller bucket', (t, testDone) => { t.plan(10) let added = 0 @@ -1489,7 +1555,7 @@ test('Schema controller bucket', t => { function factoryBucket (storeInit) { builtBucket++ - t.same(initStoreQueue.pop(), storeInit) + t.assert.deepStrictEqual(initStoreQueue.pop(), storeInit) const store = new Map(storeInit) return { add (schema) { @@ -1516,13 +1582,13 @@ test('Schema controller bucket', t => { fastify.register(async (instance) => { instance.addSchema({ $id: 'b', type: 'string' }) instance.addHook('onReady', function (done) { - t.equal(instance.getSchemas().size, 2) + t.assert.strictEqual(instance.getSchemas().size, 2) done() }) instance.register(async (subinstance) => { subinstance.addSchema({ $id: 'c', type: 'string' }) subinstance.addHook('onReady', function (done) { - t.equal(subinstance.getSchemas().size, 3) + t.assert.strictEqual(subinstance.getSchemas().size, 3) done() }) }) @@ -1530,7 +1596,7 @@ test('Schema controller bucket', t => { fastify.register(async (instance) => { instance.addHook('onReady', function (done) { - t.equal(instance.getSchemas().size, 1) + t.assert.strictEqual(instance.getSchemas().size, 1) done() }) }) @@ -1538,20 +1604,21 @@ test('Schema controller bucket', t => { fastify.addSchema({ $id: 'a', type: 'string' }) fastify.ready(err => { - t.error(err) - t.equal(added, 3, 'three schema added') - t.equal(builtBucket, 4, 'one bucket built for every register call + 1 for the root instance') + t.assert.ifError(err) + t.assert.strictEqual(added, 3, 'three schema added') + t.assert.strictEqual(builtBucket, 4, 'one bucket built for every register call + 1 for the root instance') + testDone() }) }) -test('setSchemaController per instance', t => { +test('setSchemaController per instance', (t, testDone) => { t.plan(7) const fastify = Fastify({}) fastify.register(async (instance1) => { instance1.setSchemaController({ bucket: function factoryBucket (storeInit) { - t.pass('instance1 has created the bucket') + t.assert.ok('instance1 has created the bucket') return { add (schema) { t.fail('add is not called') }, getSchema (id) { t.fail('getSchema is not called') }, @@ -1566,19 +1633,19 @@ test('setSchemaController per instance', t => { instance2.setSchemaController({ bucket: function factoryBucket (storeInit) { - t.pass('instance2 has created the bucket') + t.assert.ok('instance2 has created the bucket') const map = {} return { add (schema) { - t.equal(schema.$id, bSchema.$id, 'add is called') + t.assert.strictEqual(schema.$id, bSchema.$id, 'add is called') map[schema.$id] = schema }, getSchema (id) { - t.pass('getSchema is called') + t.assert.ok('getSchema is called') return map[id] }, getSchemas () { - t.pass('getSchemas is called') + t.assert.ok('getSchemas is called') } } } @@ -1588,12 +1655,15 @@ test('setSchemaController per instance', t => { instance2.addHook('onReady', function (done) { instance2.getSchemas() - t.same(instance2.getSchema('b'), bSchema, 'the schema are loaded') + t.assert.deepStrictEqual(instance2.getSchema('b'), bSchema, 'the schema are loaded') done() }) }) - fastify.ready(err => { t.error(err) }) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) test('setSchemaController: Inherits correctly parent schemas with a customized validator instance', async t => { @@ -1628,8 +1698,8 @@ test('setSchemaController: Inherits correctly parent schemas with a customized v compilersFactory: { buildValidator: function (externalSchemas) { const schemaKeys = Object.keys(externalSchemas) - t.equal(schemaKeys.length, 2, 'Contains same number of schemas') - t.hasStrict([someSchema, errorResponseSchema], Object.values(externalSchemas), 'Contains expected schemas') + t.assert.strictEqual(schemaKeys.length, 2, 'Contains same number of schemas') + t.assert.deepStrictEqual([someSchema, errorResponseSchema], Object.values(externalSchemas), 'Contains expected schemas') for (const key of schemaKeys) { if (customAjv.getSchema(key) == null) { customAjv.addSchema(externalSchemas[key], key) @@ -1678,9 +1748,9 @@ test('setSchemaController: Inherits correctly parent schemas with a customized v }) const json = res.json() - t.equal(json.message, 'querystring/msg must be array') - t.equal(json.statusCode, 400) - t.equal(res.statusCode, 400, 'Should not coerce the string into array') + t.assert.strictEqual(json.message, 'querystring/msg must be array') + t.assert.strictEqual(json.statusCode, 400) + t.assert.strictEqual(res.statusCode, 400, 'Should not coerce the string into array') }) test('setSchemaController: Inherits buildSerializer from parent if not present within the instance', async t => { @@ -1786,17 +1856,17 @@ test('setSchemaController: Inherits buildSerializer from parent if not present w method: 'GET', url: '/', query: { - msg: 'string' + msg: ['string'] } }) const json = res.json() - t.equal(json.statusCode, 400) - t.equal(json.message, 'querystring/msg must be array') - t.equal(rootSerializerCalled, 1, 'Should be called from the child') - t.equal(rootValidatorCalled, 0, 'Should not be called from the child') - t.equal(childValidatorCalled, 1, 'Should be called from the child') - t.equal(res.statusCode, 400, 'Should not coerce the string into array') + t.assert.strictEqual(json.statusCode, 400) + t.assert.strictEqual(json.message, 'querystring/msg must be array') + t.assert.strictEqual(rootSerializerCalled, 1, 'Should be called from the child') + t.assert.strictEqual(rootValidatorCalled, 0, 'Should not be called from the child') + t.assert.strictEqual(childValidatorCalled, 1, 'Should be called from the child') + t.assert.strictEqual(res.statusCode, 400, 'Should not coerce the string into array') }) test('setSchemaController: Inherits buildValidator from parent if not present within the instance', async t => { @@ -1911,12 +1981,12 @@ test('setSchemaController: Inherits buildValidator from parent if not present wi }) const json = res.json() - t.equal(json.statusCode, 400) - t.equal(json.message, 'querystring/msg must be array') - t.equal(rootSerializerCalled, 0, 'Should be called from the child') - t.equal(rootValidatorCalled, 1, 'Should not be called from the child') - t.equal(childSerializerCalled, 1, 'Should be called from the child') - t.equal(res.statusCode, 400, 'Should not coerce the string into array') + t.assert.strictEqual(json.statusCode, 400) + t.assert.strictEqual(json.message, 'querystring/msg must be array') + t.assert.strictEqual(rootSerializerCalled, 0, 'Should be called from the child') + t.assert.strictEqual(rootValidatorCalled, 1, 'Should not be called from the child') + t.assert.strictEqual(childSerializerCalled, 1, 'Should be called from the child') + t.assert.strictEqual(res.statusCode, 400, 'Should not coerce the string into array') }) test('Should throw if not default validator passed', async t => { @@ -1938,8 +2008,8 @@ test('Should throw if not default validator passed', async t => { compilersFactory: { buildValidator: function (externalSchemas) { const schemaKeys = Object.keys(externalSchemas) - t.equal(schemaKeys.length, 2) - t.same(schemaKeys, ['some', 'another']) + t.assert.strictEqual(schemaKeys.length, 2) + t.assert.deepStrictEqual(schemaKeys, ['some', 'another']) for (const key of schemaKeys) { if (customAjv.getSchema(key) == null) { @@ -2003,10 +2073,10 @@ test('Should throw if not default validator passed', async t => { } }) - t.equal(res.json().message, 'querystring/msg must be array') - t.equal(res.statusCode, 400, 'Should not coerce the string into array') + t.assert.strictEqual(res.json().message, 'querystring/msg must be array') + t.assert.strictEqual(res.statusCode, 400, 'Should not coerce the string into array') } catch (err) { - t.error(err) + t.assert.ifError(err) } }) @@ -2070,14 +2140,14 @@ test('Should coerce the array if the default validator is used', async t => { } }) - t.equal(res.statusCode, 200) - t.same(res.json(), { msg: ['string'] }, 'Should coerce the string into array') + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { msg: ['string'] }, 'Should coerce the string into array') } catch (err) { - t.error(err) + t.assert.ifError(err) } }) -test('Should return a human-friendly error if response status codes are not specified', t => { +test('Should return a human-friendly error if response status codes are not specified', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -2096,8 +2166,9 @@ test('Should return a human-friendly error if response status codes are not spec }) fastify.ready(err => { - t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') - t.match(err.message, 'Failed building the serialization schema for GET: /, due to error response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') + t.assert.strictEqual(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + t.assert.strictEqual(err.message, 'Failed building the serialization schema for GET: /, due to error response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') + testDone() }) }) @@ -2110,7 +2181,7 @@ test('setSchemaController: custom validator instance should not mutate headers s compilersFactory: { buildValidator: function () { return ({ schema, method, url, httpPart }) => { - t.type(schema, Headers) + t.assert.ok(schema instanceof Headers) return () => {} } } From ef9d3d061872ba5ba2c1c09e4f7afe1e108b84de Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 24 Apr 2025 18:57:23 +0200 Subject: [PATCH 0995/1295] test: added more cases for JSON schema validation (#6067) Signed-off-by: Matteo Collina --- test/schema-validation.test.js | 46 ++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index 8a632925dd4..cb36ba774a0 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -1343,8 +1343,6 @@ test('Schema validation when no content type is provided', async t => { }) test('Schema validation will not be bypass by different content type', async t => { - t.plan(10) - const fastify = Fastify() fastify.post('/', { @@ -1435,4 +1433,48 @@ test('Schema validation will not be bypass by different content type', async t = }) t.equal(invalid4.statusCode, 400) t.equal((await invalid4.body.json()).code, 'FST_ERR_VALIDATION') + + const invalid5 = await request(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn \tfoo;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.equal(invalid5.statusCode, 400) + t.equal((await invalid5.body.json()).code, 'FST_ERR_VALIDATION') + + const invalid6 = await request(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn\t foo;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.equal(invalid6.statusCode, 415) + t.equal((await invalid6.body.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + + const invalid7 = await request(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn \t' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.equal(invalid7.statusCode, 400) + t.equal((await invalid7.body.json()).code, 'FST_ERR_VALIDATION') + + const invalid8 = await request(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn\t' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.equal(invalid8.statusCode, 400) + t.equal((await invalid8.body.json()).code, 'FST_ERR_VALIDATION') }) From ecd727cdc6bf11430dc70c9ed21792a9addbeea0 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 24 Apr 2025 18:58:43 +0200 Subject: [PATCH 0996/1295] test: migrated inject.test.js from tap to node:test (#6068) --- test/inject.test.js | 211 ++++++++++++++++++++++++-------------------- 1 file changed, 114 insertions(+), 97 deletions(-) diff --git a/test/inject.test.js b/test/inject.test.js index e7e66afc7eb..b758657d160 100644 --- a/test/inject.test.js +++ b/test/inject.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Stream = require('node:stream') const util = require('node:util') const Fastify = require('..') @@ -10,11 +9,11 @@ const { Readable } = require('node:stream') test('inject should exist', t => { t.plan(2) const fastify = Fastify() - t.ok(fastify.inject) - t.equal(typeof fastify.inject, 'function') + t.assert.ok(fastify.inject) + t.assert.strictEqual(typeof fastify.inject, 'function') }) -test('should wait for the ready event', t => { +test('should wait for the ready event', (t, done) => { t.plan(4) const fastify = Fastify() const payload = { hello: 'world' } @@ -23,7 +22,6 @@ test('should wait for the ready event', t => { instance.get('/', (req, reply) => { reply.send(payload) }) - setTimeout(done, 500) }) @@ -31,14 +29,15 @@ test('should wait for the ready event', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.same(payload, JSON.parse(res.payload)) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '17') + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() }) }) -test('inject get request', t => { +test('inject get request', (t, done) => { t.plan(4) const fastify = Fastify() const payload = { hello: 'world' } @@ -51,14 +50,15 @@ test('inject get request', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.same(payload, JSON.parse(res.payload)) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '17') + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() }) }) -test('inject get request - code check', t => { +test('inject get request - code check', (t, done) => { t.plan(4) const fastify = Fastify() const payload = { hello: 'world' } @@ -71,14 +71,15 @@ test('inject get request - code check', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.same(payload, JSON.parse(res.payload)) - t.equal(res.statusCode, 201) - t.equal(res.headers['content-length'], '17') + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 201) + t.assert.strictEqual(res.headers['content-length'], '17') + done() }) }) -test('inject get request - headers check', t => { +test('inject get request - headers check', (t, done) => { t.plan(4) const fastify = Fastify() @@ -90,14 +91,15 @@ test('inject get request - headers check', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal('', res.payload) - t.equal(res.headers['content-type'], 'text/plain') - t.equal(res.headers['content-length'], '0') + t.assert.ifError(err) + t.assert.strictEqual('', res.payload) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.strictEqual(res.headers['content-length'], '0') + done() }) }) -test('inject get request - querystring', t => { +test('inject get request - querystring', (t, done) => { t.plan(4) const fastify = Fastify() @@ -109,14 +111,15 @@ test('inject get request - querystring', t => { method: 'GET', url: '/?hello=world' }, (err, res) => { - t.error(err) - t.same({ hello: 'world' }, JSON.parse(res.payload)) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '17') + t.assert.ifError(err) + t.assert.deepStrictEqual({ hello: 'world' }, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() }) }) -test('inject get request - params', t => { +test('inject get request - params', (t, done) => { t.plan(4) const fastify = Fastify() @@ -128,14 +131,15 @@ test('inject get request - params', t => { method: 'GET', url: '/world' }, (err, res) => { - t.error(err) - t.same({ hello: 'world' }, JSON.parse(res.payload)) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '17') + t.assert.ifError(err) + t.assert.deepStrictEqual({ hello: 'world' }, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() }) }) -test('inject get request - wildcard', t => { +test('inject get request - wildcard', (t, done) => { t.plan(4) const fastify = Fastify() @@ -147,14 +151,15 @@ test('inject get request - wildcard', t => { method: 'GET', url: '/test/wildcard' }, (err, res) => { - t.error(err) - t.same({ '*': 'wildcard' }, JSON.parse(res.payload)) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '16') + t.assert.ifError(err) + t.assert.deepStrictEqual({ '*': 'wildcard' }, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '16') + done() }) }) -test('inject get request - headers', t => { +test('inject get request - headers', (t, done) => { t.plan(4) const fastify = Fastify() @@ -167,14 +172,15 @@ test('inject get request - headers', t => { url: '/', headers: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal('world', JSON.parse(res.payload).hello) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '69') + t.assert.ifError(err) + t.assert.strictEqual('world', JSON.parse(res.payload).hello) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '69') + done() }) }) -test('inject post request', t => { +test('inject post request', (t, done) => { t.plan(4) const fastify = Fastify() const payload = { hello: 'world' } @@ -188,14 +194,15 @@ test('inject post request', t => { url: '/', payload }, (err, res) => { - t.error(err) - t.same(payload, JSON.parse(res.payload)) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '17') + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() }) }) -test('inject post request - send stream', t => { +test('inject post request - send stream', (t, done) => { t.plan(4) const fastify = Fastify() @@ -209,14 +216,15 @@ test('inject post request - send stream', t => { headers: { 'content-type': 'application/json' }, payload: getStream() }, (err, res) => { - t.error(err) - t.same('{"hello":"world"}', res.payload) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '17') + t.assert.ifError(err) + t.assert.deepStrictEqual('{"hello":"world"}', res.payload) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() }) }) -test('inject get request - reply stream', t => { +test('inject get request - reply stream', (t, done) => { t.plan(3) const fastify = Fastify() @@ -228,13 +236,14 @@ test('inject get request - reply stream', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.same('{"hello":"world"}', res.payload) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual('{"hello":"world"}', res.payload) + t.assert.strictEqual(res.statusCode, 200) + done() }) }) -test('inject promisify - waiting for ready event', t => { +test('inject promisify - waiting for ready event', (t, done) => { t.plan(1) const fastify = Fastify() const payload = { hello: 'world' } @@ -249,12 +258,13 @@ test('inject promisify - waiting for ready event', t => { } fastify.inject(injectParams) .then(res => { - t.equal(res.statusCode, 200) + t.assert.strictEqual(res.statusCode, 200) + done() }) - .catch(t.fail) + .catch(t.assert.fail) }) -test('inject promisify - after the ready event', t => { +test('inject promisify - after the ready event', (t, done) => { t.plan(2) const fastify = Fastify() const payload = { hello: 'world' } @@ -264,7 +274,7 @@ test('inject promisify - after the ready event', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) const injectParams = { method: 'GET', @@ -272,13 +282,14 @@ test('inject promisify - after the ready event', t => { } fastify.inject(injectParams) .then(res => { - t.equal(res.statusCode, 200) + t.assert.strictEqual(res.statusCode, 200) + done() }) - .catch(t.fail) + .catch(t.assert.fail) }) }) -test('inject promisify - when the server is up', t => { +test('inject promisify - when the server is up', (t, done) => { t.plan(2) const fastify = Fastify() const payload = { hello: 'world' } @@ -288,7 +299,7 @@ test('inject promisify - when the server is up', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) // setTimeout because the ready event don't set "started" flag // in this iteration of the 'event loop' @@ -299,14 +310,15 @@ test('inject promisify - when the server is up', t => { } fastify.inject(injectParams) .then(res => { - t.equal(res.statusCode, 200) + t.assert.strictEqual(res.statusCode, 200) + done() }) - .catch(t.fail) + .catch(t.assert.fail) }, 10) }) }) -test('should reject in error case', t => { +test('should reject in error case', (t, done) => { t.plan(1) const fastify = Fastify() @@ -320,11 +332,12 @@ test('should reject in error case', t => { url: '/' }) .catch(e => { - t.equal(e, error) + t.assert.strictEqual(e, error) + done() }) }) -test('inject a multipart request using form-body', t => { +test('inject a multipart request using form-body', (t, done) => { t.plan(2) const fastify = Fastify() @@ -350,8 +363,9 @@ test('inject a multipart request using form-body', t => { payload: form }) .then(response => { - t.equal(response.statusCode, 200) - t.ok(/Content-Disposition: form-data; name="my_field"/.test(response.payload)) + t.assert.strictEqual(response.statusCode, 200) + t.assert.ok(/Content-Disposition: form-data; name="my_field"/.test(response.payload)) + done() }) }) @@ -371,28 +385,29 @@ function getStream () { return new Read() } -test('should error the promise if ready errors', t => { +test('should error the promise if ready errors', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, opts) => { return Promise.reject(new Error('kaboom')) }).after(function () { - t.pass('after is called') + t.assert.ok('after is called') }) fastify.inject({ method: 'GET', url: '/' }).then(() => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }).catch(err => { - t.ok(err) - t.equal(err.message, 'kaboom') + t.assert.ok(err) + t.assert.strictEqual(err.message, 'kaboom') + done() }) }) -test('should throw error if callback specified and if ready errors', t => { +test('should throw error if callback specified and if ready errors', (t, done) => { t.plan(2) const fastify = Fastify() const error = new Error('kaboom') @@ -405,8 +420,9 @@ test('should throw error if callback specified and if ready errors', t => { method: 'GET', url: '/' }, err => { - t.ok(err) - t.equal(err, error) + t.assert.ok(err) + t.assert.strictEqual(err, error) + done() }) }) @@ -421,9 +437,9 @@ test('should support builder-style injection with ready app', async (t) => { await fastify.ready() const res = await fastify.inject().get('/').end() - t.same(payload, JSON.parse(res.payload)) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '17') + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') }) test('should support builder-style injection with non-ready app', async (t) => { @@ -436,9 +452,9 @@ test('should support builder-style injection with non-ready app', async (t) => { }) const res = await fastify.inject().get('/').end() - t.same(payload, JSON.parse(res.payload)) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '17') + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') }) test('should handle errors in builder-style injection correctly', async (t) => { @@ -451,19 +467,19 @@ test('should handle errors in builder-style injection correctly', async (t) => { try { await fastify.inject().get('/') } catch (err) { - t.ok(err) - t.equal(err.message, 'Kaboom') + t.assert.ok(err) + t.assert.strictEqual(err.message, 'Kaboom') } }) -test('Should not throw on access to routeConfig frameworkErrors handler - FST_ERR_BAD_URL', t => { +test('Should not throw on access to routeConfig frameworkErrors handler - FST_ERR_BAD_URL', (t, done) => { t.plan(5) const fastify = Fastify({ frameworkErrors: function (err, req, res) { - t.ok(typeof req.id === 'string') - t.ok(req.raw instanceof Readable) - t.same(req.routerPath, undefined) + t.assert.ok(typeof req.id === 'string') + t.assert.ok(req.raw instanceof Readable) + t.assert.deepStrictEqual(req.routerPath, undefined) res.send(`${err.message} - ${err.code}`) } }) @@ -478,8 +494,9 @@ test('Should not throw on access to routeConfig frameworkErrors handler - FST_ER url: '/test/%world' }, (err, res) => { - t.error(err) - t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + done() } ) }) From 85d5689e884d1f700a02d6cb6cb57244edb0348b Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 24 Apr 2025 19:01:37 +0200 Subject: [PATCH 0997/1295] test: plugin 1 (#6075) --- test/plugin.1.test.js | 131 +++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/test/plugin.1.test.js b/test/plugin.1.test.js index d537fb1e00d..0b1dccd74bc 100644 --- a/test/plugin.1.test.js +++ b/test/plugin.1.test.js @@ -1,21 +1,23 @@ 'use strict' -const t = require('tap') +const t = require('node:test') const test = t.test const Fastify = require('../fastify') const sget = require('simple-get').concat const fp = require('fastify-plugin') +const { waitForCb } = require('./toolkit') -test('require a plugin', t => { +test('require a plugin', (t, testDone) => { t.plan(1) const fastify = Fastify() fastify.register(require('./plugin.helper')) fastify.ready(() => { - t.ok(fastify.test) + t.assert.ok(fastify.test) + testDone() }) }) -test('plugin metadata - ignore prefix', t => { +test('plugin metadata - ignore prefix', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -26,8 +28,9 @@ test('plugin metadata - ignore prefix', t => { method: 'GET', url: '/' }, function (err, res) { - t.error(err) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) function plugin (instance, opts, done) { @@ -45,37 +48,37 @@ test('plugin metadata - naming plugins', async t => { fastify.register(require('./plugin.name.display')) fastify.register(function (fastify, opts, done) { // one line - t.equal(fastify.pluginName, 'function (fastify, opts, done) { -- // one line') + t.assert.strictEqual(fastify.pluginName, 'function (fastify, opts, done) { -- // one line') done() }) fastify.register(function fooBar (fastify, opts, done) { - t.equal(fastify.pluginName, 'fooBar') + t.assert.strictEqual(fastify.pluginName, 'fooBar') done() }) await fastify.ready() }) -test('fastify.register with fastify-plugin should not encapsulate his code', t => { +test('fastify.register with fastify-plugin should not encapsulate his code', (t, testDone) => { t.plan(10) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.register(fp((i, o, n) => { i.decorate('test', () => {}) - t.ok(i.test) + t.assert.ok(i.test) n() })) - t.notOk(instance.test) + t.assert.ok(!instance.test) // the decoration is added at the end instance.after(() => { - t.ok(instance.test) + t.assert.ok(instance.test) }) instance.get('/', (req, reply) => { - t.ok(instance.test) + t.assert.ok(instance.test) reply.send({ hello: 'world' }) }) @@ -83,41 +86,42 @@ test('fastify.register with fastify-plugin should not encapsulate his code', t = }) fastify.ready(() => { - t.notOk(fastify.test) + t.assert.ok(!fastify.test) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + testDone() }) }) }) -test('fastify.register with fastify-plugin should provide access to external fastify instance if opts argument is a function', t => { +test('fastify.register with fastify-plugin should provide access to external fastify instance if opts argument is a function', (t, testDone) => { t.plan(22) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.register(fp((i, o, n) => { i.decorate('global', () => {}) - t.ok(i.global) + t.assert.ok(i.global) n() })) instance.register((i, o, n) => n(), p => { - t.notOk(p === instance || p === fastify) - t.ok(Object.prototype.isPrototypeOf.call(instance, p)) - t.ok(Object.prototype.isPrototypeOf.call(fastify, p)) - t.ok(p.global) + t.assert.ok(!(p === instance || p === fastify)) + t.assert.ok(Object.prototype.isPrototypeOf.call(instance, p)) + t.assert.ok(Object.prototype.isPrototypeOf.call(fastify, p)) + t.assert.ok(p.global) }) instance.register((i, o, n) => { @@ -125,17 +129,17 @@ test('fastify.register with fastify-plugin should provide access to external fas n() }) - instance.register((i, o, n) => n(), p => t.notOk(p.local)) + instance.register((i, o, n) => n(), p => t.assert.ok(!p.local)) instance.register((i, o, n) => { - t.ok(i.local) + t.assert.ok(i.local) n() }, p => p.decorate('local', () => {})) - instance.register((i, o, n) => n(), p => t.notOk(p.local)) + instance.register((i, o, n) => n(), p => t.assert.ok(!p.local)) instance.register(fp((i, o, n) => { - t.ok(i.global_2) + t.assert.ok(i.global_2) n() }), p => p.decorate('global_2', () => 'hello')) @@ -143,40 +147,41 @@ test('fastify.register with fastify-plugin should provide access to external fas i.decorate('global_2', () => 'world') n() }, p => p.get('/', (req, reply) => { - t.ok(p.global_2) + t.assert.ok(p.global_2) reply.send({ hello: p.global_2() }) })) - t.notOk(instance.global) - t.notOk(instance.global_2) - t.notOk(instance.local) + t.assert.ok(!instance.global) + t.assert.ok(!instance.global_2) + t.assert.ok(!instance.local) // the decoration is added at the end instance.after(() => { - t.ok(instance.global) - t.equal(instance.global_2(), 'hello') - t.notOk(instance.local) + t.assert.ok(instance.global) + t.assert.strictEqual(instance.global_2(), 'hello') + t.assert.ok(!instance.local) }) done() }) fastify.ready(() => { - t.notOk(fastify.global) + t.assert.ok(!fastify.global) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + testDone() }) }) }) @@ -187,7 +192,7 @@ test('fastify.register with fastify-plugin registers fastify level plugins', t = function fastifyPlugin (instance, opts, done) { instance.decorate('test', 'first') - t.ok(instance.test) + t.assert.ok(instance.test) done() } @@ -199,11 +204,11 @@ test('fastify.register with fastify-plugin registers fastify level plugins', t = fastify.register(fp(fastifyPlugin)) fastify.register((instance, opts, done) => { - t.ok(instance.test) + t.assert.ok(instance.test) instance.register(fp(innerPlugin)) instance.get('/test2', (req, reply) => { - t.ok(instance.test2) + t.assert.ok(instance.test2) reply.send({ test2: instance.test2 }) }) @@ -211,37 +216,43 @@ test('fastify.register with fastify-plugin registers fastify level plugins', t = }) fastify.ready(() => { - t.ok(fastify.test) - t.notOk(fastify.test2) + t.assert.ok(fastify.test) + t.assert.ok(!fastify.test2) }) fastify.get('/', (req, reply) => { - t.ok(fastify.test) + t.assert.ok(fastify.test) reply.send({ test: fastify.test }) }) + const { stepIn, patience } = waitForCb({ steps: 2 }) + fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { test: 'first' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { test: 'first' }) + stepIn() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/test2' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { test2: 'second' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { test2: 'second' }) + stepIn() }) }) + + return patience }) From 84e2d8f0db7bc71f1118fd7083439928b1857d24 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 28 Apr 2025 18:42:03 +0200 Subject: [PATCH 0998/1295] ci: fix branch pattern (#6090) Signed-off-by: Manuel Spigolon --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 080dd087fd1..56ae4a31187 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: - main - next - - 'v*' + - '*.x' paths-ignore: - 'docs/**' - '*.md' From 2e7c639fb52039bdecb26aa34dbfa18c3171e049 Mon Sep 17 00:00:00 2001 From: Maik Jablonski Date: Wed, 30 Apr 2025 11:17:59 +0200 Subject: [PATCH 0999/1295] docs: added Jeasx to Ecosystem.md (#6082) --- docs/Guides/Ecosystem.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 573363e0888..2b535e34119 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -740,9 +740,13 @@ middlewares into Fastify plugins generator by directory structure. - [`fastify-flux`](https://github.com/Jnig/fastify-flux) Tool for building Fastify APIs using decorators and convert Typescript interface to JSON Schema. +- [`jeasx`](https://www.jeasx.dev) + A flexible server-rendering framework built on Fastify + that leverages asynchronous JSX to simplify web development. - [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to generate JSON Schema from TypeScript interfaces. - [`vite-plugin-fastify`](https://github.com/Vanilla-IceCream/vite-plugin-fastify) Fastify plugin for Vite with Hot-module Replacement. - [`vite-plugin-fastify-routes`](https://github.com/Vanilla-IceCream/vite-plugin-fastify-routes) File-based routing for Fastify applications using Vite. + From 894076b74a525dbe814c1031f94c219c6102c541 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:21:59 +0200 Subject: [PATCH 1000/1295] test: mv promises from tap (#6085) --- test/promises.test.js | 72 +++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/test/promises.test.js b/test/promises.test.js index 6363506f3d4..5f489dfc622 100644 --- a/test/promises.test.js +++ b/test/promises.test.js @@ -1,10 +1,12 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') +const assert = require('node:assert') const sget = require('simple-get').concat const fastify = require('..')() +test.after(() => fastify.close()) + const opts = { schema: { response: { @@ -61,80 +63,78 @@ fastify.get('/return-reply', opts, function (req, reply) { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + assert.ifError(err) - test('shorthand - sget return promise es6 get', t => { - t.plan(4) + test('shorthand - sget return promise es6 get', (t, testDone) => { sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/return' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + testDone() }) }) - test('shorthand - sget promise es6 get return error', t => { - t.plan(2) + test('shorthand - sget promise es6 get return error', (t, testDone) => { sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/return-error' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + testDone() }) }) - test('sget promise double send', t => { - t.plan(3) - + test('sget promise double send', (t, testDone) => { sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/double' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: '42' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(body), { hello: '42' }) + testDone() }) }) - test('thenable', t => { - t.plan(4) + test('thenable', (t, testDone) => { sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/thenable' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + testDone() }) }) - test('thenable (error)', t => { - t.plan(2) + test('thenable (error)', (t, testDone) => { sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/thenable-error' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + testDone() }) }) - test('return-reply', t => { - t.plan(4) + test('return-reply', (t, testDone) => { sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/return-reply' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + testDone() }) }) }) From 930d12896ea558fbb7b8a68a04a2788c115026b3 Mon Sep 17 00:00:00 2001 From: Cangit Date: Wed, 30 Apr 2025 15:48:34 +0200 Subject: [PATCH 1001/1295] chore: node:http2 is always available (#6073) --- docs/Reference/Errors.md | 2 - lib/errors.js | 8 --- lib/server.js | 81 +++++++++---------------- test/http2/missing-http2-module.test.js | 17 ------ test/internals/errors.test.js | 12 +--- test/types/errors.test-d.ts | 1 - types/errors.d.ts | 1 - 7 files changed, 31 insertions(+), 91 deletions(-) delete mode 100644 test/http2/missing-http2-module.test.js diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index af940449161..93c2334425b 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -69,7 +69,6 @@ - [FST_ERR_SCH_VALIDATION_BUILD](#fst_err_sch_validation_build) - [FST_ERR_SCH_SERIALIZATION_BUILD](#fst_err_sch_serialization_build) - [FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX](#fst_err_sch_response_schema_not_nested_2xx) - - [FST_ERR_HTTP2_INVALID_VERSION](#fst_err_http2_invalid_version) - [FST_ERR_INIT_OPTS_INVALID](#fst_err_init_opts_invalid) - [FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE](#fst_err_force_close_connections_idle_not_available) - [FST_ERR_DUPLICATED_ROUTE](#fst_err_duplicated_route) @@ -340,7 +339,6 @@ Below is a table with all the error codes used by Fastify. |
FST_ERR_SCH_VALIDATION_BUILD | The JSON schema provided for validation to a route is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) | | FST_ERR_SCH_SERIALIZATION_BUILD | The JSON schema provided for serialization of a route response is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) | | FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX | Response schemas should be nested under a valid status code (2XX). | Use a valid status code. | [#4554](https://github.com/fastify/fastify/pull/4554) | -| FST_ERR_HTTP2_INVALID_VERSION | HTTP2 is available only from node >= 8.8.1. | Use a higher version of node. | [#1346](https://github.com/fastify/fastify/pull/1346) | | FST_ERR_INIT_OPTS_INVALID | Invalid initialization options. | Use valid initialization options. | [#1471](https://github.com/fastify/fastify/pull/1471) | | FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE | Cannot set forceCloseConnections to `idle` as your HTTP server does not support `closeIdleConnections` method. | Use a different value for `forceCloseConnections`. | [#3925](https://github.com/fastify/fastify/pull/3925) | | FST_ERR_DUPLICATED_ROUTE | The HTTP method already has a registered controller for that URL. | Use a different URL or register the controller for another HTTP method. | [#2954](https://github.com/fastify/fastify/pull/2954) | diff --git a/lib/errors.js b/lib/errors.js index d9cede1d53f..fe6b524565f 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -328,14 +328,6 @@ const codes = { 'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }' ), - /** - * http2 - */ - FST_ERR_HTTP2_INVALID_VERSION: createError( - 'FST_ERR_HTTP2_INVALID_VERSION', - 'HTTP2 is available only from node >= 8.8.1' - ), - /** * initialConfig */ diff --git a/lib/server.js b/lib/server.js index 14399d6e3e0..a632e52d533 100644 --- a/lib/server.js +++ b/lib/server.js @@ -2,6 +2,7 @@ const http = require('node:http') const https = require('node:https') +const http2 = require('node:http2') const dns = require('node:dns') const os = require('node:os') @@ -9,7 +10,6 @@ const { kState, kOptions, kServerBindings } = require('./symbols') const { FSTWRN003 } = require('./warnings') const { onListenHookRunner } = require('./hooks') const { - FST_ERR_HTTP2_INVALID_VERSION, FST_ERR_REOPENED_CLOSE_SERVER, FST_ERR_REOPENED_SERVER, FST_ERR_LISTEN_OPTIONS_INVALID @@ -98,7 +98,6 @@ function createServer (options, httpHandler) { if (cb === undefined) { const listening = listenPromise.call(this, server, listenOptions) - /* istanbul ignore else */ return listening.then(address => { return new Promise((resolve, reject) => { if (host === 'localhost') { @@ -192,7 +191,6 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o // to the secondary servers. It is valid only when the user is // listening on localhost const originUnref = mainServer.unref - /* c8 ignore next 4 */ mainServer.unref = function () { originUnref.call(mainServer) mainServer.emit('unref') @@ -218,7 +216,8 @@ function listenCallback (server, listenOptions) { if (this[kState].listening && this[kState].closing) { return listenOptions.cb(new FST_ERR_REOPENED_CLOSE_SERVER(), null) - } else if (this[kState].listening) { + } + if (this[kState].listening) { return listenOptions.cb(new FST_ERR_REOPENED_SERVER(), null) } @@ -234,7 +233,8 @@ function listenCallback (server, listenOptions) { function listenPromise (server, listenOptions) { if (this[kState].listening && this[kState].closing) { return Promise.reject(new FST_ERR_REOPENED_CLOSE_SERVER()) - } else if (this[kState].listening) { + } + if (this[kState].listening) { return Promise.reject(new FST_ERR_REOPENED_SERVER()) } @@ -272,41 +272,38 @@ function listenPromise (server, listenOptions) { } function getServerInstance (options, httpHandler) { - let server = null - // node@20 do not accepts options as boolean - // we need to provide proper https option - const httpsOptions = options.https === true ? {} : options.https if (options.serverFactory) { - server = options.serverFactory(httpHandler, options) - } else if (options.http2) { - if (typeof httpsOptions === 'object') { - server = http2().createSecureServer(httpsOptions, httpHandler) - } else { - server = http2().createServer(httpHandler) - } - server.on('session', sessionTimeout(options.http2SessionTimeout)) - } else { - // this is http1 - if (httpsOptions) { - server = https.createServer(httpsOptions, httpHandler) - } else { - server = http.createServer(options.http, httpHandler) - } - server.keepAliveTimeout = options.keepAliveTimeout - server.requestTimeout = options.requestTimeout - // we treat zero as null - // and null is the default setting from nodejs - // so we do not pass the option to server - if (options.maxRequestsPerSocket > 0) { - server.maxRequestsPerSocket = options.maxRequestsPerSocket - } + // User provided server instance + return options.serverFactory(httpHandler, options) } - if (!options.serverFactory) { + // We have accepted true as a valid way to init https but node requires an options obj + const httpsOptions = options.https === true ? {} : options.https + + if (options.http2) { + const server = typeof httpsOptions === 'object' ? http2.createSecureServer(httpsOptions, httpHandler) : http2.createServer(options.http, httpHandler) + server.on('session', (session) => session.setTimeout(options.http2SessionTimeout, function closeSession () { + this.close() + })) + server.setTimeout(options.connectionTimeout) + + return server } + + // HTTP1 server instance + const server = httpsOptions ? https.createServer(httpsOptions, httpHandler) : http.createServer(options.http, httpHandler) + server.keepAliveTimeout = options.keepAliveTimeout + server.requestTimeout = options.requestTimeout + server.setTimeout(options.connectionTimeout) + // We treat zero as null(node default) so we do not pass zero to the server instance + if (options.maxRequestsPerSocket > 0) { + server.maxRequestsPerSocket = options.maxRequestsPerSocket + } + return server } + /** * Inspects the provided `server.address` object and returns a * normalized list of IP address strings. Normalization in this @@ -355,21 +352,3 @@ function logServerAddress (server, listenTextResolver) { } return addresses[0] } - -function http2 () { - try { - return require('node:http2') - } catch (err) { - throw new FST_ERR_HTTP2_INVALID_VERSION() - } -} - -function sessionTimeout (timeout) { - return function (session) { - session.setTimeout(timeout, close) - } -} - -function close () { - this.close() -} diff --git a/test/http2/missing-http2-module.test.js b/test/http2/missing-http2-module.test.js deleted file mode 100644 index 27df5285bea..00000000000 --- a/test/http2/missing-http2-module.test.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict' - -const { test } = require('node:test') -const proxyquire = require('proxyquire') -const server = proxyquire('../../lib/server', { 'node:http2': null }) -const Fastify = proxyquire('../..', { './lib/server.js': server }) - -test('should throw when http2 module cannot be found', t => { - t.plan(2) - try { - Fastify({ http2: true }) - t.assert.fail('fastify did not throw expected error') - } catch (err) { - t.assert.strictEqual(err.code, 'FST_ERR_HTTP2_INVALID_VERSION') - t.assert.strictEqual(err.message, 'HTTP2 is available only from node >= 8.8.1') - } -}) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index a4f83c5dad2..2fa3562539a 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -const expectedErrors = 85 +const expectedErrors = 84 test(`should expose ${expectedErrors} errors`, t => { t.plan(1) @@ -580,16 +580,6 @@ test('FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX', t => { t.assert.ok(error instanceof Error) }) -test('FST_ERR_HTTP2_INVALID_VERSION', t => { - t.plan(5) - const error = new errors.FST_ERR_HTTP2_INVALID_VERSION() - t.assert.strictEqual(error.name, 'FastifyError') - t.assert.strictEqual(error.code, 'FST_ERR_HTTP2_INVALID_VERSION') - t.assert.strictEqual(error.message, 'HTTP2 is available only from node >= 8.8.1') - t.assert.strictEqual(error.statusCode, 500) - t.assert.ok(error instanceof Error) -}) - test('FST_ERR_INIT_OPTS_INVALID', t => { t.plan(5) const error = new errors.FST_ERR_INIT_OPTS_INVALID() diff --git a/test/types/errors.test-d.ts b/test/types/errors.test-d.ts index cf1843321dc..497759b0dfc 100644 --- a/test/types/errors.test-d.ts +++ b/test/types/errors.test-d.ts @@ -52,7 +52,6 @@ expectAssignable(errorCodes.FST_ERR_SCH_DUPLICATE) expectAssignable(errorCodes.FST_ERR_SCH_VALIDATION_BUILD) expectAssignable(errorCodes.FST_ERR_SCH_SERIALIZATION_BUILD) expectAssignable(errorCodes.FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX) -expectAssignable(errorCodes.FST_ERR_HTTP2_INVALID_VERSION) expectAssignable(errorCodes.FST_ERR_INIT_OPTS_INVALID) expectAssignable(errorCodes.FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE) expectAssignable(errorCodes.FST_ERR_DUPLICATED_ROUTE) diff --git a/types/errors.d.ts b/types/errors.d.ts index add96123c87..d59bf6301c3 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -51,7 +51,6 @@ export type FastifyErrorCodes = Record< 'FST_ERR_SCH_VALIDATION_BUILD' | 'FST_ERR_SCH_SERIALIZATION_BUILD' | 'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX' | - 'FST_ERR_HTTP2_INVALID_VERSION' | 'FST_ERR_INIT_OPTS_INVALID' | 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE' | 'FST_ERR_DUPLICATED_ROUTE' | From a74b4ed1e98e1aa69e42eff75208acc9195637ec Mon Sep 17 00:00:00 2001 From: Lucas Holmquist Date: Wed, 30 Apr 2025 13:09:37 -0400 Subject: [PATCH 1002/1295] fix: update borp to 0.20.0. (#6091) This fixes an issue where the tests would fail to run if running on a system where there was just 1 CPU. See https://github.com/mcollina/borp/issues/160 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 99665f655a6..e4f4676f986 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "autocannon": "^8.0.0", - "borp": "^0.19.0", + "borp": "^0.20.0", "branch-comparer": "^1.1.0", "concurrently": "^9.1.2", "cross-env": "^7.0.3", From cc35911c5b2d9251b0c1603c5f821dd6f06583bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 15:06:32 +0000 Subject: [PATCH 1003/1295] chore: Bump fluent-json-schema from 5.0.0 to 6.0.0 (#6101) Bumps [fluent-json-schema](https://github.com/fastify/fluent-json-schema) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/fastify/fluent-json-schema/releases) - [Commits](https://github.com/fastify/fluent-json-schema/compare/v5.0.0...v6.0.0) --- updated-dependencies: - dependency-name: fluent-json-schema dependency-version: 6.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4f4676f986..eb3941cd60f 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "eslint": "^9.0.0", "fast-json-body": "^1.1.0", "fastify-plugin": "^5.0.0", - "fluent-json-schema": "^5.0.0", + "fluent-json-schema": "^6.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", "joi": "^17.12.3", From 53ca71a67596a892e746d42ac8ca40bfb44df9fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 15:08:00 +0000 Subject: [PATCH 1004/1295] chore: Bump tsd in the dev-dependencies-typescript group (#6100) Bumps the dev-dependencies-typescript group with 1 update: [tsd](https://github.com/tsdjs/tsd). Updates `tsd` from 0.31.2 to 0.32.0 - [Release notes](https://github.com/tsdjs/tsd/releases) - [Commits](https://github.com/tsdjs/tsd/compare/v0.31.2...v0.32.0) --- updated-dependencies: - dependency-name: tsd dependency-version: 0.32.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies-typescript ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb3941cd60f..ac8f35e8e42 100644 --- a/package.json +++ b/package.json @@ -192,7 +192,7 @@ "simple-get": "^4.0.1", "split2": "^4.2.0", "tap": "^21.0.0", - "tsd": "^0.31.0", + "tsd": "^0.32.0", "typescript": "~5.8.2", "undici": "^6.13.0", "vary": "^1.1.2", From 5fd2a3c031670d43bc4f12360bc5389149dbc52f Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 1 May 2025 23:24:17 +0200 Subject: [PATCH 1005/1295] test: migrated decorator.test.js from tap to node:test (#5957) * test: migrated decorator.test.js from tap to node:test * test: update test * test: update test --- test/decorator.test.js | 763 +++++++++++++++++++++++------------------ 1 file changed, 422 insertions(+), 341 deletions(-) diff --git a/test/decorator.test.js b/test/decorator.test.js index 942a7e48097..ad8fa51b061 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test, describe } = require('node:test') const Fastify = require('..') const fp = require('fastify-plugin') const sget = require('simple-get').concat @@ -10,87 +9,96 @@ const symbols = require('../lib/symbols.js') test('server methods should exist', t => { t.plan(2) const fastify = Fastify() - t.ok(fastify.decorate) - t.ok(fastify.hasDecorator) + t.assert.ok(fastify.decorate) + t.assert.ok(fastify.hasDecorator) }) -test('should check if the given decoration already exist when null', t => { +test('should check if the given decoration already exist when null', (t, done) => { t.plan(1) const fastify = Fastify() fastify.decorate('null', null) fastify.ready(() => { - t.ok(fastify.hasDecorator('null')) + t.assert.ok(fastify.hasDecorator('null')) + done() }) }) -test('server methods should be encapsulated via .register', t => { +test('server methods should be encapsulated via .register', (t, done) => { t.plan(2) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.decorate('test', () => {}) - t.ok(instance.test) + t.assert.ok(instance.test) done() }) fastify.ready(() => { - t.notOk(fastify.test) + t.assert.strictEqual(fastify.test, undefined) + done() }) }) -test('hasServerMethod should check if the given method already exist', t => { +test('hasServerMethod should check if the given method already exist', (t, done) => { t.plan(2) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.decorate('test', () => {}) - t.ok(instance.hasDecorator('test')) + t.assert.ok(instance.hasDecorator('test')) done() }) fastify.ready(() => { - t.notOk(fastify.hasDecorator('test')) + t.assert.strictEqual(fastify.hasDecorator('test'), false) + done() }) }) -test('decorate should throw if a declared dependency is not present', t => { +test('decorate should throw if a declared dependency is not present', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, opts, done) => { try { instance.decorate('test', () => {}, ['dependency']) - t.fail() + t.assert.fail() } catch (e) { - t.same(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') - t.same(e.message, 'The decorator is missing dependency \'dependency\'.') + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'dependency\'.') } done() }) - fastify.ready(() => t.pass()) + fastify.ready(() => { + t.assert.ok('ready') + done() + }) }) -test('decorate should throw if declared dependency is not array', t => { +test('decorate should throw if declared dependency is not array', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, opts, done) => { try { instance.decorate('test', () => {}, {}) - t.fail() + t.assert.fail() } catch (e) { - t.same(e.code, 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE') - t.same(e.message, 'The dependencies of decorator \'test\' must be of type Array.') + t.assert.strictEqual(e.code, 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE') + t.assert.strictEqual(e.message, 'The dependencies of decorator \'test\' must be of type Array.') } done() }) - fastify.ready(() => t.pass()) + fastify.ready(() => { + t.assert.ok('ready') + done() + }) }) // issue #777 -test('should pass error for missing request decorator', t => { +test('should pass error for missing request decorator', (t, done) => { t.plan(2) const fastify = Fastify() @@ -104,12 +112,13 @@ test('should pass error for missing request decorator', t => { fastify .register(plugin) .ready((err) => { - t.type(err, Error) - t.match(err, /The decorator 'foo'/) + t.assert.ok(err instanceof Error) + t.assert.ok(err.message.includes("'foo'")) + done() }) }) -test('decorateReply inside register', t => { +test('decorateReply inside register', (t, done) => { t.plan(11) const fastify = Fastify() @@ -117,7 +126,7 @@ test('decorateReply inside register', t => { instance.decorateReply('test', 'test') instance.get('/yes', (req, reply) => { - t.ok(reply.test, 'test exists') + t.assert.ok(reply.test, 'test exists') reply.send({ hello: 'world' }) }) @@ -125,37 +134,47 @@ test('decorateReply inside register', t => { }) fastify.get('/no', (req, reply) => { - t.notOk(reply.test) + t.assert.ok(!reply.test) reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/no' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) }) }) -test('decorateReply as plugin (inside .after)', t => { +test('decorateReply as plugin (inside .after)', (t, done) => { t.plan(11) const fastify = Fastify() @@ -165,7 +184,7 @@ test('decorateReply as plugin (inside .after)', t => { n() })).after(() => { instance.get('/yes', (req, reply) => { - t.ok(reply.test) + t.assert.ok(reply.test) reply.send({ hello: 'world' }) }) }) @@ -173,37 +192,47 @@ test('decorateReply as plugin (inside .after)', t => { }) fastify.get('/no', (req, reply) => { - t.notOk(reply.test) + t.assert.ok(!reply.test) reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/no' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) }) }) -test('decorateReply as plugin (outside .after)', t => { +test('decorateReply as plugin (outside .after)', (t, done) => { t.plan(11) const fastify = Fastify() @@ -214,44 +243,54 @@ test('decorateReply as plugin (outside .after)', t => { })) instance.get('/yes', (req, reply) => { - t.ok(reply.test) + t.assert.ok(reply.test) reply.send({ hello: 'world' }) }) done() }) fastify.get('/no', (req, reply) => { - t.notOk(reply.test) + t.assert.ok(!reply.test) reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/no' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) }) }) -test('decorateRequest inside register', t => { +test('decorateRequest inside register', (t, done) => { t.plan(11) const fastify = Fastify() @@ -259,7 +298,7 @@ test('decorateRequest inside register', t => { instance.decorateRequest('test', 'test') instance.get('/yes', (req, reply) => { - t.ok(req.test, 'test exists') + t.assert.ok(req.test, 'test exists') reply.send({ hello: 'world' }) }) @@ -267,37 +306,47 @@ test('decorateRequest inside register', t => { }) fastify.get('/no', (req, reply) => { - t.notOk(req.test) + t.assert.ok(!req.test) reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/no' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) }) }) -test('decorateRequest as plugin (inside .after)', t => { +test('decorateRequest as plugin (inside .after)', (t, done) => { t.plan(11) const fastify = Fastify() @@ -307,7 +356,7 @@ test('decorateRequest as plugin (inside .after)', t => { n() })).after(() => { instance.get('/yes', (req, reply) => { - t.ok(req.test) + t.assert.ok(req.test) reply.send({ hello: 'world' }) }) }) @@ -315,37 +364,47 @@ test('decorateRequest as plugin (inside .after)', t => { }) fastify.get('/no', (req, reply) => { - t.notOk(req.test) + t.assert.ok(!req.test) reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/no' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) }) }) -test('decorateRequest as plugin (outside .after)', t => { +test('decorateRequest as plugin (outside .after)', (t, done) => { t.plan(11) const fastify = Fastify() @@ -356,44 +415,54 @@ test('decorateRequest as plugin (outside .after)', t => { })) instance.get('/yes', (req, reply) => { - t.ok(req.test) + t.assert.ok(req.test) reply.send({ hello: 'world' }) }) done() }) fastify.get('/no', (req, reply) => { - t.notOk(req.test) + t.assert.ok(!req.test) reply.send({ hello: 'world' }) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/no' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completed() }) }) }) -test('decorators should be instance separated', t => { +test('decorators should be instance separated', (t, done) => { t.plan(1) const fastify1 = Fastify() @@ -408,140 +477,119 @@ test('decorators should be instance separated', t => { fastify1.decorateReply('test', 'foo') fastify2.decorateReply('test', 'foo') - t.pass() + t.assert.ok('Done') + done() }) -test('hasRequestDecorator', t => { +describe('hasRequestDecorator', () => { const requestDecoratorName = 'my-decorator-name' - t.test('is a function', t => { - t.plan(1) + test('is a function', async t => { const fastify = Fastify() - t.ok(fastify.hasRequestDecorator) + t.assert.ok(fastify.hasRequestDecorator) }) - t.test('should check if the given request decoration already exist', t => { - t.plan(2) + test('should check if the given request decoration already exist', async t => { const fastify = Fastify() - t.notOk(fastify.hasRequestDecorator(requestDecoratorName)) + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) fastify.decorateRequest(requestDecoratorName, 42) - t.ok(fastify.hasRequestDecorator(requestDecoratorName)) + t.assert.ok(fastify.hasRequestDecorator(requestDecoratorName)) }) - t.test('should check if the given request decoration already exist when null', t => { - t.plan(2) + test('should check if the given request decoration already exist when null', async t => { const fastify = Fastify() - t.notOk(fastify.hasRequestDecorator(requestDecoratorName)) + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) fastify.decorateRequest(requestDecoratorName, null) - t.ok(fastify.hasRequestDecorator(requestDecoratorName)) + t.assert.ok(fastify.hasRequestDecorator(requestDecoratorName)) }) - t.test('should be plugin encapsulable', t => { - t.plan(4) + test('should be plugin encapsulable', async t => { const fastify = Fastify() - t.notOk(fastify.hasRequestDecorator(requestDecoratorName)) + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) - fastify.register(function (fastify2, opts, done) { + await fastify.register(async function (fastify2, opts) { fastify2.decorateRequest(requestDecoratorName, 42) - t.ok(fastify2.hasRequestDecorator(requestDecoratorName)) - done() + t.assert.ok(fastify2.hasRequestDecorator(requestDecoratorName)) }) - t.notOk(fastify.hasRequestDecorator(requestDecoratorName)) + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) - fastify.ready(function () { - t.notOk(fastify.hasRequestDecorator(requestDecoratorName)) - }) + await fastify.ready() + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) }) - t.test('should be inherited', t => { - t.plan(2) + test('should be inherited', async t => { const fastify = Fastify() fastify.decorateRequest(requestDecoratorName, 42) - fastify.register(function (fastify2, opts, done) { - t.ok(fastify2.hasRequestDecorator(requestDecoratorName)) - done() + await fastify.register(async function (fastify2, opts) { + t.assert.ok(fastify2.hasRequestDecorator(requestDecoratorName)) }) - fastify.ready(function () { - t.ok(fastify.hasRequestDecorator(requestDecoratorName)) - }) + await fastify.ready() + t.assert.ok(fastify.hasRequestDecorator(requestDecoratorName)) }) - - t.end() }) -test('hasReplyDecorator', t => { +describe('hasReplyDecorator', () => { const replyDecoratorName = 'my-decorator-name' - t.test('is a function', t => { - t.plan(1) + test('is a function', async t => { const fastify = Fastify() - t.ok(fastify.hasReplyDecorator) + t.assert.ok(fastify.hasReplyDecorator) }) - t.test('should check if the given reply decoration already exist', t => { - t.plan(2) + test('should check if the given reply decoration already exist', async t => { const fastify = Fastify() - t.notOk(fastify.hasReplyDecorator(replyDecoratorName)) + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) fastify.decorateReply(replyDecoratorName, 42) - t.ok(fastify.hasReplyDecorator(replyDecoratorName)) + t.assert.ok(fastify.hasReplyDecorator(replyDecoratorName)) }) - t.test('should check if the given reply decoration already exist when null', t => { - t.plan(2) + test('should check if the given reply decoration already exist when null', async t => { const fastify = Fastify() - t.notOk(fastify.hasReplyDecorator(replyDecoratorName)) + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) fastify.decorateReply(replyDecoratorName, null) - t.ok(fastify.hasReplyDecorator(replyDecoratorName)) + t.assert.ok(fastify.hasReplyDecorator(replyDecoratorName)) }) - t.test('should be plugin encapsulable', t => { - t.plan(4) + test('should be plugin encapsulable', async t => { const fastify = Fastify() - t.notOk(fastify.hasReplyDecorator(replyDecoratorName)) + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) - fastify.register(function (fastify2, opts, done) { + await fastify.register(async function (fastify2, opts) { fastify2.decorateReply(replyDecoratorName, 42) - t.ok(fastify2.hasReplyDecorator(replyDecoratorName)) - done() + t.assert.ok(fastify2.hasReplyDecorator(replyDecoratorName)) }) - t.notOk(fastify.hasReplyDecorator(replyDecoratorName)) + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) - fastify.ready(function () { - t.notOk(fastify.hasReplyDecorator(replyDecoratorName)) - }) + await fastify.ready() + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) }) - t.test('should be inherited', t => { - t.plan(2) + test('should be inherited', async t => { const fastify = Fastify() fastify.decorateReply(replyDecoratorName, 42) - fastify.register(function (fastify2, opts, done) { - t.ok(fastify2.hasReplyDecorator(replyDecoratorName)) - done() + await fastify.register(async function (fastify2, opts) { + t.assert.ok(fastify2.hasReplyDecorator(replyDecoratorName)) }) - fastify.ready(function () { - t.ok(fastify.hasReplyDecorator(replyDecoratorName)) - }) + await fastify.ready() + t.assert.ok(fastify.hasReplyDecorator(replyDecoratorName)) }) - - t.end() }) -test('should register properties via getter/setter objects', t => { +test('should register properties via getter/setter objects', (t, done) => { t.plan(3) const fastify = Fastify() @@ -551,17 +599,18 @@ test('should register properties via getter/setter objects', t => { return 'a getter' } }) - t.ok(instance.test) - t.ok(instance.test, 'a getter') + t.assert.ok(instance.test) + t.assert.ok(instance.test, 'a getter') done() }) fastify.ready(() => { - t.notOk(fastify.test) + t.assert.ok(!fastify.test) + done() }) }) -test('decorateRequest should work with getter/setter', t => { +test('decorateRequest should work with getter/setter', (t, done) => { t.plan(5) const fastify = Fastify() @@ -580,24 +629,34 @@ test('decorateRequest should work with getter/setter', t => { }) fastify.get('/not-decorated', (req, res) => { - t.notOk(req.test) + t.assert.ok(!req.test) res.send() }) + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + fastify.ready(() => { fastify.inject({ url: '/req-decorated-get-set' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { test: 'a getter' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { test: 'a getter' }) + completed() }) fastify.inject({ url: '/not-decorated' }, (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok('ok', 'not decorated') + completed() }) }) }) -test('decorateReply should work with getter/setter', t => { +test('decorateReply should work with getter/setter', (t, done) => { t.plan(5) const fastify = Fastify() @@ -616,39 +675,49 @@ test('decorateReply should work with getter/setter', t => { }) fastify.get('/not-decorated', (req, res) => { - t.notOk(res.test) + t.assert.ok(!res.test) res.send() }) + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } fastify.ready(() => { fastify.inject({ url: '/res-decorated-get-set' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { test: 'a getter' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { test: 'a getter' }) + completed() }) fastify.inject({ url: '/not-decorated' }, (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok('ok') + completed() }) }) }) -test('should register empty values', t => { +test('should register empty values', (t, done) => { t.plan(2) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.decorate('test', null) - t.ok(Object.hasOwn(instance, 'test')) + t.assert.ok(Object.hasOwn(instance, 'test')) done() }) fastify.ready(() => { - t.notOk(fastify.test) + t.assert.ok(!fastify.test) + done() }) }) -test('nested plugins can override things', t => { +test('nested plugins can override things', (t, done) => { t.plan(6) const fastify = Fastify() @@ -663,20 +732,21 @@ test('nested plugins can override things', t => { instance.decorateRequest('test', func) instance.decorateReply('test', func) - t.equal(instance.test, func) - t.equal(instance[symbols.kRequest].prototype.test, func) - t.equal(instance[symbols.kReply].prototype.test, func) + t.assert.strictEqual(instance.test, func) + t.assert.strictEqual(instance[symbols.kRequest].prototype.test, func) + t.assert.strictEqual(instance[symbols.kReply].prototype.test, func) done() }) fastify.ready(() => { - t.equal(fastify.test, rootFunc) - t.equal(fastify[symbols.kRequest].prototype.test, rootFunc) - t.equal(fastify[symbols.kReply].prototype.test, rootFunc) + t.assert.strictEqual(fastify.test, rootFunc) + t.assert.strictEqual(fastify[symbols.kRequest].prototype.test, rootFunc) + t.assert.strictEqual(fastify[symbols.kReply].prototype.test, rootFunc) + done() }) }) -test('a decorator should addSchema to all the encapsulated tree', t => { +test('a decorator should addSchema to all the encapsulated tree', (t, done) => { t.plan(1) const fastify = Fastify() @@ -700,10 +770,13 @@ test('a decorator should addSchema to all the encapsulated tree', t => { done() }) - fastify.ready(t.error) + fastify.ready(() => { + t.assert.ifError() + done() + }) }) -test('after can access to a decorated instance and previous plugin decoration', t => { +test('after can access to a decorated instance and previous plugin decoration', (t, done) => { t.plan(11) const TEST_VALUE = {} const OTHER_TEST_VALUE = {} @@ -716,38 +789,39 @@ test('after can access to a decorated instance and previous plugin decoration', done() })).after(function (err, instance, done) { - t.error(err) - t.equal(instance.test, TEST_VALUE) + t.assert.ifError(err) + t.assert.strictEqual(instance.test, TEST_VALUE) instance.decorate('test2', OTHER_TEST_VALUE) done() }) fastify.register(fp(function (instance, options, done) { - t.equal(instance.test, TEST_VALUE) - t.equal(instance.test2, OTHER_TEST_VALUE) + t.assert.strictEqual(instance.test, TEST_VALUE) + t.assert.strictEqual(instance.test2, OTHER_TEST_VALUE) instance.decorate('test3', NEW_TEST_VALUE) done() })).after(function (err, instance, done) { - t.error(err) - t.equal(instance.test, TEST_VALUE) - t.equal(instance.test2, OTHER_TEST_VALUE) - t.equal(instance.test3, NEW_TEST_VALUE) + t.assert.ifError(err) + t.assert.strictEqual(instance.test, TEST_VALUE) + t.assert.strictEqual(instance.test2, OTHER_TEST_VALUE) + t.assert.strictEqual(instance.test3, NEW_TEST_VALUE) done() }) fastify.get('/', function (req, res) { - t.equal(this.test, TEST_VALUE) - t.equal(this.test2, OTHER_TEST_VALUE) + t.assert.strictEqual(this.test, TEST_VALUE) + t.assert.strictEqual(this.test2, OTHER_TEST_VALUE) res.send({}) }) fastify.inject('/') .then(response => { - t.equal(response.statusCode, 200) + t.assert.strictEqual(response.statusCode, 200) + done() }) }) @@ -764,24 +838,24 @@ test('decorate* should throw if called after ready', async t => { await fastify.listen({ port: 0 }) try { fastify.decorate('test', true) - t.fail('should not decorate') + t.assert.fail('should not decorate') } catch (err) { - t.same(err.code, 'FST_ERR_DEC_AFTER_START') - t.same(err.message, "The decorator 'test' has been added after start!") + t.assert.strictEqual(err.code, 'FST_ERR_DEC_AFTER_START') + t.assert.strictEqual(err.message, "The decorator 'test' has been added after start!") } try { fastify.decorateRequest('test', true) - t.fail('should not decorate') + t.assert.fail('should not decorate') } catch (e) { - t.same(e.code, 'FST_ERR_DEC_AFTER_START') - t.same(e.message, "The decorator 'test' has been added after start!") + t.assert.strictEqual(e.code, 'FST_ERR_DEC_AFTER_START') + t.assert.strictEqual(e.message, "The decorator 'test' has been added after start!") } try { fastify.decorateReply('test', true) - t.fail('should not decorate') + t.assert.fail('should not decorate') } catch (e) { - t.same(e.code, 'FST_ERR_DEC_AFTER_START') - t.same(e.message, "The decorator 'test' has been added after start!") + t.assert.strictEqual(e.code, 'FST_ERR_DEC_AFTER_START') + t.assert.strictEqual(e.message, "The decorator 'test' has been added after start!") } await fastify.close() }) @@ -792,10 +866,10 @@ test('decorate* should emit error if an array is passed', t => { const fastify = Fastify() try { fastify.decorateRequest('test_array', []) - t.fail('should not decorate') + t.assert.fail('should not decorate') } catch (err) { - t.same(err.code, 'FST_ERR_DEC_REFERENCE_TYPE') - t.same(err.message, "The decorator 'test_array' of type 'object' is a reference type. Use the { getter, setter } interface instead.") + t.assert.strictEqual(err.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.assert.strictEqual(err.message, "The decorator 'test_array' of type 'object' is a reference type. Use the { getter, setter } interface instead.") } }) @@ -806,7 +880,7 @@ test('server.decorate should not emit error if reference type is passed', async fastify.decorate('test_array', []) fastify.decorate('test_object', {}) await fastify.ready() - t.pass('Done') + t.assert.ok('Done') }) test('decorate* should emit warning if object type is passed', t => { @@ -815,10 +889,10 @@ test('decorate* should emit warning if object type is passed', t => { const fastify = Fastify() try { fastify.decorateRequest('test_object', { foo: 'bar' }) - t.fail('should not decorate') + t.assert.fail('should not decorate') } catch (err) { - t.same(err.code, 'FST_ERR_DEC_REFERENCE_TYPE') - t.same(err.message, "The decorator 'test_object' of type 'object' is a reference type. Use the { getter, setter } interface instead.") + t.assert.strictEqual(err.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.assert.strictEqual(err.message, "The decorator 'test_object' of type 'object' is a reference type. Use the { getter, setter } interface instead.") } }) @@ -833,7 +907,7 @@ test('decorate* should not emit warning if object with getter/setter is passed', return 'a getter' } }) - t.end('Done') + t.assert.ok('Done') }) test('decorateRequest with getter/setter can handle encapsulation', async t => { @@ -850,22 +924,22 @@ test('decorateRequest with getter/setter can handle encapsulation', async t => { }) fastify.get('/', async function (req, reply) { - t.same(req.test_getter_setter, {}, 'a getter') + t.assert.deepStrictEqual(req.test_getter_setter, {}, 'a getter') req.test_getter_setter.a = req.id - t.same(req.test_getter_setter, { a: req.id }) + t.assert.deepStrictEqual(req.test_getter_setter, { a: req.id }) }) fastify.addHook('onResponse', async function hook (req, reply) { - t.same(req.test_getter_setter, { a: req.id }) + t.assert.deepStrictEqual(req.test_getter_setter, { a: req.id }) }) await Promise.all([ - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)) + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)) ]) }) @@ -883,22 +957,22 @@ test('decorateRequest with getter/setter can handle encapsulation with arrays', }) fastify.get('/', async function (req, reply) { - t.same(req.my_array, []) + t.assert.deepStrictEqual(req.my_array, []) req.my_array.push(req.id) - t.same(req.my_array, [req.id]) + t.assert.deepStrictEqual(req.my_array, [req.id]) }) fastify.addHook('onResponse', async function hook (req, reply) { - t.same(req.my_array, [req.id]) + t.assert.deepStrictEqual(req.my_array, [req.id]) }) await Promise.all([ - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)), - fastify.inject('/').then(res => t.same(res.statusCode, 200)) + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)) ]) }) @@ -915,7 +989,7 @@ test('decorate* should not emit error if string,bool,numbers are passed', t => { fastify.decorateReply('test_number', 42) fastify.decorateReply('test_null', null) fastify.decorateReply('test_undefined', undefined) - t.end('Done') + t.assert.ok('Done') }) test('Request/reply decorators should be able to access the server instance', async t => { @@ -948,12 +1022,12 @@ test('Request/reply decorators should be able to access the server instance', as // ---- function rootAssert () { - t.equal(this.server, server) + t.assert.strictEqual(this.server, server) } function nestedAssert () { - t.not(this.server, server) - t.equal(this.server.foo, 'bar') + t.assert.notStrictEqual(this.server, server) + t.assert.strictEqual(this.server.foo, 'bar') } }) @@ -990,94 +1064,97 @@ test('plugin required decorators', async t => { await app.ready() }) -test('decorateRequest/decorateReply empty string', t => { +test('decorateRequest/decorateReply empty string', (t, done) => { t.plan(7) const fastify = Fastify() fastify.decorateRequest('test', '') fastify.decorateReply('test2', '') fastify.get('/yes', (req, reply) => { - t.equal(req.test, '') - t.equal(reply.test2, '') + t.assert.strictEqual(req.test, '') + t.assert.strictEqual(reply.test2, '') reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) -test('decorateRequest/decorateReply is undefined', t => { +test('decorateRequest/decorateReply is undefined', (t, done) => { t.plan(7) const fastify = Fastify() fastify.decorateRequest('test', undefined) fastify.decorateReply('test2', undefined) fastify.get('/yes', (req, reply) => { - t.equal(req.test, undefined) - t.equal(reply.test2, undefined) + t.assert.strictEqual(req.test, undefined) + t.assert.strictEqual(reply.test2, undefined) reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) -test('decorateRequest/decorateReply is not set to a value', t => { +test('decorateRequest/decorateReply is not set to a value', (t, done) => { t.plan(7) const fastify = Fastify() fastify.decorateRequest('test') fastify.decorateReply('test2') fastify.get('/yes', (req, reply) => { - t.equal(req.test, undefined) - t.equal(reply.test2, undefined) + t.assert.strictEqual(req.test, undefined) + t.assert.strictEqual(reply.test2, undefined) reply.send({ hello: 'world' }) }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/yes' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + done() }) }) }) -test('decorateRequest with dependencies', (t) => { +test('decorateRequest with dependencies', (t, done) => { t.plan(2) const app = Fastify() @@ -1091,12 +1168,13 @@ test('decorateRequest with dependencies', (t) => { app.hasDecorator('decorator1') && app.hasRequestDecorator('decorator1') ) { - t.doesNotThrow(() => app.decorateRequest('decorator2', decorator2, ['decorator1'])) - t.ok(app.hasRequestDecorator('decorator2')) + t.assert.doesNotThrow(() => app.decorateRequest('decorator2', decorator2, ['decorator1'])) + t.assert.ok(app.hasRequestDecorator('decorator2')) + done() } }) -test('decorateRequest with dependencies (functions)', (t) => { +test('decorateRequest with dependencies (functions)', (t, done) => { t.plan(2) const app = Fastify() @@ -1110,12 +1188,13 @@ test('decorateRequest with dependencies (functions)', (t) => { app.hasDecorator('decorator1') && app.hasRequestDecorator('decorator1') ) { - t.doesNotThrow(() => app.decorateRequest('decorator2', decorator2, ['decorator1'])) - t.ok(app.hasRequestDecorator('decorator2')) + t.assert.doesNotThrow(() => app.decorateRequest('decorator2', decorator2, ['decorator1'])) + t.assert.ok(app.hasRequestDecorator('decorator2')) + done() } }) -test('chain of decorators on Request', async (t) => { +test('chain of decorators on Request', async t => { const fastify = Fastify() fastify.register(fp(async function (fastify) { fastify.decorateRequest('foo', 'toto') @@ -1159,32 +1238,32 @@ test('chain of decorators on Request', async (t) => { { const response = await fastify.inject('/foo') - t.equal(response.body, 'toto') + t.assert.strictEqual(response.body, 'toto') } { const response = await fastify.inject('/bar') - t.equal(response.body, 'tata') + t.assert.strictEqual(response.body, 'tata') } { const response = await fastify.inject('/plugin2/foo') - t.equal(response.body, 'toto') + t.assert.strictEqual(response.body, 'toto') } { const response = await fastify.inject('/plugin2/bar') - t.equal(response.body, 'tata') + t.assert.strictEqual(response.body, 'tata') } { const response = await fastify.inject('/plugin2/plugin3/foo') - t.equal(response.body, 'toto') + t.assert.strictEqual(response.body, 'toto') } { const response = await fastify.inject('/plugin2/plugin3/bar') - t.equal(response.body, 'tata') + t.assert.strictEqual(response.body, 'tata') } }) @@ -1232,36 +1311,36 @@ test('chain of decorators on Reply', async (t) => { { const response = await fastify.inject('/foo') - t.equal(response.body, 'toto') + t.assert.strictEqual(response.body, 'toto') } { const response = await fastify.inject('/bar') - t.equal(response.body, 'tata') + t.assert.strictEqual(response.body, 'tata') } { const response = await fastify.inject('/plugin2/foo') - t.equal(response.body, 'toto') + t.assert.strictEqual(response.body, 'toto') } { const response = await fastify.inject('/plugin2/bar') - t.equal(response.body, 'tata') + t.assert.strictEqual(response.body, 'tata') } { const response = await fastify.inject('/plugin2/plugin3/foo') - t.equal(response.body, 'toto') + t.assert.strictEqual(response.body, 'toto') } { const response = await fastify.inject('/plugin2/plugin3/bar') - t.equal(response.body, 'tata') + t.assert.strictEqual(response.body, 'tata') } }) -test('getDecorator should return the decorator', t => { +test('getDecorator should return the decorator', (t, done) => { t.plan(12) const fastify = Fastify() @@ -1269,10 +1348,10 @@ test('getDecorator should return the decorator', t => { fastify.decorateRequest('root', 'from_root_request') fastify.decorateReply('root', 'from_root_reply') - t.equal(fastify.getDecorator('root'), 'from_root') + t.assert.strictEqual(fastify.getDecorator('root'), 'from_root') fastify.get('/', async (req, res) => { - t.equal(req.getDecorator('root'), 'from_root_request') - t.equal(res.getDecorator('root'), 'from_root_reply') + t.assert.strictEqual(req.getDecorator('root'), 'from_root_request') + t.assert.strictEqual(res.getDecorator('root'), 'from_root_reply') res.send() }) @@ -1280,32 +1359,33 @@ test('getDecorator should return the decorator', t => { fastify.register((child) => { child.decorate('child', 'from_child') - t.equal(child.getDecorator('child'), 'from_child') - t.equal(child.getDecorator('root'), 'from_root') + t.assert.strictEqual(child.getDecorator('child'), 'from_child') + t.assert.strictEqual(child.getDecorator('root'), 'from_root') child.get('/child', async (req, res) => { - t.equal(req.getDecorator('root'), 'from_root_request') - t.equal(res.getDecorator('root'), 'from_root_reply') + t.assert.strictEqual(req.getDecorator('root'), 'from_root_request') + t.assert.strictEqual(res.getDecorator('root'), 'from_root_reply') res.send() }) }) fastify.ready((err) => { - t.error(err) + t.assert.ifError(err) fastify.inject({ url: '/' }, (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok(true) }) fastify.inject({ url: '/child' }, (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok(true) + done() }) }) }) -test('getDecorator should return function decorators with expected binded context', t => { +test('getDecorator should return function decorators with expected binded context', (t, done) => { t.plan(12) const fastify = Fastify() @@ -1324,49 +1404,48 @@ test('getDecorator should return function decorators with expected binded contex return this }) - t.same(child.getDecorator('a')(), child) + t.assert.deepEqual(child.getDecorator('a')(), child) child.get('/child', async (req, res) => { - t.same(req.getDecorator('b')(), req) - t.same(res.getDecorator('c')(), res) + t.assert.deepEqual(req.getDecorator('b')(), req) + t.assert.deepEqual(res.getDecorator('c')(), res) res.send() }) }) - t.same(fastify.getDecorator('a')(), fastify) + t.assert.deepEqual(fastify.getDecorator('a')(), fastify) fastify.get('/', async (req, res) => { - t.same(req.getDecorator('b')(), req) - t.same(res.getDecorator('c')(), res) - + t.assert.deepEqual(req.getDecorator('b')(), req) + t.assert.deepEqual(res.getDecorator('c')(), res) res.send() }) fastify.ready((err) => { - t.error(err) + t.assert.ifError(err) fastify.inject({ url: '/' }, (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok(true, 'passed') }) fastify.inject({ url: '/child' }, (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok(true, 'passed') + done() }) - - t.pass() + t.assert.ok(true, 'passed') }) }) -test('getDecorator should only return decorators existing in the scope', t => { +test('getDecorator should only return decorators existing in the scope', (t, done) => { t.plan(9) function assertsThrowOnUndeclaredDecorator (notDecorated, instanceType) { try { notDecorated.getDecorator('foo') - t.fail() + t.assert.fail() } catch (e) { - t.same(e.code, 'FST_ERR_DEC_UNDECLARED') - t.same(e.message, `No decorator 'foo' has been declared on ${instanceType}.`) + t.assert.deepEqual(e.code, 'FST_ERR_DEC_UNDECLARED') + t.assert.deepEqual(e.message, `No decorator 'foo' has been declared on ${instanceType}.`) } } @@ -1385,17 +1464,18 @@ test('getDecorator should only return decorators existing in the scope', t => { }) fastify.ready((err) => { - t.error(err) + t.assert.ifError(err) assertsThrowOnUndeclaredDecorator(fastify, 'instance') fastify.inject({ url: '/' }, (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok(true, 'passed') + done() }) }) }) -test('Request.setDecorator should update an existing decorator', t => { +test('Request.setDecorator should update an existing decorator', (t, done) => { t.plan(7) const fastify = Fastify() @@ -1408,25 +1488,26 @@ test('Request.setDecorator should update an existing decorator', t => { }) try { req.setDecorator('foo', { user: 'Jean' }) - t.fail() + t.assert.fail() } catch (e) { - t.same(e.code, 'FST_ERR_DEC_UNDECLARED') - t.same(e.message, "No decorator 'foo' has been declared on request.") + t.assert.deepEqual(e.code, 'FST_ERR_DEC_UNDECLARED') + t.assert.deepEqual(e.message, "No decorator 'foo' has been declared on request.") } }) fastify.get('/', async (req, res) => { - t.strictSame(req.getDecorator('session'), { user: 'Jean' }) - t.same(req.getDecorator('utility')(), req) + t.assert.deepEqual(req.getDecorator('session'), { user: 'Jean' }) + t.assert.deepEqual(req.getDecorator('utility')(), req) res.send() }) fastify.ready((err) => { - t.error(err) + t.assert.ifError(err) fastify.inject({ url: '/' }, (err, res) => { - t.error(err) - t.pass() + t.assert.ifError(err) + t.assert.ok(true, 'passed') + done() }) }) }) From 08fdb547fef70e80d19f0e4b1e6e11291dacc702 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Fri, 2 May 2025 09:37:17 +0200 Subject: [PATCH 1006/1295] test: stabilize pipelining shutdown test with controlled close timing (#6099) --- test/close-pipelining.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index bbadbb02de7..34dd5053d5f 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -59,10 +59,11 @@ test('Should close the socket abruptly - pipelining - return503OnClosing: false' instance.request({ path: '/', method: 'GET' }) ]) - t.assert.strictEqual(responses[0].status, 'fulfilled') - t.assert.strictEqual(responses[1].status, 'fulfilled') - t.assert.strictEqual(responses[2].status, 'rejected') - t.assert.strictEqual(responses[3].status, 'rejected') + const fulfilled = responses.filter(r => r.status === 'fulfilled') + const rejected = responses.filter(r => r.status === 'rejected') + + t.assert.strictEqual(fulfilled.length, 2) + t.assert.strictEqual(rejected.length, 2) await instance.close() }) From 5381a92a57c4bb78aba30407754c7d77405117d8 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 3 May 2025 16:50:44 +0200 Subject: [PATCH 1007/1295] test: migrated output-validation.test.js from tap to node:test (#6076) --- test/output-validation.test.js | 119 ++++++++++++++------------------- 1 file changed, 49 insertions(+), 70 deletions(-) diff --git a/test/output-validation.test.js b/test/output-validation.test.js index 69d99ebe494..ee4e60937f5 100644 --- a/test/output-validation.test.js +++ b/test/output-validation.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const fastify = require('..')() @@ -34,9 +33,9 @@ test('shorthand - output string', t => { fastify.get('/string', opts, function (req, reply) { reply.code(200).send({ hello: 'world' }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -46,9 +45,9 @@ test('shorthand - output number', t => { fastify.get('/number', opts, function (req, reply) { reply.code(201).send({ hello: 55 }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -59,9 +58,9 @@ test('wrong object for schema - output', t => { // will send { } reply.code(201).send({ hello: 'world' }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -72,9 +71,9 @@ test('empty response', t => { fastify.get('/empty', opts, function (req, reply) { reply.code(204).send() }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) @@ -84,80 +83,60 @@ test('unlisted response code', t => { fastify.get('/400', opts, function (req, reply) { reply.code(400).send({ hello: 'DOOM' }) }) - t.pass() + t.assert.ok(true) } catch (e) { - t.fail() + t.assert.fail() } }) -fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) +test('start server and run tests', async (t) => { + await fastify.listen({ port: 0 }) + const baseUrl = 'http://localhost:' + fastify.server.address().port + t.after(() => fastify.close()) - test('shorthand - string get ok', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/string' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + function sgetAsync (opts) { + return new Promise((resolve, reject) => { + sget(opts, (err, res, body) => { + if (err) return reject(err) + resolve({ res, body }) + }) }) + } + + await test('shorthand - string get ok', async (t) => { + const { res, body } = await sgetAsync({ method: 'GET', url: `${baseUrl}/string` }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - test('shorthand - number get ok', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/number' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 201) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 55 }) - }) + await test('shorthand - number get ok', async (t) => { + const { res, body } = await sgetAsync({ method: 'GET', url: `${baseUrl}/number` }) + t.assert.strictEqual(res.statusCode, 201) + t.assert.strictEqual(res.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 55 }) }) - test('shorthand - wrong-object-for-schema', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/wrong-object-for-schema' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - statusCode: 500, - error: 'Internal Server Error', - message: 'The value "world" cannot be converted to a number.' - }) + await test('shorthand - wrong-object-for-schema', async (t) => { + const { res, body } = await sgetAsync({ method: 'GET', url: `${baseUrl}/wrong-object-for-schema` }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { + statusCode: 500, + error: 'Internal Server Error', + message: 'The value "world" cannot be converted to a number.' }) }) - test('shorthand - empty', t => { - t.plan(2) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/empty' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 204) - }) + await test('shorthand - empty', async (t) => { + const { res } = await sgetAsync({ method: 'GET', url: `${baseUrl}/empty` }) + t.assert.strictEqual(res.statusCode, 204) }) - test('shorthand - 400', t => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/400' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 400) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'DOOM' }) - }) + await test('shorthand - 400', async (t) => { + const { res, body } = await sgetAsync({ method: 'GET', url: `${baseUrl}/400` }) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'DOOM' }) }) }) From d4d3db2657235cf32cf827d87e3ef361ee7fdc13 Mon Sep 17 00:00:00 2001 From: Icaro Vieira Date: Mon, 5 May 2025 19:19:34 -0300 Subject: [PATCH 1008/1295] chore: remove tap from hooks-on ready file (#6080) --- test/hooks.on-ready.test.js | 202 ++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 92 deletions(-) diff --git a/test/hooks.on-ready.test.js b/test/hooks.on-ready.test.js index 80c7a3e4590..4e0379eb34f 100644 --- a/test/hooks.on-ready.test.js +++ b/test/hooks.on-ready.test.js @@ -1,41 +1,44 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const Fastify = require('../fastify') const immediate = require('node:util').promisify(setImmediate) -t.test('onReady should be called in order', t => { +test('onReady should be called in order', (t, done) => { t.plan(7) const fastify = Fastify() let order = 0 fastify.addHook('onReady', function (done) { - t.equal(order++, 0, 'called in root') - t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(order++, 0, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onReady', function (done) { - t.equal(order++, 1, 'called in childOne') - t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(order++, 1, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') done() }) childOne.register(async (childTwo, o) => { childTwo.addHook('onReady', async function () { await immediate() - t.equal(order++, 2, 'called in childTwo') - t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(order++, 2, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') }) }) }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -t.test('onReady should be called once', async (t) => { +test('onReady should be called once', async (t) => { const app = Fastify() let counter = 0 @@ -47,11 +50,11 @@ t.test('onReady should be called once', async (t) => { const result = await Promise.race(promises) - t.strictSame(result, 1, 'Should resolve in order') - t.equal(counter, 1, 'Should call onReady only once') + t.assert.strictEqual(result, 1, 'Should resolve in order') + t.assert.strictEqual(counter, 1, 'Should call onReady only once') }) -t.test('async onReady should be called in order', async t => { +test('async onReady should be called in order', async t => { t.plan(7) const fastify = Fastify() @@ -59,31 +62,31 @@ t.test('async onReady should be called in order', async t => { fastify.addHook('onReady', async function () { await immediate() - t.equal(order++, 0, 'called in root') - t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(order++, 0, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') }) fastify.register(async (childOne, o) => { childOne.addHook('onReady', async function () { await immediate() - t.equal(order++, 1, 'called in childOne') - t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(order++, 1, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') }) childOne.register(async (childTwo, o) => { childTwo.addHook('onReady', async function () { await immediate() - t.equal(order++, 2, 'called in childTwo') - t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(order++, 2, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') }) }) }) await fastify.ready() - t.pass('ready') + t.assert.ok('ready') }) -t.test('mix ready and onReady', async t => { +test('mix ready and onReady', async t => { t.plan(2) const fastify = Fastify() let order = 0 @@ -94,13 +97,13 @@ t.test('mix ready and onReady', async t => { }) await fastify.ready() - t.equal(order, 1) + t.assert.strictEqual(order, 1) await fastify.ready() - t.equal(order, 1, 'ready hooks execute once') + t.assert.strictEqual(order, 1, 'ready hooks execute once') }) -t.test('listen and onReady order', async t => { +test('listen and onReady order', async t => { t.plan(9) const fastify = Fastify() @@ -127,19 +130,19 @@ t.test('listen and onReady order', async t => { fastify.addHook('onReady', checkOrder.bind(null, 3)) await fastify.ready() - t.pass('trigger the onReady') + t.assert.ok('trigger the onReady') await fastify.listen({ port: 0 }) - t.pass('do not trigger the onReady') + t.assert.ok('do not trigger the onReady') await fastify.close() function checkOrder (shouldbe) { - t.equal(order, shouldbe) + t.assert.strictEqual(order, shouldbe) order++ } }) -t.test('multiple ready calls', async t => { +test('multiple ready calls', async t => { t.plan(11) const fastify = Fastify() @@ -154,7 +157,7 @@ t.test('multiple ready calls', async t => { subinstance.addHook('onReady', checkOrder.bind(null, 7)) }) - t.equal(order, 0, 'ready and hooks not triggered yet') + t.assert.strictEqual(order, 0, 'ready and hooks not triggered yet') order++ }) @@ -163,139 +166,145 @@ t.test('multiple ready calls', async t => { fastify.addHook('onReady', checkOrder.bind(null, 5)) await fastify.ready() - t.pass('trigger the onReady') + t.assert.ok('trigger the onReady') await fastify.ready() - t.pass('do not trigger the onReady') + t.assert.ok('do not trigger the onReady') await fastify.ready() - t.pass('do not trigger the onReady') + t.assert.ok('do not trigger the onReady') function checkOrder (shouldbe) { - t.equal(order, shouldbe) + t.assert.strictEqual(order, shouldbe) order++ } }) -t.test('onReady should manage error in sync', t => { +test('onReady should manage error in sync', (t, done) => { t.plan(4) const fastify = Fastify() fastify.addHook('onReady', function (done) { - t.pass('called in root') + t.assert.ok('called in root') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onReady', function (done) { - t.pass('called in childOne') + t.assert.ok('called in childOne') done(new Error('FAIL ON READY')) }) childOne.register(async (childTwo, o) => { childTwo.addHook('onReady', async function () { - t.fail('should not be called') + t.assert.fail('should not be called') }) }) }) fastify.ready(err => { - t.ok(err) - t.equal(err.message, 'FAIL ON READY') + t.assert.ok(err) + t.assert.strictEqual(err.message, 'FAIL ON READY') + done() }) }) -t.test('onReady should manage error in async', t => { +test('onReady should manage error in async', (t, done) => { t.plan(4) const fastify = Fastify() fastify.addHook('onReady', function (done) { - t.pass('called in root') + t.assert.ok('called in root') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onReady', async function () { - t.pass('called in childOne') + t.assert.ok('called in childOne') throw new Error('FAIL ON READY') }) childOne.register(async (childTwo, o) => { childTwo.addHook('onReady', async function () { - t.fail('should not be called') + t.assert.fail('should not be called') }) }) }) fastify.ready(err => { - t.ok(err) - t.equal(err.message, 'FAIL ON READY') + t.assert.ok(err) + t.assert.strictEqual(err.message, 'FAIL ON READY') + done() }) }) -t.test('onReady should manage sync error', t => { +test('onReady should manage sync error', (t, done) => { t.plan(4) const fastify = Fastify() fastify.addHook('onReady', function (done) { - t.pass('called in root') + t.assert.ok('called in root') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onReady', function (done) { - t.pass('called in childOne') + t.assert.ok('called in childOne') throw new Error('FAIL UNWANTED SYNC EXCEPTION') }) childOne.register(async (childTwo, o) => { childTwo.addHook('onReady', async function () { - t.fail('should not be called') + t.assert.fail('should not be called') }) }) }) fastify.ready(err => { - t.ok(err) - t.equal(err.message, 'FAIL UNWANTED SYNC EXCEPTION') + t.assert.ok(err) + t.assert.strictEqual(err.message, 'FAIL UNWANTED SYNC EXCEPTION') + done() }) }) -t.test('onReady can not add decorators or application hooks', t => { +test('onReady can not add decorators or application hooks', (t, done) => { t.plan(3) const fastify = Fastify() fastify.addHook('onReady', function (done) { - t.pass('called in root') + t.assert.ok('called in root') fastify.decorate('test', () => {}) fastify.addHook('onReady', async function () { - t.fail('it will be not called') + t.assert.fail('it will be not called') }) done() }) fastify.addHook('onReady', function (done) { - t.ok(this.hasDecorator('test')) + t.assert.ok(this.hasDecorator('test')) done() }) - fastify.ready(err => { t.error(err) }) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) }) -t.test('onReady cannot add lifecycle hooks', t => { +test('onReady cannot add lifecycle hooks', (t, done) => { t.plan(5) const fastify = Fastify() fastify.addHook('onReady', function (done) { - t.pass('called in root') + t.assert.ok('called in root') try { fastify.addHook('onRequest', (request, reply, done) => {}) } catch (error) { - t.ok(error) - t.equal(error.message, 'Root plugin has already booted') + t.assert.ok(error) + t.assert.strictEqual(error.message, 'Root plugin has already booted') // TODO: look where the error pops up - t.equal(error.code, 'AVV_ERR_ROOT_PLG_BOOTED') + t.assert.strictEqual(error.code, 'AVV_ERR_ROOT_PLG_BOOTED') done(error) } }) @@ -303,93 +312,102 @@ t.test('onReady cannot add lifecycle hooks', t => { fastify.addHook('onRequest', (request, reply, done) => {}) fastify.get('/', async () => 'hello') - fastify.ready((err) => { t.ok(err) }) + fastify.ready((err) => { + t.assert.ok(err) + done() + }) }) -t.test('onReady throw loading error', t => { +test('onReady throw loading error', t => { t.plan(2) const fastify = Fastify() try { fastify.addHook('onReady', async function (done) {}) } catch (e) { - t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) -t.test('onReady does not call done', t => { +test('onReady does not call done', (t, done) => { t.plan(6) const fastify = Fastify({ pluginTimeout: 500 }) fastify.addHook('onReady', function someHookName (done) { - t.pass('called in root') + t.assert.ok('called in root') // done() // don't call done to test timeout }) fastify.ready(err => { - t.ok(err) - t.equal(err.message, 'A callback for \'onReady\' hook "someHookName" timed out. You may have forgotten to call \'done\' function or to resolve a Promise') - t.equal(err.code, 'FST_ERR_HOOK_TIMEOUT') - t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + t.assert.ok(err) + t.assert.strictEqual(err.message, 'A callback for \'onReady\' hook "someHookName" timed out. You may have forgotten to call \'done\' function or to resolve a Promise') + t.assert.strictEqual(err.code, 'FST_ERR_HOOK_TIMEOUT') + t.assert.ok(err.cause) + t.assert.strictEqual(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + done() }) }) -t.test('onReady execution order', t => { +test('onReady execution order', (t, done) => { t.plan(3) const fastify = Fastify({ }) let i = 0 - fastify.ready(() => { i++; t.equal(i, 1) }) - fastify.ready(() => { i++; t.equal(i, 2) }) - fastify.ready(() => { i++; t.equal(i, 3) }) + fastify.ready(() => { i++; t.assert.strictEqual(i, 1) }) + fastify.ready(() => { i++; t.assert.strictEqual(i, 2) }) + fastify.ready(() => { + i++ + t.assert.strictEqual(i, 3) + done() + }) }) -t.test('ready return the server with callback', t => { +test('ready return the server with callback', (t, done) => { t.plan(2) const fastify = Fastify() fastify.ready((err, instance) => { - t.error(err) - t.same(instance, fastify) + t.assert.ifError(err) + t.assert.deepStrictEqual(instance, fastify) + done() }) }) -t.test('ready return the server with Promise', t => { +test('ready return the server with Promise', async t => { t.plan(1) const fastify = Fastify() - fastify.ready() - .then(instance => { t.same(instance, fastify) }) - .catch(err => { t.fail(err) }) + await fastify.ready() + .then(instance => { t.assert.deepStrictEqual(instance, fastify) }) + .catch(err => { t.assert.fail(err) }) }) -t.test('ready return registered', t => { +test('ready return registered', async t => { t.plan(4) const fastify = Fastify() fastify.register((one, opts, done) => { - one.ready().then(itself => { t.same(itself, one) }) + one.ready().then(itself => { t.assert.deepStrictEqual(itself, one) }) done() }) fastify.register((two, opts, done) => { - two.ready().then(itself => { t.same(itself, two) }) + two.ready().then(itself => { t.assert.deepStrictEqual(itself, two) }) two.register((twoDotOne, opts, done) => { - twoDotOne.ready().then(itself => { t.same(itself, twoDotOne) }) + twoDotOne.ready().then(itself => { t.assert.deepStrictEqual(itself, twoDotOne) }) done() }) done() }) - fastify.ready() - .then(instance => { t.same(instance, fastify) }) - .catch(err => { t.fail(err) }) + await fastify.ready() + .then(instance => { t.assert.deepStrictEqual(instance, fastify) }) + .catch(err => { t.assert.fail(err) }) }) -t.test('do not crash with error in follow up onReady hook', async t => { +test('do not crash with error in follow up onReady hook', async t => { const fastify = Fastify() fastify.addHook('onReady', async function () { @@ -399,5 +417,5 @@ t.test('do not crash with error in follow up onReady hook', async t => { throw new Error('kaboom') }) - await t.rejects(fastify.ready()) + await t.assert.rejects(fastify.ready()) }) From 66db4293da210c9a5b6418fc1e21b8075d7c98a8 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Tue, 6 May 2025 23:18:53 +0200 Subject: [PATCH 1009/1295] test: mv hooks.on-listen from tap (#6086) * test: mv hooks.on-listen from tap * test: reintroduce plan --- test/hooks.on-listen.test.js | 494 ++++++++++++++++++----------------- 1 file changed, 255 insertions(+), 239 deletions(-) diff --git a/test/hooks.on-listen.test.js b/test/hooks.on-listen.test.js index 1a09f306c2c..d1791926fa7 100644 --- a/test/hooks.on-listen.test.js +++ b/test/hooks.on-listen.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test, before } = require('tap') +const { test, before } = require('node:test') const Fastify = require('../fastify') const fp = require('fastify-plugin') const split = require('split2') @@ -15,60 +15,63 @@ before(async function () { [localhost] = await helper.getLoopbackHost() }) -test('onListen should not be processed when .ready() is called', t => { +test('onListen should not be processed when .ready() is called', (t, testDone) => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.addHook('onListen', function (done) { - t.fail() + t.assert.fail() done() }) - fastify.ready(err => t.error(err)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('localhost onListen should be called in order', t => { +test('localhost onListen should be called in order', (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, '1st called in root') + t.assert.strictEqual(++order, 1, '1st called in root') done() }) fastify.addHook('onListen', function (done) { - t.equal(++order, 2, '2nd called in root') + t.assert.strictEqual(++order, 2, '2nd called in root') done() }) fastify.listen({ host: 'localhost', port: 0 - }) + }, testDone) }) test('localhost async onListen should be called in order', async t => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', async function () { - t.equal(++order, 1, '1st async called in root') + t.assert.strictEqual(++order, 1, '1st async called in root') }) fastify.addHook('onListen', async function () { - t.equal(++order, 2, '2nd async called in root') + t.assert.strictEqual(++order, 2, '2nd async called in root') }) await fastify.listen({ host: 'localhost', port: 0 }) - t.equal(order, 2, 'the onListen hooks are awaited') + t.assert.strictEqual(order, 2, 'the onListen hooks are awaited') }) test('localhost onListen sync should log errors as warnings and continue /1', async t => { @@ -82,30 +85,30 @@ test('localhost onListen sync should log errors as warnings and continue /1', as level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.equal(order, 2) - t.pass('Logged Error Message') + t.assert.strictEqual(order, 2) + t.assert.ok('Logged Error Message') } }) fastify.addHook('onListen', function (done) { - t.equal(++order, 1, '1st call') - t.pass('called in root') + t.assert.strictEqual(++order, 1, '1st call') + t.assert.ok('called in root') done() }) fastify.addHook('onListen', function (done) { - t.equal(++order, 2, '2nd call') - t.pass('called onListen error') + t.assert.strictEqual(++order, 2, '2nd call') + t.assert.ok('called onListen error') throw new Error('FAIL ON LISTEN') }) fastify.addHook('onListen', function (done) { - t.equal(++order, 3, '3rd call') - t.pass('onListen hooks continue after error') + t.assert.strictEqual(++order, 3, '3rd call') + t.assert.ok('onListen hooks continue after error') done() }) @@ -115,7 +118,7 @@ test('localhost onListen sync should log errors as warnings and continue /1', as }) }) -test('localhost onListen sync should log errors as warnings and continue /2', t => { +test('localhost onListen sync should log errors as warnings and continue /2', (t, testDone) => { t.plan(7) const stream = split(JSON.parse) const fastify = Fastify({ @@ -125,38 +128,38 @@ test('localhost onListen sync should log errors as warnings and continue /2', t level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) fastify.addHook('onListen', function (done) { - t.equal(++order, 1, '1st call') - t.pass('called in root') + t.assert.strictEqual(++order, 1, '1st call') + t.assert.ok('called in root') done() }) fastify.addHook('onListen', function (done) { - t.equal(++order, 2, '2nd call') - t.pass('called onListen error') + t.assert.strictEqual(++order, 2, '2nd call') + t.assert.ok('called onListen error') done(new Error('FAIL ON LISTEN')) }) fastify.addHook('onListen', function (done) { - t.equal(++order, 3, '3rd call') - t.pass('onListen hooks continue after error') + t.assert.strictEqual(++order, 3, '3rd call') + t.assert.ok('onListen hooks continue after error') done() }) fastify.listen({ host: 'localhost', port: 0 - }) + }, testDone) }) test('localhost onListen async should log errors as warnings and continue', async t => { @@ -169,25 +172,25 @@ test('localhost onListen async should log errors as warnings and continue', asyn level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) fastify.addHook('onListen', async function () { - t.pass('called in root') + t.assert.ok('called in root') }) fastify.addHook('onListen', async function () { - t.pass('called onListen error') + t.assert.ok('called onListen error') throw new Error('FAIL ON LISTEN') }) fastify.addHook('onListen', async function () { - t.pass('onListen hooks continue after error') + t.assert.ok('onListen hooks continue after error') }) await fastify.listen({ @@ -196,14 +199,14 @@ test('localhost onListen async should log errors as warnings and continue', asyn }) }) -test('localhost Register onListen hook after a plugin inside a plugin', t => { +test('localhost Register onListen hook after a plugin inside a plugin', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) done() @@ -211,12 +214,12 @@ test('localhost Register onListen hook after a plugin inside a plugin', t => { fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) @@ -226,10 +229,10 @@ test('localhost Register onListen hook after a plugin inside a plugin', t => { fastify.listen({ host: 'localhost', port: 0 - }) + }, testDone) }) -test('localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', t => { +test('localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', (t, testDone) => { t.plan(6) const stream = split(JSON.parse) const fastify = Fastify({ @@ -239,17 +242,17 @@ test('localhost Register onListen hook after a plugin inside a plugin should log level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('Plugin Error')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function () { - t.pass('called') + t.assert.ok('called') throw new Error('Plugin Error') }) done() @@ -257,12 +260,12 @@ test('localhost Register onListen hook after a plugin inside a plugin should log fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function () { - t.pass('called') + t.assert.ok('called') throw new Error('Plugin Error') }) instance.addHook('onListen', function () { - t.pass('called') + t.assert.ok('called') throw new Error('Plugin Error') }) @@ -272,40 +275,40 @@ test('localhost Register onListen hook after a plugin inside a plugin should log fastify.listen({ host: 'localhost', port: 0 - }) + }, testDone) }) test('localhost onListen encapsulation should be called in order', async t => { t.plan(8) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, 'called in root') - t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') done() }) await fastify.register(async (childOne, o) => { childOne.addHook('onListen', function (done) { - t.equal(++order, 2, 'called in childOne') - t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') done() }) await childOne.register(async (childTwo, o) => { childTwo.addHook('onListen', async function () { - t.equal(++order, 3, 'called in childTwo') - t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') }) }) await childOne.register(async (childTwoPeer, o) => { childTwoPeer.addHook('onListen', async function () { - t.equal(++order, 4, 'called second in childTwo') - t.equal(this.pluginName, childTwoPeer.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 4, 'called second in childTwo') + t.assert.strictEqual(this.pluginName, childTwoPeer.pluginName, 'the this binding is the right instance') }) }) }) @@ -318,12 +321,12 @@ test('localhost onListen encapsulation should be called in order', async t => { test('localhost onListen encapsulation with only nested hook', async t => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) await fastify.register(async (child) => { await child.register(async (child2) => { child2.addHook('onListen', function (done) { - t.pass() + t.assert.ok() done() }) }) @@ -338,19 +341,19 @@ test('localhost onListen encapsulation with only nested hook', async t => { test('localhost onListen peer encapsulations with only nested hooks', async t => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) await fastify.register(async (child) => { await child.register(async (child2) => { child2.addHook('onListen', function (done) { - t.pass() + t.assert.ok() done() }) }) await child.register(async (child2) => { child2.addHook('onListen', function (done) { - t.pass() + t.assert.ok() done() }) }) @@ -362,7 +365,7 @@ test('localhost onListen peer encapsulations with only nested hooks', async t => }) }) -test('localhost onListen encapsulation should be called in order and should log errors as warnings and continue', t => { +test('localhost onListen encapsulation should be called in order and should log errors as warnings and continue', (t, testDone) => { t.plan(7) const stream = split(JSON.parse) const fastify = Fastify({ @@ -372,32 +375,32 @@ test('localhost onListen encapsulation should be called in order and should log level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('Error in onListen hook of childTwo')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, 'called in root') - t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onListen', function (done) { - t.equal(++order, 2, 'called in childOne') - t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') done() }) childOne.register(async (childTwo, o) => { childTwo.addHook('onListen', async function () { - t.equal(++order, 3, 'called in childTwo') - t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') throw new Error('Error in onListen hook of childTwo') }) }) @@ -405,44 +408,44 @@ test('localhost onListen encapsulation should be called in order and should log fastify.listen({ host: 'localhost', port: 0 - }) + }, testDone) }) -test('non-localhost onListen should be called in order', { skip: isIPv6Missing }, t => { +test('non-localhost onListen should be called in order', { skip: isIPv6Missing }, (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, '1st called in root') + t.assert.strictEqual(++order, 1, '1st called in root') done() }) fastify.addHook('onListen', function (done) { - t.equal(++order, 2, '2nd called in root') + t.assert.strictEqual(++order, 2, '2nd called in root') done() }) fastify.listen({ host: '::1', port: 0 - }) + }, testDone) }) test('non-localhost async onListen should be called in order', { skip: isIPv6Missing }, async t => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', async function () { - t.equal(++order, 1, '1st async called in root') + t.assert.strictEqual(++order, 1, '1st async called in root') }) fastify.addHook('onListen', async function () { - t.equal(++order, 2, '2nd async called in root') + t.assert.strictEqual(++order, 2, '2nd async called in root') }) await fastify.listen({ @@ -451,7 +454,7 @@ test('non-localhost async onListen should be called in order', { skip: isIPv6Mis }) }) -test('non-localhost sync onListen should log errors as warnings and continue', { skip: isIPv6Missing }, t => { +test('non-localhost sync onListen should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { t.plan(4) const stream = split(JSON.parse) const fastify = Fastify({ @@ -461,34 +464,34 @@ test('non-localhost sync onListen should log errors as warnings and continue', { level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1) + t.assert.strictEqual(++order, 1) done() }) fastify.addHook('onListen', function () { - t.equal(++order, 2) + t.assert.strictEqual(++order, 2) throw new Error('FAIL ON LISTEN') }) fastify.addHook('onListen', function (done) { - t.equal(++order, 3, 'should still run') + t.assert.strictEqual(++order, 3, 'should still run') done() }) fastify.listen({ host: '::1', port: 0 - }) + }, testDone) }) test('non-localhost async onListen should log errors as warnings and continue', { skip: isIPv6Missing }, async t => { @@ -501,29 +504,29 @@ test('non-localhost async onListen should log errors as warnings and continue', level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) let order = 0 fastify.addHook('onListen', async function () { - t.equal(++order, 1) - t.pass('called in root') + t.assert.strictEqual(++order, 1) + t.assert.ok('called in root') }) fastify.addHook('onListen', async function () { - t.equal(++order, 2, '2nd async failed in root') + t.assert.strictEqual(++order, 2, '2nd async failed in root') throw new Error('FAIL ON LISTEN') }) fastify.addHook('onListen', async function () { - t.equal(++order, 3) - t.pass('should still run') + t.assert.strictEqual(++order, 3) + t.assert.ok('should still run') }) await fastify.listen({ @@ -532,14 +535,14 @@ test('non-localhost async onListen should log errors as warnings and continue', }) }) -test('non-localhost Register onListen hook after a plugin inside a plugin', { skip: isIPv6Missing }, t => { +test('non-localhost Register onListen hook after a plugin inside a plugin', { skip: isIPv6Missing }, (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) done() @@ -547,12 +550,12 @@ test('non-localhost Register onListen hook after a plugin inside a plugin', { sk fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) @@ -562,10 +565,10 @@ test('non-localhost Register onListen hook after a plugin inside a plugin', { sk fastify.listen({ host: '::1', port: 0 - }) + }, testDone) }) -test('non-localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', { skip: isIPv6Missing }, t => { +test('non-localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { t.plan(6) const stream = split(JSON.parse) const fastify = Fastify({ @@ -575,17 +578,17 @@ test('non-localhost Register onListen hook after a plugin inside a plugin should level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('Plugin Error')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function () { - t.pass('called') + t.assert.ok('called') throw new Error('Plugin Error') }) done() @@ -593,12 +596,12 @@ test('non-localhost Register onListen hook after a plugin inside a plugin should fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function () { - t.pass('called') + t.assert.ok('called') throw new Error('Plugin Error') }) instance.addHook('onListen', function () { - t.pass('called') + t.assert.ok('called') throw new Error('Plugin Error') }) @@ -608,42 +611,42 @@ test('non-localhost Register onListen hook after a plugin inside a plugin should fastify.listen({ host: '::1', port: 0 - }) + }, testDone) }) -test('non-localhost onListen encapsulation should be called in order', { skip: isIPv6Missing }, t => { +test('non-localhost onListen encapsulation should be called in order', { skip: isIPv6Missing }, (t, testDone) => { t.plan(6) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, 'called in root') - t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onListen', function (done) { - t.equal(++order, 2, 'called in childOne') - t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') done() }) childOne.register(async (childTwo, o) => { childTwo.addHook('onListen', async function () { - t.equal(++order, 3, 'called in childTwo') - t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') }) }) }) fastify.listen({ host: '::1', port: 0 - }) + }, testDone) }) -test('non-localhost onListen encapsulation should be called in order and should log errors as warnings and continue', { skip: isIPv6Missing }, t => { +test('non-localhost onListen encapsulation should be called in order and should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { t.plan(7) const stream = split(JSON.parse) const fastify = Fastify({ @@ -653,33 +656,33 @@ test('non-localhost onListen encapsulation should be called in order and should level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('Error in onListen hook of childTwo')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, 'called in root') + t.assert.strictEqual(++order, 1, 'called in root') - t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onListen', function (done) { - t.equal(++order, 2, 'called in childOne') - t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') done() }) childOne.register(async (childTwo, o) => { childTwo.addHook('onListen', async function () { - t.equal(++order, 3, 'called in childTwo') - t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') throw new Error('Error in onListen hook of childTwo') }) }) @@ -687,52 +690,54 @@ test('non-localhost onListen encapsulation should be called in order and should fastify.listen({ host: '::1', port: 0 - }) + }, testDone) }) -test('onListen localhost should work in order with callback', t => { +test('onListen localhost should work in order with callback', (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, '1st called in root') + t.assert.strictEqual(++order, 1, '1st called in root') done() }) fastify.addHook('onListen', function (done) { - t.equal(++order, 2, '2nd called in root') + t.assert.strictEqual(++order, 2, '2nd called in root') done() }) fastify.listen({ port: 0 }, (err) => { - t.equal(fastify.server.address().address, localhost) - t.error(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + t.assert.ifError(err) + testDone() }) }) -test('onListen localhost should work in order with callback in async', t => { +test('onListen localhost should work in order with callback in async', (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', async function () { - t.equal(++order, 1, '1st called in root') + t.assert.strictEqual(++order, 1, '1st called in root') }) fastify.addHook('onListen', async function () { - t.equal(++order, 2, '2nd called in root') + t.assert.strictEqual(++order, 2, '2nd called in root') }) fastify.listen({ host: 'localhost', port: 0 }, (err) => { - t.equal(fastify.server.address().address, localhost) - t.error(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + t.assert.ifError(err) + testDone() }) }) -test('onListen localhost sync with callback should log errors as warnings and continue', t => { +test('onListen localhost sync with callback should log errors as warnings and continue', (t, testDone) => { t.plan(6) const stream = split(JSON.parse) const fastify = Fastify({ @@ -742,38 +747,39 @@ test('onListen localhost sync with callback should log errors as warnings and co level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, '1st called in root') + t.assert.strictEqual(++order, 1, '1st called in root') done() }) fastify.addHook('onListen', function () { - t.equal(++order, 2, 'error sync called in root') + t.assert.strictEqual(++order, 2, 'error sync called in root') throw new Error('FAIL ON LISTEN') }) fastify.addHook('onListen', function (done) { - t.equal(++order, 3, '1st called in root') + t.assert.strictEqual(++order, 3, '1st called in root') done() }) fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.equal(fastify.server.address().address, localhost) + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + testDone() }) }) -test('onListen localhost async with callback should log errors as warnings and continue', t => { +test('onListen localhost async with callback should log errors as warnings and continue', (t, testDone) => { t.plan(6) const stream = split(JSON.parse) const fastify = Fastify({ @@ -783,43 +789,44 @@ test('onListen localhost async with callback should log errors as warnings and c level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) let order = 0 fastify.addHook('onListen', async function () { - t.pass('1st called in root') + t.assert.ok('1st called in root') }) fastify.addHook('onListen', async function () { - t.equal(++order, 1, 'error sync called in root') + t.assert.strictEqual(++order, 1, 'error sync called in root') throw new Error('FAIL ON LISTEN') }) fastify.addHook('onListen', async function () { - t.pass('3rd called in root') + t.assert.ok('3rd called in root') }) fastify.listen({ port: 0 }, (err) => { - t.error(err) - t.equal(fastify.server.address().address, localhost) + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + testDone() }) }) -test('Register onListen hook localhost with callback after a plugin inside a plugin', t => { +test('Register onListen hook localhost with callback after a plugin inside a plugin', (t, testDone) => { t.plan(5) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) done() @@ -827,12 +834,12 @@ test('Register onListen hook localhost with callback after a plugin inside a plu fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) @@ -840,86 +847,90 @@ test('Register onListen hook localhost with callback after a plugin inside a plu })) fastify.listen({ port: 0 }, (err) => { - t.equal(fastify.server.address().address, localhost) - t.error(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + t.assert.ifError(err) + testDone() }) }) -test('onListen localhost with callback encapsulation should be called in order', t => { +test('onListen localhost with callback encapsulation should be called in order', (t, testDone) => { t.plan(8) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, 'called in root') - t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onListen', function (done) { - t.equal(++order, 2, 'called in childOne') - t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') done() }) childOne.register(async (childTwo, o) => { childTwo.addHook('onListen', async function () { - t.equal(++order, 3, 'called in childTwo') - t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') }) }) }) fastify.listen({ port: 0 }, (err) => { - t.equal(fastify.server.address().address, localhost) - t.error(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + t.assert.ifError(err) + testDone() }) }) -test('onListen non-localhost should work in order with callback in sync', { skip: isIPv6Missing }, t => { +test('onListen non-localhost should work in order with callback in sync', { skip: isIPv6Missing }, (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, '1st called in root') + t.assert.strictEqual(++order, 1, '1st called in root') done() }) fastify.addHook('onListen', function (done) { - t.equal(++order, 2, '2nd called in root') + t.assert.strictEqual(++order, 2, '2nd called in root') done() }) fastify.listen({ host: '::1', port: 0 }, (err) => { - t.equal(fastify.server.address().address, '::1') - t.error(err) + t.assert.strictEqual(fastify.server.address().address, '::1') + t.assert.ifError(err) + testDone() }) }) -test('onListen non-localhost should work in order with callback in async', { skip: isIPv6Missing }, t => { +test('onListen non-localhost should work in order with callback in async', { skip: isIPv6Missing }, (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', async function () { - t.equal(++order, 1, '1st called in root') + t.assert.strictEqual(++order, 1, '1st called in root') }) fastify.addHook('onListen', async function () { - t.equal(++order, 2, '2nd called in root') + t.assert.strictEqual(++order, 2, '2nd called in root') }) fastify.listen({ host: '::1', port: 0 }, (err) => { - t.equal(fastify.server.address().address, '::1') - t.error(err) + t.assert.strictEqual(fastify.server.address().address, '::1') + t.assert.ifError(err) + testDone() }) }) -test('onListen non-localhost sync with callback should log errors as warnings and continue', { skip: isIPv6Missing }, t => { +test('onListen non-localhost sync with callback should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { t.plan(8) const stream = split(JSON.parse) @@ -930,40 +941,41 @@ test('onListen non-localhost sync with callback should log errors as warnings an level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1) - t.pass('1st called in root') + t.assert.strictEqual(++order, 1) + t.assert.ok('1st called in root') done() }) fastify.addHook('onListen', function () { - t.equal(++order, 2) + t.assert.strictEqual(++order, 2) throw new Error('FAIL ON LISTEN') }) fastify.addHook('onListen', function (done) { - t.equal(++order, 3) - t.pass('3rd called in root') + t.assert.strictEqual(++order, 3) + t.assert.ok('3rd called in root') done() }) fastify.listen({ host: '::1', port: 0 }, (err) => { - t.error(err) - t.equal(fastify.server.address().address, '::1') + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, '::1') + testDone() }) }) -test('onListen non-localhost async with callback should log errors as warnings and continue', { skip: isIPv6Missing }, t => { +test('onListen non-localhost async with callback should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { t.plan(8) const stream = split(JSON.parse) @@ -974,45 +986,46 @@ test('onListen non-localhost async with callback should log errors as warnings a level: 'info' } }) - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) stream.on('data', message => { if (message.msg.includes('FAIL ON LISTEN')) { - t.pass('Logged Error Message') + t.assert.ok('Logged Error Message') } }) let order = 0 fastify.addHook('onListen', async function () { - t.equal(++order, 1) - t.pass('1st called in root') + t.assert.strictEqual(++order, 1) + t.assert.ok('1st called in root') }) fastify.addHook('onListen', async function () { - t.equal(++order, 2, 'error sync called in root') + t.assert.strictEqual(++order, 2, 'error sync called in root') throw new Error('FAIL ON LISTEN') }) fastify.addHook('onListen', async function () { - t.equal(++order, 3) - t.pass('3rd called in root') + t.assert.strictEqual(++order, 3) + t.assert.ok('3rd called in root') }) fastify.listen({ host: '::1', port: 0 }, (err) => { - t.error(err) - t.equal(fastify.server.address().address, '::1') + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, '::1') + testDone() }) }) -test('Register onListen hook non-localhost with callback after a plugin inside a plugin', { skip: isIPv6Missing }, t => { +test('Register onListen hook non-localhost with callback after a plugin inside a plugin', { skip: isIPv6Missing }, (t, testDone) => { t.plan(5) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) done() @@ -1020,12 +1033,12 @@ test('Register onListen hook non-localhost with callback after a plugin inside a fastify.register(fp(function (instance, opts, done) { instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) instance.addHook('onListen', function (done) { - t.pass('called') + t.assert.ok('called') done() }) @@ -1033,91 +1046,93 @@ test('Register onListen hook non-localhost with callback after a plugin inside a })) fastify.listen({ host: '::1', port: 0 }, (err) => { - t.equal(fastify.server.address().address, '::1') - t.error(err) + t.assert.strictEqual(fastify.server.address().address, '::1') + t.assert.ifError(err) + testDone() }) }) -test('onListen non-localhost with callback encapsulation should be called in order', { skip: isIPv6Missing }, t => { +test('onListen non-localhost with callback encapsulation should be called in order', { skip: isIPv6Missing }, (t, testDone) => { t.plan(8) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function (done) { - t.equal(++order, 1, 'called in root') - t.equal(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') done() }) fastify.register(async (childOne, o) => { childOne.addHook('onListen', function (done) { - t.equal(++order, 2, 'called in childOne') - t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') done() }) childOne.register(async (childTwo, o) => { childTwo.addHook('onListen', async function () { - t.equal(++order, 3, 'called in childTwo') - t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') }) }) }) fastify.listen({ host: '::1', port: 0 }, (err) => { - t.equal(fastify.server.address().address, '::1') - t.error(err) + t.assert.strictEqual(fastify.server.address().address, '::1') + t.assert.ifError(err) + testDone() }) }) -test('onListen sync should work if user does not pass done', t => { +test('onListen sync should work if user does not pass done', (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', function () { - t.equal(++order, 1, '1st called in root') + t.assert.strictEqual(++order, 1, '1st called in root') }) fastify.addHook('onListen', function () { - t.equal(++order, 2, '2nd called in root') + t.assert.strictEqual(++order, 2, '2nd called in root') }) fastify.listen({ host: 'localhost', port: 0 - }) + }, testDone) }) -test('async onListen does not need to be awaited', t => { +test('async onListen does not need to be awaited', (t, testDone) => { const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) let order = 0 fastify.addHook('onListen', async function () { - t.equal(++order, 1, '1st async called in root') + t.assert.strictEqual(++order, 1, '1st async called in root') }) fastify.addHook('onListen', async function () { - t.equal(++order, 2, '2nd async called in root') + t.assert.strictEqual(++order, 2, '2nd async called in root') t.end() }) fastify.listen({ host: 'localhost', port: 0 - }) + }, testDone) }) -test('onListen hooks do not block /1', t => { +test('onListen hooks do not block /1', (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.addHook('onListen', function (done) { - t.equal(fastify[kState].listening, true) + t.assert.strictEqual(fastify[kState].listening, true) done() }) @@ -1125,7 +1140,8 @@ test('onListen hooks do not block /1', t => { host: 'localhost', port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) @@ -1133,10 +1149,10 @@ test('onListen hooks do not block /2', async t => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.addHook('onListen', async function () { - t.equal(fastify[kState].listening, true) + t.assert.strictEqual(fastify[kState].listening, true) }) await fastify.listen({ From 8318d016747e208f967e6d82a45d760c5d9f9c49 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 7 May 2025 12:53:30 +0100 Subject: [PATCH 1010/1295] ci: ignore scripts (#6108) * ci: ignore scripts * chore(.npmrc): ignore scripts --- .github/workflows/ci.yml | 4 ++-- .github/workflows/citgm-package.yml | 2 +- .github/workflows/integration-alternative-runtimes.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- .npmrc | 1 + 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56ae4a31187..80bec4c945c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -193,13 +193,13 @@ jobs: npm install --ignore-scripts - name: install webpack stack run: | - cd test/bundler/webpack && npm install + cd test/bundler/webpack && npm install --ignore-scripts - name: Test webpack bundle run: | cd test/bundler/webpack && npm run test - name: install esbuild stack run: | - cd test/bundler/esbuild && npm install + cd test/bundler/esbuild && npm install --ignore-scripts - name: Test esbuild bundle run: | cd test/bundler/esbuild && npm run test diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index ee988866aa6..66b700a6319 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -87,7 +87,7 @@ jobs: - name: Install Dependencies for ${{inputs.package}} working-directory: package run: | - npm install + npm install --ignore-scripts - name: Sym Link Fastify working-directory: package run: | diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index 732751ba75b..06724b93684 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -51,7 +51,7 @@ jobs: - name: Install Production run: | - pnpm install --prod + pnpm install --ignore-scripts --prod - name: Run server run: | diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 1ededa5f65b..ad577125416 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -47,7 +47,7 @@ jobs: - name: Install Production run: | - pnpm install --prod + pnpm install --ignore-scripts --prod - name: Run server run: | diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 2d8223c5238..73a50a5205e 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -37,7 +37,7 @@ jobs: with: version: ${{ matrix.pnpm-version }} - - run: pnpm install + - run: pnpm install --ignore-scripts - name: Run tests run: | @@ -70,7 +70,7 @@ jobs: - name: Install with yarn run: | curl -o- -L https://yarnpkg.com/install.sh | bash - yarn install --ignore-engines + yarn install --ignore-engines --ignore-scripts - run: yarn diff --git a/.npmrc b/.npmrc index 43c97e719a5..3757b3046ec 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ +ignore-scripts=true package-lock=false From 59f21ed6bbbb191782384c23871f39f2de9b8dce Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Thu, 8 May 2025 09:05:05 +0200 Subject: [PATCH 1011/1295] docs: add a warning about `setErrorHandler` overriding a previously defined error handler on an encapsulated context (#6097) * docs: calling setErrorHandler more than once in the same scope will silently override the previous handler * fix: lint markdown * docs: nit * docs: apply convention * docs: apply convention --- docs/Reference/Server.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index fb37f129d0b..07d19fa37b1 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1574,6 +1574,22 @@ if (statusCode >= 500) { log.error(error) } ``` + +> ⚠ Warning: +> Avoid calling setErrorHandler multiple times in the same scope. +> Only the last handler will take effect, and previous ones will be silently overridden. +> +> Incorrect usage: +> ```js +> app.setErrorHandler(function freeSomeResources () { +> // Never executed, memory leaks +> }) +> +> app.setErrorHandler(function anotherErrorHandler () { +> // Overrides the previous handler +> }) +> ``` + #### setChildLoggerFactory From 850ebf6ef0d71284e37c2bc98f41bbe2ec2c45c0 Mon Sep 17 00:00:00 2001 From: Sahachai Date: Fri, 9 May 2025 09:03:15 +0700 Subject: [PATCH 1012/1295] docs(ecosystem): remove fastify-diagnostics-channel (#6117) --- docs/Guides/Ecosystem.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 2b535e34119..c8a1d414ff0 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -40,8 +40,6 @@ section. plugin for adding [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) protection to Fastify. -- [`@fastify/diagnostics-channel`](https://github.com/fastify/fastify-diagnostics-channel) - Plugin to deal with `diagnostics_channel` on Fastify. - [`@fastify/elasticsearch`](https://github.com/fastify/fastify-elasticsearch) Plugin to share the same ES client. - [`@fastify/env`](https://github.com/fastify/fastify-env) Load and check From 8ddd4fa85629b6907ef2d262c2dde0081568dee2 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Sun, 11 May 2025 08:58:47 +0200 Subject: [PATCH 1013/1295] fix: internal function _addHook failure should be turned into the rejection that app.ready() is waiting for (#6105) --- fastify.js | 8 ++++++-- test/internals/hooks.test.js | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index d23e03f01e4..1238cfdcde2 100644 --- a/fastify.js +++ b/fastify.js @@ -681,8 +681,12 @@ function fastify (options) { this[kHooks].add(name, fn) } else { this.after((err, done) => { - _addHook.call(this, name, fn) - done(err) + try { + _addHook.call(this, name, fn) + done(err) + } catch (err) { + done(err) + } }) } return this diff --git a/test/internals/hooks.test.js b/test/internals/hooks.test.js index 2f91196d710..a0811a97b02 100644 --- a/test/internals/hooks.test.js +++ b/test/internals/hooks.test.js @@ -2,6 +2,7 @@ const { test } = require('node:test') const { Hooks } = require('../../lib/hooks') +const { default: fastify } = require('../../fastify') const noop = () => {} test('hooks should have 4 array with the registered hooks', t => { @@ -77,3 +78,19 @@ test('should throw on wrong parameters', t => { t.assert.strictEqual(e.message, 'onSend hook should be a function, instead got [object Null]') } }) + +test('Integration test: internal function _addHook should be turned into app.ready() rejection', async (t) => { + const app = fastify() + + app.register(async function () { + app.addHook('notRealHook', async () => {}) + }) + + try { + await app.ready() + t.assert.fail('Expected ready() to throw') + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_HOOK_NOT_SUPPORTED') + t.assert.match(err.message, /hook not supported/i) + } +}) From 51933de5c74dacb6143aeff3ff728a3e4a94adef Mon Sep 17 00:00:00 2001 From: Sahachai Date: Sun, 11 May 2025 13:59:43 +0700 Subject: [PATCH 1014/1295] test: replace removed request properties and update docs (#6111) --- docs/Reference/Server.md | 2 +- test/inject.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 07d19fa37b1..13b1520034f 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1529,7 +1529,7 @@ plugins. > 🛈 Note: > Some config properties from the request object will be > undefined inside the custom not found handler. E.g.: -> `request.routerPath`, `routerMethod` and `context.config`. +> `request.routeOptions.url`, `routeOptions.method` and `routeOptions.config`. > This method design goal is to allow calling the common not found route. > To return a per-route customized 404 response, you can do it in > the response itself. diff --git a/test/inject.test.js b/test/inject.test.js index b758657d160..9cf3802d2a5 100644 --- a/test/inject.test.js +++ b/test/inject.test.js @@ -479,7 +479,7 @@ test('Should not throw on access to routeConfig frameworkErrors handler - FST_ER frameworkErrors: function (err, req, res) { t.assert.ok(typeof req.id === 'string') t.assert.ok(req.raw instanceof Readable) - t.assert.deepStrictEqual(req.routerPath, undefined) + t.assert.deepStrictEqual(req.routeOptions.url, undefined) res.send(`${err.message} - ${err.code}`) } }) From 2635e256c651474bc286fb471eb81b82d8d6677b Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Sun, 11 May 2025 09:04:36 +0200 Subject: [PATCH 1015/1295] test: mv reply from tap (#6089) --- test/reply-error.test.js | 317 +++++++++++++++++++----------------- test/reply-trailers.test.js | 227 ++++++++++++++------------ 2 files changed, 288 insertions(+), 256 deletions(-) diff --git a/test/reply-error.test.js b/test/reply-error.test.js index fd2865e12d4..8f2a35f36de 100644 --- a/test/reply-error.test.js +++ b/test/reply-error.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test, describe } = require('node:test') const net = require('node:net') const Fastify = require('..') const statusCodes = require('node:http').STATUS_CODES @@ -15,10 +14,10 @@ codes.forEach(code => { }) function helper (code) { - test('Reply error handling - code: ' + code, t => { + test('Reply error handling - code: ' + code, (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const err = new Error('winter is coming') fastify.get('/', (req, reply) => { @@ -31,10 +30,10 @@ function helper (code) { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, Number(code)) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') - t.same( + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, Number(code)) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual( { error: statusCodes[code], message: err.message, @@ -42,14 +41,15 @@ function helper (code) { }, JSON.parse(res.payload) ) + testDone() }) }) } -test('preHandler hook error handling with external code', t => { +test('preHandler hook error handling with external code', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const err = new Error('winter is coming') fastify.addHook('preHandler', (req, reply, done) => { @@ -63,9 +63,9 @@ test('preHandler hook error handling with external code', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 400) - t.same( + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual( { error: statusCodes['400'], message: err.message, @@ -73,13 +73,14 @@ test('preHandler hook error handling with external code', t => { }, JSON.parse(res.payload) ) + testDone() }) }) -test('onRequest hook error handling with external done', t => { +test('onRequest hook error handling with external done', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const err = new Error('winter is coming') fastify.addHook('onRequest', (req, reply, done) => { @@ -93,9 +94,9 @@ test('onRequest hook error handling with external done', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 400) - t.same( + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual( { error: statusCodes['400'], message: err.message, @@ -103,16 +104,17 @@ test('onRequest hook error handling with external done', t => { }, JSON.parse(res.payload) ) + testDone() }) }) -test('Should reply 400 on client error', t => { +test('Should reply 400 on client error', (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.listen({ port: 0, host: '127.0.0.1' }, err => { - t.error(err) + t.assert.ifError(err) const client = net.connect(fastify.server.address().port, '127.0.0.1') client.end('oooops!') @@ -128,12 +130,13 @@ test('Should reply 400 on client error', t => { message: 'Client Error', statusCode: 400 }) - t.equal(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`, chunks) + t.assert.strictEqual(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`, chunks) + testDone() }) }) }) -test('Should set the response from client error handler', t => { +test('Should set the response from client error handler', (t, testDone) => { t.plan(5) const responseBody = JSON.stringify({ @@ -144,7 +147,7 @@ test('Should set the response from client error handler', t => { const response = `HTTP/1.1 400 Bad Request\r\nContent-Length: ${responseBody.length}\r\nContent-Type: application/json; charset=utf-8\r\n\r\n${responseBody}` function clientErrorHandler (err, socket) { - t.type(err, Error) + t.assert.ok(err instanceof Error) this.log.warn({ err }, 'Handled client error') socket.end(response) @@ -160,8 +163,8 @@ test('Should set the response from client error handler', t => { }) fastify.listen({ port: 0, host: '127.0.0.1' }, err => { - t.error(err) - t.teardown(fastify.close.bind(fastify)) + t.assert.ifError(err) + t.after(() => fastify.close()) const client = net.connect(fastify.server.address().port, '127.0.0.1') client.end('oooops!') @@ -172,20 +175,22 @@ test('Should set the response from client error handler', t => { }) client.once('end', () => { - t.equal(response, chunks) + t.assert.strictEqual(response, chunks) + + testDone() }) }) logStream.once('data', line => { - t.equal('Handled client error', line.msg) - t.equal(40, line.level, 'Log level is not warn') + t.assert.strictEqual('Handled client error', line.msg) + t.assert.strictEqual(40, line.level, 'Log level is not warn') }) }) -test('Error instance sets HTTP status code', t => { +test('Error instance sets HTTP status code', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const err = new Error('winter is coming') err.statusCode = 418 @@ -197,9 +202,9 @@ test('Error instance sets HTTP status code', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same( + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual( { error: statusCodes['418'], message: err.message, @@ -207,13 +212,14 @@ test('Error instance sets HTTP status code', t => { }, JSON.parse(res.payload) ) + testDone() }) }) -test('Error status code below 400 defaults to 500', t => { +test('Error status code below 400 defaults to 500', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const err = new Error('winter is coming') err.statusCode = 399 @@ -225,9 +231,9 @@ test('Error status code below 400 defaults to 500', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 500) - t.same( + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual( { error: statusCodes['500'], message: err.message, @@ -235,13 +241,14 @@ test('Error status code below 400 defaults to 500', t => { }, JSON.parse(res.payload) ) + testDone() }) }) -test('Error.status property support', t => { +test('Error.status property support', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const err = new Error('winter is coming') err.status = 418 @@ -253,9 +260,9 @@ test('Error.status property support', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 418) - t.same( + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual( { error: statusCodes['418'], message: err.message, @@ -263,10 +270,11 @@ test('Error.status property support', t => { }, JSON.parse(res.payload) ) + testDone() }) }) -test('Support rejection with values that are not Error instances', t => { +describe('Support rejection with values that are not Error instances', () => { const objs = [ 0, '', @@ -280,12 +288,11 @@ test('Support rejection with values that are not Error instances', t => { new Date(), new Uint8Array() ] - t.plan(objs.length) for (const nonErr of objs) { - t.test('Type: ' + typeof nonErr, t => { + test('Type: ' + typeof nonErr, (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', () => { return Promise.reject(nonErr) @@ -293,9 +300,9 @@ test('Support rejection with values that are not Error instances', t => { fastify.setErrorHandler((err, request, reply) => { if (typeof err === 'object') { - t.same(err, nonErr) + t.assert.deepStrictEqual(err, nonErr) } else { - t.equal(err, nonErr) + t.assert.strictEqual(err, nonErr) } reply.code(500).send('error') }) @@ -304,19 +311,20 @@ test('Support rejection with values that are not Error instances', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 500) - t.equal(res.payload, 'error') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.payload, 'error') + testDone() }) }) } }) -test('invalid schema - ajv', t => { +test('invalid schema - ajv', (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', { schema: { querystring: { @@ -327,11 +335,11 @@ test('invalid schema - ajv', t => { } } }, (req, reply) => { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.setErrorHandler((err, request, reply) => { - t.ok(Array.isArray(err.validation)) + t.assert.ok(Array.isArray(err.validation)) reply.code(400).send('error') }) @@ -339,16 +347,17 @@ test('invalid schema - ajv', t => { url: '/?id=abc', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.payload, 'error') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.payload, 'error') + testDone() }) }) -test('should set the status code and the headers from the error object (from route handler) (no custom error handler)', t => { +test('should set the status code and the headers from the error object (from route handler) (no custom error handler)', (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { const error = new Error('kaboom') @@ -361,21 +370,23 @@ test('should set the status code and the headers from the error object (from rou url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.headers.hello, 'world') - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers.hello, 'world') + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Bad Request', message: 'kaboom', statusCode: 400 }) + + testDone() }) }) -test('should set the status code and the headers from the error object (from custom error handler)', t => { +test('should set the status code and the headers from the error object (from custom error handler)', (t, testDone) => { t.plan(6) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { const error = new Error('ouch') @@ -384,8 +395,8 @@ test('should set the status code and the headers from the error object (from cus }) fastify.setErrorHandler((err, request, reply) => { - t.equal(err.message, 'ouch') - t.equal(reply.raw.statusCode, 200) + t.assert.strictEqual(err.message, 'ouch') + t.assert.strictEqual(reply.raw.statusCode, 200) const error = new Error('kaboom') error.headers = { hello: 'world' } error.statusCode = 400 @@ -396,31 +407,33 @@ test('should set the status code and the headers from the error object (from cus url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.headers.hello, 'world') - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers.hello, 'world') + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Bad Request', message: 'kaboom', statusCode: 400 }) + testDone() }) }) // Issue 595 https://github.com/fastify/fastify/issues/595 -test('\'*\' should throw an error due to serializer can not handle the payload type', t => { +test('\'*\' should throw an error due to serializer can not handle the payload type', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { reply.type('text/html') try { reply.send({}) } catch (err) { - t.type(err, TypeError) - t.equal(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') - t.equal(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.") + t.assert.ok(err instanceof TypeError) + t.assert.strictEqual(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') + t.assert.strictEqual(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.") + testDone() } }) @@ -428,14 +441,14 @@ test('\'*\' should throw an error due to serializer can not handle the payload t url: '/', method: 'GET' }, (e, res) => { - t.fail('should not be called') + t.assert.fail('should not be called') }) }) -test('should throw an error if the custom serializer does not serialize the payload to a valid type', t => { +test('should throw an error if the custom serializer does not serialize the payload to a valid type', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { try { @@ -444,9 +457,10 @@ test('should throw an error if the custom serializer does not serialize the payl .serializer(payload => payload) .send({}) } catch (err) { - t.type(err, TypeError) - t.equal(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') - t.equal(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.") + t.assert.ok(err instanceof TypeError) + t.assert.strictEqual(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') + t.assert.strictEqual(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.") + testDone() } }) @@ -454,15 +468,15 @@ test('should throw an error if the custom serializer does not serialize the payl url: '/', method: 'GET' }, (e, res) => { - t.fail('should not be called') + t.assert.fail('should not be called') }) }) -test('should not set headers or status code for custom error handler', t => { +test('should not set headers or status code for custom error handler', (t, testDone) => { t.plan(7) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function (req, reply) { const err = new Error('kaboom') err.headers = { @@ -472,8 +486,8 @@ test('should not set headers or status code for custom error handler', t => { }) fastify.setErrorHandler(async (err, req, res) => { - t.equal(res.statusCode, 200) - t.equal('fake-random-header' in res.headers, false) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual('fake-random-header' in res.headers, false) return res.code(500).send(err.message) }) @@ -481,19 +495,20 @@ test('should not set headers or status code for custom error handler', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal('fake-random-header' in res.headers, false) - t.equal(res.headers['content-length'], ('kaboom'.length).toString()) - t.same(res.payload, 'kaboom') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual('fake-random-header' in res.headers, false) + t.assert.strictEqual(res.headers['content-length'], ('kaboom'.length).toString()) + t.assert.deepStrictEqual(res.payload, 'kaboom') + testDone() }) }) -test('error thrown by custom error handler routes to default error handler', t => { +test('error thrown by custom error handler routes to default error handler', (t, testDone) => { t.plan(6) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const error = new Error('kaboom') error.headers = { @@ -507,9 +522,9 @@ test('error thrown by custom error handler routes to default error handler', t = const newError = new Error('kabong') fastify.setErrorHandler(async (err, req, res) => { - t.equal(res.statusCode, 200) - t.equal('fake-random-header' in res.headers, false) - t.same(err.headers, error.headers) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual('fake-random-header' in res.headers, false) + t.assert.deepStrictEqual(err.headers, error.headers) return res.send(newError) }) @@ -518,24 +533,25 @@ test('error thrown by custom error handler routes to default error handler', t = method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: statusCodes['500'], message: newError.message, statusCode: 500 }) + testDone() }) }) // Refs: https://github.com/fastify/fastify/pull/4484#issuecomment-1367301750 -test('allow re-thrown error to default error handler when route handler is async and error handler is sync', t => { +test('allow re-thrown error to default error handler when route handler is async and error handler is sync', (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.setErrorHandler(function (error) { - t.equal(error.message, 'kaboom') + t.assert.strictEqual(error.message, 'kaboom') throw Error('kabong') }) @@ -547,13 +563,14 @@ test('allow re-thrown error to default error handler when route handler is async url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: statusCodes['500'], message: 'kabong', statusCode: 500 }) + testDone() }) }) @@ -572,32 +589,34 @@ const invalidErrorCodes = [ 700 ] invalidErrorCodes.forEach((invalidCode) => { - test(`should throw error if error code is ${invalidCode}`, t => { + test(`should throw error if error code is ${invalidCode}`, (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (request, reply) => { try { return reply.code(invalidCode).send('You should not read this') } catch (err) { - t.equal(err.code, 'FST_ERR_BAD_STATUS_CODE') - t.equal(err.message, 'Called reply with an invalid status code: ' + invalidCode) + t.assert.strictEqual(err.code, 'FST_ERR_BAD_STATUS_CODE') + t.assert.strictEqual(err.message, 'Called reply with an invalid status code: ' + invalidCode) + testDone() } }) + fastify.inject({ url: '/', method: 'GET' }, (e, res) => { - t.fail('should not be called') + t.assert.fail('should not be called') }) }) }) -test('error handler is triggered when a string is thrown from sync handler', t => { +test('error handler is triggered when a string is thrown from sync handler', (t, testDone) => { t.plan(3) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const throwable = 'test' const payload = 'error' @@ -607,7 +626,7 @@ test('error handler is triggered when a string is thrown from sync handler', t = }) fastify.setErrorHandler((err, req, res) => { - t.equal(err, throwable) + t.assert.strictEqual(err, throwable) res.send(payload) }) @@ -616,15 +635,16 @@ test('error handler is triggered when a string is thrown from sync handler', t = method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.payload, payload) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, payload) + testDone() }) }) test('status code should be set to 500 and return an error json payload if route handler throws any non Error object expression', async t => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', () => { /* eslint-disable-next-line */ @@ -633,14 +653,14 @@ test('status code should be set to 500 and return an error json payload if route // ---- const reply = await fastify.inject({ method: 'GET', url: '/' }) - t.equal(reply.statusCode, 500) - t.equal(JSON.parse(reply.body).foo, 'bar') + t.assert.strictEqual(reply.statusCode, 500) + t.assert.strictEqual(JSON.parse(reply.body).foo, 'bar') }) test('should preserve the status code set by the user if an expression is thrown in a sync route', async t => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (_, rep) => { rep.status(501) @@ -651,15 +671,15 @@ test('should preserve the status code set by the user if an expression is thrown // ---- const reply = await fastify.inject({ method: 'GET', url: '/' }) - t.equal(reply.statusCode, 501) - t.equal(JSON.parse(reply.body).foo, 'bar') + t.assert.strictEqual(reply.statusCode, 501) + t.assert.strictEqual(JSON.parse(reply.body).foo, 'bar') }) test('should trigger error handlers if a sync route throws any non-error object', async t => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) const throwable = 'test' const payload = 'error' @@ -669,19 +689,19 @@ test('should trigger error handlers if a sync route throws any non-error object' }) fastify.setErrorHandler((err, req, res) => { - t.equal(err, throwable) + t.assert.strictEqual(err, throwable) res.code(500).send(payload) }) const reply = await fastify.inject({ method: 'GET', url: '/' }) - t.equal(reply.statusCode, 500) + t.assert.strictEqual(reply.statusCode, 500) }) test('should trigger error handlers if a sync route throws undefined', async t => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', function async (req, reply) { // eslint-disable-next-line no-throw-literal @@ -689,13 +709,13 @@ test('should trigger error handlers if a sync route throws undefined', async t = }) const reply = await fastify.inject({ method: 'GET', url: '/' }) - t.equal(reply.statusCode, 500) + t.assert.strictEqual(reply.statusCode, 500) }) -test('setting content-type on reply object should not hang the server case 1', t => { +test('setting content-type on reply object should not hang the server case 1', (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { reply @@ -708,15 +728,16 @@ test('setting content-type on reply object should not hang the server case 1', t url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) test('setting content-type on reply object should not hang the server case 2', async t => { t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { reply @@ -731,7 +752,7 @@ test('setting content-type on reply object should not hang the server case 2', a url: '/', method: 'GET' }) - t.same({ + t.assert.deepStrictEqual({ error: 'Internal Server Error', message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.', statusCode: 500, @@ -739,16 +760,14 @@ test('setting content-type on reply object should not hang the server case 2', a }, res.json()) } catch (error) { - t.error(error) - } finally { - await fastify.close() + t.assert.ifError(error) } }) -test('setting content-type on reply object should not hang the server case 3', t => { +test('setting content-type on reply object should not hang the server case 3', (t, testDone) => { t.plan(2) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.get('/', (req, reply) => { reply @@ -761,18 +780,19 @@ test('setting content-type on reply object should not hang the server case 3', t url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('pipe stream inside error handler should not cause error', t => { +test('pipe stream inside error handler should not cause error', (t, testDone) => { t.plan(3) const location = path.join(__dirname, '..', 'package.json') const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8')) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.setErrorHandler((_error, _request, reply) => { const stream = fs.createReadStream(location) @@ -787,8 +807,9 @@ test('pipe stream inside error handler should not cause error', t => { url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(JSON.parse(res.payload), json) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(res.payload), json) + testDone() }) }) diff --git a/test/reply-trailers.test.js b/test/reply-trailers.test.js index 374625a8a03..0d72eef42eb 100644 --- a/test/reply-trailers.test.js +++ b/test/reply-trailers.test.js @@ -1,13 +1,12 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test, describe } = require('node:test') const Fastify = require('..') const { Readable } = require('node:stream') const { createHash } = require('node:crypto') const { sleep } = require('./helper') -test('send trailers when payload is empty string', t => { +test('send trailers when payload is empty string', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -23,15 +22,16 @@ test('send trailers when payload is empty string', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers.trailer, 'etag') - t.equal(res.trailers.etag, 'custom-etag') - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('send trailers when payload is empty buffer', t => { +test('send trailers when payload is empty buffer', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -47,15 +47,16 @@ test('send trailers when payload is empty buffer', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers.trailer, 'etag') - t.equal(res.trailers.etag, 'custom-etag') - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('send trailers when payload is undefined', t => { +test('send trailers when payload is undefined', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -71,15 +72,16 @@ test('send trailers when payload is undefined', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers.trailer, 'etag') - t.equal(res.trailers.etag, 'custom-etag') - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('send trailers when payload is json', t => { +test('send trailers when payload is json', (t, testDone) => { t.plan(7) const fastify = Fastify() @@ -90,7 +92,7 @@ test('send trailers when payload is json', t => { fastify.get('/', function (request, reply) { reply.trailer('Content-MD5', function (reply, payload, done) { - t.equal(data, payload) + t.assert.strictEqual(data, payload) const hash = createHash('md5') hash.update(payload) done(null, hash.digest('hex')) @@ -102,23 +104,24 @@ test('send trailers when payload is json', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['transfer-encoding'], 'chunked') - t.equal(res.headers.trailer, 'content-md5') - t.equal(res.trailers['content-md5'], md5) - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(res.headers.trailer, 'content-md5') + t.assert.strictEqual(res.trailers['content-md5'], md5) + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('send trailers when payload is stream', t => { +test('send trailers when payload is stream', (t, testDone) => { t.plan(7) const fastify = Fastify() fastify.get('/', function (request, reply) { reply.trailer('ETag', function (reply, payload, done) { - t.same(payload, null) + t.assert.deepStrictEqual(payload, null) done(null, 'custom-etag') }) const stream = Readable.from([JSON.stringify({ hello: 'world' })]) @@ -129,16 +132,17 @@ test('send trailers when payload is stream', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['transfer-encoding'], 'chunked') - t.equal(res.headers.trailer, 'etag') - t.equal(res.trailers.etag, 'custom-etag') - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('send trailers when using async-await', t => { +test('send trailers when using async-await', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -154,15 +158,16 @@ test('send trailers when using async-await', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers.trailer, 'etag') - t.equal(res.trailers.etag, 'custom-etag') - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('error in trailers should be ignored', t => { +test('error in trailers should be ignored', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -178,33 +183,32 @@ test('error in trailers should be ignored', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers.trailer, 'etag') - t.notHas(res.trailers, 'etag') - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.ok(!res.trailers['etag']) + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('trailer handler counter', t => { - t.plan(2) - +describe('trailer handler counter', () => { const data = JSON.stringify({ hello: 'world' }) const hash = createHash('md5') hash.update(data) const md5 = hash.digest('hex') - t.test('callback with timeout', t => { + test('callback with timeout', (t, testDone) => { t.plan(9) const fastify = Fastify() fastify.get('/', function (request, reply) { reply.trailer('Return-Early', function (reply, payload, done) { - t.equal(data, payload) + t.assert.strictEqual(data, payload) done(null, 'return') }) reply.trailer('Content-MD5', function (reply, payload, done) { - t.equal(data, payload) + t.assert.strictEqual(data, payload) const hash = createHash('md5') hash.update(payload) setTimeout(() => { @@ -218,27 +222,28 @@ test('trailer handler counter', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['transfer-encoding'], 'chunked') - t.equal(res.headers.trailer, 'return-early content-md5') - t.equal(res.trailers['return-early'], 'return') - t.equal(res.trailers['content-md5'], md5) - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(res.headers.trailer, 'return-early content-md5') + t.assert.strictEqual(res.trailers['return-early'], 'return') + t.assert.strictEqual(res.trailers['content-md5'], md5) + t.assert.ok(!res.headers['content-length']) + testDone() }) }) - t.test('async-await', t => { + test('async-await', (t, testDone) => { t.plan(9) const fastify = Fastify() fastify.get('/', function (request, reply) { reply.trailer('Return-Early', async function (reply, payload) { - t.equal(data, payload) + t.assert.strictEqual(data, payload) return 'return' }) reply.trailer('Content-MD5', async function (reply, payload) { - t.equal(data, payload) + t.assert.strictEqual(data, payload) const hash = createHash('md5') hash.update(payload) await sleep(500) @@ -251,18 +256,19 @@ test('trailer handler counter', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers['transfer-encoding'], 'chunked') - t.equal(res.headers.trailer, 'return-early content-md5') - t.equal(res.trailers['return-early'], 'return') - t.equal(res.trailers['content-md5'], md5) - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(res.headers.trailer, 'return-early content-md5') + t.assert.strictEqual(res.trailers['return-early'], 'return') + t.assert.strictEqual(res.trailers['content-md5'], md5) + t.assert.ok(!res.headers['content-length']) + testDone() }) }) }) -test('removeTrailer', t => { +test('removeTrailer', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -273,7 +279,7 @@ test('removeTrailer', t => { done(null, 'custom-etag') }) reply.trailer('Should-Not-Call', function (reply, payload, done) { - t.fail('it should not called as this trailer is removed') + t.assert.fail('it should not called as this trailer is removed') done(null, 'should-not-call') }) reply.removeTrailer('Should-Not-Call') @@ -284,28 +290,29 @@ test('removeTrailer', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers.trailer, 'etag') - t.equal(res.trailers.etag, 'custom-etag') - t.notOk(res.trailers['should-not-call']) - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.trailers['should-not-call']) + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('remove all trailers', t => { +test('remove all trailers', (t, testDone) => { t.plan(6) const fastify = Fastify() fastify.get('/', function (request, reply) { reply.trailer('ETag', function (reply, payload, done) { - t.fail('it should not called as this trailer is removed') + t.assert.fail('it should not called as this trailer is removed') done(null, 'custom-etag') }) reply.removeTrailer('ETag') reply.trailer('Should-Not-Call', function (reply, payload, done) { - t.fail('it should not called as this trailer is removed') + t.assert.fail('it should not called as this trailer is removed') done(null, 'should-not-call') }) reply.removeTrailer('Should-Not-Call') @@ -316,33 +323,34 @@ test('remove all trailers', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.notOk(res.headers.trailer) - t.notOk(res.trailers.etag) - t.notOk(res.trailers['should-not-call']) - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.ok(!res.headers.trailer) + t.assert.ok(!res.trailers.etag) + t.assert.ok(!res.trailers['should-not-call']) + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('hasTrailer', t => { +test('hasTrailer', (t, testDone) => { t.plan(10) const fastify = Fastify() fastify.get('/', function (request, reply) { - t.equal(reply.hasTrailer('ETag'), false) + t.assert.strictEqual(reply.hasTrailer('ETag'), false) reply.trailer('ETag', function (reply, payload, done) { done(null, 'custom-etag') }) - t.equal(reply.hasTrailer('ETag'), true) + t.assert.strictEqual(reply.hasTrailer('ETag'), true) reply.trailer('Should-Not-Call', function (reply, payload, done) { - t.fail('it should not called as this trailer is removed') + t.assert.fail('it should not called as this trailer is removed') done(null, 'should-not-call') }) - t.equal(reply.hasTrailer('Should-Not-Call'), true) + t.assert.strictEqual(reply.hasTrailer('Should-Not-Call'), true) reply.removeTrailer('Should-Not-Call') - t.equal(reply.hasTrailer('Should-Not-Call'), false) + t.assert.strictEqual(reply.hasTrailer('Should-Not-Call'), false) reply.send(undefined) }) @@ -350,16 +358,17 @@ test('hasTrailer', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) - t.equal(res.headers.trailer, 'etag') - t.equal(res.trailers.etag, 'custom-etag') - t.notOk(res.trailers['should-not-call']) - t.notHas(res.headers, 'content-length') + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.trailers['should-not-call']) + t.assert.ok(!res.headers['content-length']) + testDone() }) }) -test('throw error when trailer header name is not allowed', t => { +test('throw error when trailer header name is not allowed', (t, testDone) => { const INVALID_TRAILERS = [ 'transfer-encoding', 'content-length', @@ -383,7 +392,7 @@ test('throw error when trailer header name is not allowed', t => { try { reply.trailer(key, () => { }) } catch (err) { - t.equal(err.message, `Called reply.trailer with an invalid header name: ${key}`) + t.assert.strictEqual(err.message, `Called reply.trailer with an invalid header name: ${key}`) } } reply.send('') @@ -393,12 +402,13 @@ test('throw error when trailer header name is not allowed', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('throw error when trailer header value is not function', t => { +test('throw error when trailer header value is not function', (t, testDone) => { const INVALID_TRAILERS_VALUE = [ undefined, null, @@ -418,7 +428,7 @@ test('throw error when trailer header value is not function', t => { try { reply.trailer('invalid', value) } catch (err) { - t.equal(err.message, `Called reply.trailer('invalid', fn) with an invalid type: ${typeof value}. Expected a function.`) + t.assert.strictEqual(err.message, `Called reply.trailer('invalid', fn) with an invalid type: ${typeof value}. Expected a function.`) } } reply.send('') @@ -428,7 +438,8 @@ test('throw error when trailer header value is not function', t => { method: 'GET', url: '/' }, (error, res) => { - t.error(error) - t.equal(res.statusCode, 200) + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) From 0c18914748e2b67d1fc7adfcecef2199399cb218 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 11 May 2025 09:09:03 +0200 Subject: [PATCH 1016/1295] test: updated promises.test.js re-added the plan() method (#6057) --- test/promises.test.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/test/promises.test.js b/test/promises.test.js index 5f489dfc622..c3ee8162d8e 100644 --- a/test/promises.test.js +++ b/test/promises.test.js @@ -65,7 +65,8 @@ fastify.get('/return-reply', opts, function (req, reply) { fastify.listen({ port: 0 }, err => { assert.ifError(err) - test('shorthand - sget return promise es6 get', (t, testDone) => { + test('shorthand - sget return promise es6 get', (t, done) => { + t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/return' @@ -74,22 +75,24 @@ fastify.listen({ port: 0 }, err => { t.assert.strictEqual(response.statusCode, 200) t.assert.strictEqual(response.headers['content-length'], '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - testDone() + done() }) }) - test('shorthand - sget promise es6 get return error', (t, testDone) => { + test('shorthand - sget promise es6 get return error', (t, done) => { + t.plan(2) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/return-error' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 500) - testDone() + done() }) }) - test('sget promise double send', (t, testDone) => { + test('sget promise double send', (t, done) => { + t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/double' @@ -97,11 +100,12 @@ fastify.listen({ port: 0 }, err => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 200) t.assert.deepStrictEqual(JSON.parse(body), { hello: '42' }) - testDone() + done() }) }) - test('thenable', (t, testDone) => { + test('thenable', (t, done) => { + t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/thenable' @@ -110,22 +114,24 @@ fastify.listen({ port: 0 }, err => { t.assert.strictEqual(response.statusCode, 200) t.assert.strictEqual(response.headers['content-length'], '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - testDone() + done() }) }) - test('thenable (error)', (t, testDone) => { + test('thenable (error)', (t, done) => { + t.plan(2) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/thenable-error' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 500) - testDone() + done() }) }) - test('return-reply', (t, testDone) => { + test('return-reply', (t, done) => { + t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/return-reply' @@ -134,7 +140,7 @@ fastify.listen({ port: 0 }, err => { t.assert.strictEqual(response.statusCode, 200) t.assert.strictEqual(response.headers['content-length'], '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - testDone() + done() }) }) }) From 8537e82704a321cdfb197bfdf6da44b83ed142a1 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Sun, 11 May 2025 08:13:22 -0300 Subject: [PATCH 1017/1295] ci: add support to test release candidates (#6103) --- .github/workflows/ci.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80bec4c945c..515c23abbcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,12 @@ on: paths-ignore: - 'docs/**' - '*.md' + workflow_dispatch: + inputs: + nodejs-version: + description: 'Node.js version to use (e.g., 24.0.0-rc.1)' + required: true + type: string # This allows a subsequently queued workflow run to interrupt previous runs concurrency: @@ -139,6 +145,34 @@ jobs: run: | npm run unit + # Useful for testing Release Candidates of Node.js + test-unit-custom: + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Use Custom Node.js Version + uses: actions/setup-node@v4 + with: + node-version: ${{ github.event.inputs.nodejs-version }} + cache: 'npm' + cache-dependency-path: package.json + check-latest: true + + - name: Install + run: | + npm install --ignore-scripts + + - name: Run tests + run: | + npm run unit + test-typescript: needs: - lint From 220b5320b3560bb0f34b3b0c3de16b09d53e1bca Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 13 May 2025 18:12:16 +0200 Subject: [PATCH 1018/1295] Bumped v5.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac8f35e8e42..7a892971280 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.3.2", + "version": "5.3.3", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From be8f72fcb3c99fad1982fe4853119a9a79db40ba Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 13 May 2025 18:14:48 +0200 Subject: [PATCH 1019/1295] chore: update code version --- fastify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastify.js b/fastify.js index 1238cfdcde2..793981bb1af 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.3.2' +const VERSION = '5.3.3' const Avvio = require('avvio') const http = require('node:http') From 83a8096eab5b0877145496deed3a395112143164 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Thu, 15 May 2025 10:51:35 +0200 Subject: [PATCH 1020/1295] test: mv routes-* from tap (#6092) --- test/route-hooks.test.js | 204 +++++++++++++----------- test/route-prefix.test.js | 327 ++++++++++++++++++++++---------------- 2 files changed, 306 insertions(+), 225 deletions(-) diff --git a/test/route-hooks.test.js b/test/route-hooks.test.js index 422a3aef23c..8b62696724b 100644 --- a/test/route-hooks.test.js +++ b/test/route-hooks.test.js @@ -1,7 +1,7 @@ 'use strict' const { Readable } = require('node:stream') -const test = require('tap').test +const { test } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../') @@ -16,13 +16,13 @@ function endRouteHook (doneOrPayload, done, doneValue) { } function testExecutionHook (hook) { - test(`${hook}`, t => { + test(`${hook}`, (t, testDone) => { t.plan(3) const fastify = Fastify() fastify.post('/', { [hook]: (req, reply, doneOrPayload, done) => { - t.pass('hook called') + t.assert.ok('hook called') endRouteHook(doneOrPayload, done) } }, (req, reply) => { @@ -34,13 +34,14 @@ function testExecutionHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() }) }) - test(`${hook} option should be called after ${hook} hook`, t => { + test(`${hook} option should be called after ${hook} hook`, (t, testDone) => { t.plan(3) const fastify = Fastify() const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { @@ -48,13 +49,13 @@ function testExecutionHook (hook) { }) fastify.addHook(hook, (req, reply, doneOrPayload, done) => { - t.equal(checker.check, 1) + t.assert.strictEqual(checker.check, 1) endRouteHook(doneOrPayload, done) }) fastify.post('/', { [hook]: (req, reply, doneOrPayload, done) => { - t.equal(checker.check, 2) + t.assert.strictEqual(checker.check, 2) endRouteHook(doneOrPayload, done) } }, (req, reply) => { @@ -66,11 +67,12 @@ function testExecutionHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) - test(`${hook} option could accept an array of functions`, t => { + test(`${hook} option could accept an array of functions`, (t, testDone) => { t.plan(3) const fastify = Fastify() const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { @@ -80,11 +82,11 @@ function testExecutionHook (hook) { fastify.post('/', { [hook]: [ (req, reply, doneOrPayload, done) => { - t.equal(checker.check, 1) + t.assert.strictEqual(checker.check, 1) endRouteHook(doneOrPayload, done) }, (req, reply, doneOrPayload, done) => { - t.equal(checker.check, 2) + t.assert.strictEqual(checker.check, 2) endRouteHook(doneOrPayload, done) } ] @@ -97,11 +99,12 @@ function testExecutionHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) - test(`${hook} option could accept an array of async functions`, t => { + test(`${hook} option could accept an array of async functions`, (t, testDone) => { t.plan(3) const fastify = Fastify() const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { @@ -111,10 +114,10 @@ function testExecutionHook (hook) { fastify.post('/', { [hook]: [ async (req, reply) => { - t.equal(checker.check, 1) + t.assert.strictEqual(checker.check, 1) }, async (req, reply) => { - t.equal(checker.check, 2) + t.assert.strictEqual(checker.check, 2) } ] }, (req, reply) => { @@ -126,11 +129,12 @@ function testExecutionHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) - test(`${hook} option does not interfere with ${hook} hook`, t => { + test(`${hook} option does not interfere with ${hook} hook`, (t, testDone) => { t.plan(7) const fastify = Fastify() const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { @@ -138,13 +142,13 @@ function testExecutionHook (hook) { }) fastify.addHook(hook, (req, reply, doneOrPayload, done) => { - t.equal(checker.check, 1) + t.assert.strictEqual(checker.check, 1) endRouteHook(doneOrPayload, done) }) fastify.post('/', { [hook]: (req, reply, doneOrPayload, done) => { - t.equal(checker.check, 2) + t.assert.strictEqual(checker.check, 2) endRouteHook(doneOrPayload, done) } }, handler) @@ -159,8 +163,8 @@ function testExecutionHook (hook) { method: 'post', url: '/' }, (err, res) => { - t.error(err) - t.equal(checker.calledTimes, 2) + t.assert.ifError(err) + t.assert.strictEqual(checker.calledTimes, 2) checker.calledTimes = 0 @@ -168,15 +172,16 @@ function testExecutionHook (hook) { method: 'post', url: '/no' }, (err, res) => { - t.error(err) - t.equal(checker.calledTimes, 1) + t.assert.ifError(err) + t.assert.strictEqual(checker.calledTimes, 1) + testDone() }) }) }) } function testBeforeHandlerHook (hook) { - test(`${hook} option should be unique per route`, t => { + test(`${hook} option should be unique per route`, (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -198,9 +203,9 @@ function testBeforeHandlerHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'earth' }) + t.assert.deepStrictEqual(payload, { hello: 'earth' }) }) fastify.inject({ @@ -208,13 +213,14 @@ function testBeforeHandlerHook (hook) { url: '/no', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() }) }) - test(`${hook} option should handle errors`, t => { + test(`${hook} option should handle errors`, (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -231,25 +237,26 @@ function testBeforeHandlerHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(res.statusCode, 500) - t.same(payload, { + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(payload, { message: 'kaboom', error: 'Internal Server Error', statusCode: 500 }) + testDone() }) }) - test(`${hook} option should handle throwing objects`, t => { + test(`${hook} option should handle throwing objects`, (t, testDone) => { t.plan(4) const fastify = Fastify() const myError = { myError: 'kaboom' } fastify.setErrorHandler(async (error, request, reply) => { - t.same(error, myError, 'the error object throws by the user') + t.assert.deepStrictEqual(error, myError, 'the error object throws by the user') return reply.code(500).send({ this: 'is', my: 'error' }) }) @@ -258,20 +265,21 @@ function testBeforeHandlerHook (hook) { throw myError } }, (req, reply) => { - t.fail('the handler must not be called') + t.assert.fail('the handler must not be called') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(res.json(), { this: 'is', my: 'error' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { this: 'is', my: 'error' }) + testDone() }) }) - test(`${hook} option should handle throwing objects by default`, t => { + test(`${hook} option should handle throwing objects by default`, (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -281,20 +289,21 @@ function testBeforeHandlerHook (hook) { throw { myError: 'kaboom', message: 'i am an error' } } }, (req, reply) => { - t.fail('the handler must not be called') + t.assert.fail('the handler must not be called') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(res.json(), { myError: 'kaboom', message: 'i am an error' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { myError: 'kaboom', message: 'i am an error' }) + testDone() }) }) - test(`${hook} option should handle errors with custom status code`, t => { + test(`${hook} option should handle errors with custom status code`, (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -312,18 +321,19 @@ function testBeforeHandlerHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.equal(res.statusCode, 401) - t.same(payload, { + t.assert.strictEqual(res.statusCode, 401) + t.assert.deepStrictEqual(payload, { message: 'go away', error: 'Unauthorized', statusCode: 401 }) + testDone() }) }) - test(`${hook} option should keep the context`, t => { + test(`${hook} option should keep the context`, (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -331,7 +341,7 @@ function testBeforeHandlerHook (hook) { fastify.post('/', { [hook]: function (req, reply, doneOrPayload, done) { - t.equal(this.foo, 42) + t.assert.strictEqual(this.foo, 42) this.foo += 1 endRouteHook(doneOrPayload, done) } @@ -344,13 +354,14 @@ function testBeforeHandlerHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { foo: 43 }) + t.assert.deepStrictEqual(payload, { foo: 43 }) + testDone() }) }) - test(`${hook} option should keep the context (array)`, t => { + test(`${hook} option should keep the context (array)`, (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -358,7 +369,7 @@ function testBeforeHandlerHook (hook) { fastify.post('/', { [hook]: [function (req, reply, doneOrPayload, done) { - t.equal(this.foo, 42) + t.assert.strictEqual(this.foo, 42) this.foo += 1 endRouteHook(doneOrPayload, done) }] @@ -371,9 +382,10 @@ function testBeforeHandlerHook (hook) { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { foo: 43 }) + t.assert.deepStrictEqual(payload, { foo: 43 }) + testDone() }) }) } @@ -390,12 +402,12 @@ testBeforeHandlerHook('onRequest') testBeforeHandlerHook('preValidation') testBeforeHandlerHook('preParsing') -test('preValidation option should be called before preHandler hook', t => { +test('preValidation option should be called before preHandler hook', (t, testDone) => { t.plan(3) const fastify = Fastify() fastify.addHook('preHandler', (req, reply, done) => { - t.ok(req.called) + t.assert.ok(req.called) done() }) @@ -413,13 +425,14 @@ test('preValidation option should be called before preHandler hook', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() }) }) -test('preSerialization option should be able to modify the payload', t => { +test('preSerialization option should be able to modify the payload', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -435,18 +448,19 @@ test('preSerialization option should be able to modify the payload', t => { method: 'GET', url: '/only' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'another world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'another world' }) + testDone() }) }) -test('preParsing option should be called before preValidation hook', t => { +test('preParsing option should be called before preValidation hook', (t, testDone) => { t.plan(3) const fastify = Fastify() fastify.addHook('preValidation', (req, reply, done) => { - t.ok(req.called) + t.assert.ok(req.called) done() }) @@ -464,13 +478,14 @@ test('preParsing option should be called before preValidation hook', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() }) }) -test('preParsing option should be able to modify the payload', t => { +test('preParsing option should be able to modify the payload', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -491,13 +506,14 @@ test('preParsing option should be able to modify the payload', t => { url: '/only', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'another world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'another world' }) + testDone() }) }) -test('preParsing option should be able to supply statusCode', t => { +test('preParsing option should be able to supply statusCode', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -514,10 +530,10 @@ test('preParsing option should be able to supply statusCode', t => { return stream }, onError: async (req, res, err) => { - t.equal(err.statusCode, 408) + t.assert.strictEqual(err.statusCode, 408) } }, (req, reply) => { - t.fail('should not be called') + t.assert.fail('should not be called') }) fastify.inject({ @@ -525,22 +541,23 @@ test('preParsing option should be able to supply statusCode', t => { url: '/only', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 408) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 408) + t.assert.deepStrictEqual(JSON.parse(res.payload), { statusCode: 408, error: 'Request Timeout', message: 'kaboom' }) + testDone() }) }) -test('onRequest option should be called before preParsing', t => { +test('onRequest option should be called before preParsing', (t, testDone) => { t.plan(3) const fastify = Fastify() fastify.addHook('preParsing', (req, reply, payload, done) => { - t.ok(req.called) + t.assert.ok(req.called) done() }) @@ -558,39 +575,41 @@ test('onRequest option should be called before preParsing', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payload = JSON.parse(res.payload) - t.same(payload, { hello: 'world' }) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() }) }) -test('onTimeout on route', t => { +test('onTimeout on route', (t, testDone) => { t.plan(4) const fastify = Fastify({ connectionTimeout: 500 }) fastify.get('/timeout', { handler (request, reply) { }, onTimeout (request, reply, done) { - t.pass('onTimeout called') + t.assert.ok('onTimeout called') done() } }) fastify.listen({ port: 0 }, (err, address) => { - t.error(err) - t.teardown(() => fastify.close()) + t.assert.ifError(err) + t.after(() => fastify.close()) sget({ method: 'GET', url: `${address}/timeout` }, (err, response, body) => { - t.type(err, Error) - t.equal(err.message, 'socket hang up') + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'socket hang up') + testDone() }) }) }) -test('onError on route', t => { +test('onError on route', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -600,7 +619,7 @@ test('onError on route', t => { fastify.get('/', { onError (request, reply, error, done) { - t.match(error, err) + t.assert.deepStrictEqual(error, err) done() } }, @@ -609,11 +628,12 @@ test('onError on route', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', message: 'kaboom', statusCode: 500 }) + testDone() }) }) diff --git a/test/route-prefix.test.js b/test/route-prefix.test.js index 7999d849564..d910a9a6d7a 100644 --- a/test/route-prefix.test.js +++ b/test/route-prefix.test.js @@ -1,10 +1,10 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') +const { waitForCb } = require('./toolkit') -test('Prefix options should add a prefix for all the routes inside a register / 1', t => { +test('Prefix options should add a prefix for all the routes inside a register / 1', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -27,32 +27,37 @@ test('Prefix options should add a prefix for all the routes inside a register / done() }, { prefix: '/v1' }) + const completion = waitForCb({ steps: 3 }) fastify.inject({ method: 'GET', url: '/first' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/first' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/first' }) + completion.stepIn() }) fastify.inject({ method: 'GET', url: '/v1/first' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/v1/first' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/first' }) + completion.stepIn() }) fastify.inject({ method: 'GET', url: '/v1/v2/first' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/v1/v2/first' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/v2/first' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Prefix options should add a prefix for all the routes inside a register / 2', t => { +test('Prefix options should add a prefix for all the routes inside a register / 2', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -67,24 +72,27 @@ test('Prefix options should add a prefix for all the routes inside a register / done() }, { prefix: '/v1' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/v1/first' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/v1/first' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/first' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/v1/second' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/v1/second' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/second' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Prefix options should add a prefix for all the chained routes inside a register / 3', t => { +test('Prefix options should add a prefix for all the chained routes inside a register / 3', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -100,24 +108,27 @@ test('Prefix options should add a prefix for all the chained routes inside a reg done() }, { prefix: '/v1' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/v1/first' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/v1/first' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/first' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/v1/second' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/v1/second' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/second' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Prefix should support parameters as well', t => { +test('Prefix should support parameters as well', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -132,12 +143,13 @@ test('Prefix should support parameters as well', t => { method: 'GET', url: '/v1/param/hello' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { id: 'param' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { id: 'param' }) + testDone() }) }) -test('Prefix should support /', t => { +test('Prefix should support /', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -152,12 +164,13 @@ test('Prefix should support /', t => { method: 'GET', url: '/v1' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('Prefix without /', t => { +test('Prefix without /', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -172,12 +185,13 @@ test('Prefix without /', t => { method: 'GET', url: '/v1' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('Prefix with trailing /', t => { +test('Prefix with trailing /', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -199,32 +213,35 @@ test('Prefix with trailing /', t => { done() }, { prefix: '/v1/' }) + const completion = waitForCb({ steps: 3 }) fastify.inject({ method: 'GET', url: '/v1/route1' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world1' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world1' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/v1/route2' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world2' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world2' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/v1/inner/route3' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world3' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world3' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Prefix works multiple levels deep', t => { +test('Prefix works multiple levels deep', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -248,12 +265,13 @@ test('Prefix works multiple levels deep', t => { method: 'GET', url: '/v1/v2/v3' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('Different register - encapsulation check', t => { +test('Different register - encapsulation check', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -281,24 +299,27 @@ test('Different register - encapsulation check', t => { done() }, { prefix: '/v3' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/v1/v2' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/v1/v2' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/v2' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/v3/v4' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { route: '/v3/v4' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v3/v4' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Can retrieve prefix within encapsulated instances', t => { +test('Can retrieve prefix within encapsulated instances', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -317,24 +338,27 @@ test('Can retrieve prefix within encapsulated instances', t => { done() }, { prefix: '/v1' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/v1/one' }, (err, res) => { - t.error(err) - t.equal(res.payload, '/v1') + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, '/v1') + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/v1/v2/two' }, (err, res) => { - t.error(err) - t.equal(res.payload, '/v1/v2') + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, '/v1/v2') + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches both /prefix and /prefix/ with a / route', t => { +test('matches both /prefix and /prefix/ with a / route', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -346,24 +370,27 @@ test('matches both /prefix and /prefix/ with a / route', t => { done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('prefix "/prefix/" does not match "/prefix" with a / route', t => { +test('prefix "/prefix/" does not match "/prefix" with a / route', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -375,24 +402,27 @@ test('prefix "/prefix/" does not match "/prefix" with a / route', t => { done() }, { prefix: '/prefix/' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.statusCode, 404) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true', t => { +test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreTrailingSlash: true @@ -406,24 +436,27 @@ test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: tr done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches both /prefix and /prefix/ with a / route - ignoreDuplicateSlashes: true', t => { +test('matches both /prefix and /prefix/ with a / route - ignoreDuplicateSlashes: true', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreDuplicateSlashes: true @@ -437,24 +470,27 @@ test('matches both /prefix and /prefix/ with a / route - ignoreDuplicateSlashes: done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreTrailingSlash: false', t => { +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreTrailingSlash: false', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreTrailingSlash: false @@ -473,24 +509,27 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: " done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: false', t => { +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: false', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreDuplicateSlashes: false @@ -509,24 +548,27 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: " done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true, ignoreDuplicateSlashes: true', t => { +test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true, ignoreDuplicateSlashes: true', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreTrailingSlash: true, @@ -541,24 +583,27 @@ test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: tr done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true, ignoreDuplicateSlashes: false', t => { +test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true, ignoreDuplicateSlashes: false', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreTrailingSlash: true, @@ -573,24 +618,27 @@ test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: tr done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('returns 404 status code with /prefix/ and / route - prefixTrailingSlash: "both" (default), ignoreTrailingSlash: true', t => { +test('returns 404 status code with /prefix/ and / route - prefixTrailingSlash: "both" (default), ignoreTrailingSlash: true', (t, testDone) => { t.plan(2) const fastify = Fastify({ ignoreTrailingSlash: true @@ -612,16 +660,17 @@ test('returns 404 status code with /prefix/ and / route - prefixTrailingSlash: " method: 'GET', url: '/prefix//' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'Route GET:/prefix// not found', statusCode: 404 }) + testDone() }) }) -test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: true', t => { +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: true', (t, testDone) => { t.plan(2) const fastify = Fastify({ ignoreDuplicateSlashes: true @@ -643,12 +692,13 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: " method: 'GET', url: '/prefix//' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreTrailingSlash: true, ignoreDuplicateSlashes: true', t => { +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreTrailingSlash: true, ignoreDuplicateSlashes: true', (t, testDone) => { t.plan(2) const fastify = Fastify({ ignoreTrailingSlash: true, @@ -671,12 +721,13 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: " method: 'GET', url: '/prefix//' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: true', t => { +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: true', (t, testDone) => { t.plan(2) const fastify = Fastify({ ignoreTrailingSlash: true, @@ -699,12 +750,13 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: " method: 'GET', url: '/prefix//' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreTrailingSlash: false', t => { +test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreTrailingSlash: false', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreTrailingSlash: false @@ -723,24 +775,27 @@ test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ig done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.equal(JSON.parse(res.payload).statusCode, 404) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload).statusCode, 404) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreDuplicateSlashes: false', t => { +test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreDuplicateSlashes: false', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreDuplicateSlashes: false @@ -759,24 +814,27 @@ test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ig done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.equal(JSON.parse(res.payload).statusCode, 404) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload).statusCode, 404) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('matches only /prefix/ with a / route - prefixTrailingSlash: "slash", ignoreTrailingSlash: false', t => { +test('matches only /prefix/ with a / route - prefixTrailingSlash: "slash", ignoreTrailingSlash: false', (t, testDone) => { t.plan(4) const fastify = Fastify({ ignoreTrailingSlash: false @@ -795,21 +853,24 @@ test('matches only /prefix/ with a / route - prefixTrailingSlash: "slash", igno done() }, { prefix: '/prefix' }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/prefix/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/prefix' }, (err, res) => { - t.error(err) - t.equal(JSON.parse(res.payload).statusCode, 404) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload).statusCode, 404) + completion.stepIn() }) + completion.patience.then(testDone) }) test('calls onRoute only once when prefixing', async t => { @@ -839,5 +900,5 @@ test('calls onRoute only once when prefixing', async t => { await fastify.ready() - t.same(onRouteCalled, 1) + t.assert.deepStrictEqual(onRouteCalled, 1) }) From df9ea6f4622c3e41457bf9ce3e0fc9965f4ce627 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Thu, 15 May 2025 10:56:52 +0200 Subject: [PATCH 1021/1295] test: mv skip-reply-send from tap (#6094) --- test/skip-reply-send.test.js | 133 +++++++++++++++++------------------ 1 file changed, 64 insertions(+), 69 deletions(-) diff --git a/test/skip-reply-send.test.js b/test/skip-reply-send.test.js index bf1b6117897..a919aa6a87c 100644 --- a/test/skip-reply-send.test.js +++ b/test/skip-reply-send.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test, describe } = require('node:test') const split = require('split2') const net = require('node:net') const Fastify = require('../fastify') @@ -19,7 +19,7 @@ const lifecycleHooks = [ 'onError' ] -test('skip automatic reply.send() with reply.hijack and a body', (t) => { +test('skip automatic reply.send() with reply.hijack and a body', async (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { @@ -28,8 +28,8 @@ test('skip automatic reply.send() with reply.hijack and a body', (t) => { }) stream.on('data', (line) => { - t.not(line.level, 40) // there are no errors - t.not(line.level, 50) // there are no errors + t.assert.notStrictEqual(line.level, 40) // there are no errors + t.assert.notStrictEqual(line.level, 50) // there are no errors }) app.get('/', (req, reply) => { @@ -39,16 +39,16 @@ test('skip automatic reply.send() with reply.hijack and a body', (t) => { return Promise.resolve('this will be skipped') }) - return app.inject({ + await app.inject({ method: 'GET', url: '/' }).then((res) => { - t.equal(res.statusCode, 200) - t.equal(res.body, 'hello world') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'hello world') }) }) -test('skip automatic reply.send() with reply.hijack and no body', (t) => { +test('skip automatic reply.send() with reply.hijack and no body', async (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { @@ -57,8 +57,8 @@ test('skip automatic reply.send() with reply.hijack and no body', (t) => { }) stream.on('data', (line) => { - t.not(line.level, 40) // there are no error - t.not(line.level, 50) // there are no error + t.assert.notStrictEqual(line.level, 40) // there are no error + t.assert.notStrictEqual(line.level, 50) // there are no error }) app.get('/', (req, reply) => { @@ -68,16 +68,16 @@ test('skip automatic reply.send() with reply.hijack and no body', (t) => { return Promise.resolve() }) - return app.inject({ + await app.inject({ method: 'GET', url: '/' }).then((res) => { - t.equal(res.statusCode, 200) - t.equal(res.body, 'hello world') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'hello world') }) }) -test('skip automatic reply.send() with reply.hijack and an error', (t) => { +test('skip automatic reply.send() with reply.hijack and an error', async (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { @@ -90,8 +90,8 @@ test('skip automatic reply.send() with reply.hijack and an error', (t) => { stream.on('data', (line) => { if (line.level === 50) { errorSeen = true - t.equal(line.err.message, 'kaboom') - t.equal(line.msg, 'Promise errored, but reply.sent = true was set') + t.assert.strictEqual(line.err.message, 'kaboom') + t.assert.strictEqual(line.msg, 'Promise errored, but reply.sent = true was set') } }) @@ -102,13 +102,13 @@ test('skip automatic reply.send() with reply.hijack and an error', (t) => { return Promise.reject(new Error('kaboom')) }) - return app.inject({ + await app.inject({ method: 'GET', url: '/' }).then((res) => { - t.equal(errorSeen, true) - t.equal(res.statusCode, 200) - t.equal(res.body, 'hello world') + t.assert.strictEqual(errorSeen, true) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'hello world') }) }) @@ -117,11 +117,8 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { const previousHooks = lifecycleHooks.slice(0, idx) const nextHooks = lifecycleHooks.slice(idx + 1) - test(`Hijacking inside ${hookOrHandler} skips all the following hooks and handler execution`, t => { - t.plan(4) - const test = t.test - - test('Sending a response using reply.raw => onResponse hook is called', t => { + describe(`Hijacking inside ${hookOrHandler} skips all the following hooks and handler execution`, () => { + test('Sending a response using reply.raw => onResponse hook is called', async (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { @@ -130,11 +127,11 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { }) stream.on('data', (line) => { - t.not(line.level, 40) // there are no errors - t.not(line.level, 50) // there are no errors + t.assert.notStrictEqual(line.level, 40) // there are no errors + t.assert.notStrictEqual(line.level, 50) // there are no errors }) - previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`))) + previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))) if (hookOrHandler === 'handler') { app.get('/', (req, reply) => { @@ -146,41 +143,41 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { reply.hijack() reply.raw.end(`hello from ${hookOrHandler}`) }) - app.get('/', (req, reply) => t.fail('Handler should not be called')) + app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) } nextHooks.forEach(h => { if (h === 'onResponse') { - app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)) + app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`)) } else { - app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)) + app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`)) } }) - return app.inject({ + await app.inject({ method: 'GET', url: '/' }).then((res) => { - t.equal(res.statusCode, 200) - t.equal(res.body, `hello from ${hookOrHandler}`) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, `hello from ${hookOrHandler}`) }) }) - test('Sending a response using req.socket => onResponse not called', t => { + test('Sending a response using req.socket => onResponse not called', (t, testDone) => { const stream = split(JSON.parse) const app = Fastify({ logger: { stream } }) - t.teardown(() => app.close()) + t.after(() => app.close()) stream.on('data', (line) => { - t.not(line.level, 40) // there are no errors - t.not(line.level, 50) // there are no errors + t.assert.notStrictEqual(line.level, 40) // there are no errors + t.assert.notStrictEqual(line.level, 50) // there are no errors }) - previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`))) + previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))) if (hookOrHandler === 'handler') { app.get('/', (req, reply) => { @@ -196,13 +193,13 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { req.socket.write(`hello from ${hookOrHandler}`) req.socket.end() }) - app.get('/', (req, reply) => t.fail('Handler should not be called')) + app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) } - nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`))) + nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`))) app.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const client = net.createConnection({ port: (app.server.address()).port }, () => { client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') @@ -213,36 +210,36 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { }) client.on('end', function () { - t.match(chunks, new RegExp(`hello from ${hookOrHandler}`, 'i')) - t.end() + t.assert.match(chunks, new RegExp(`hello from ${hookOrHandler}`, 'i')) + testDone() }) }) }) }) - test('Throwing an error does not trigger any hooks', t => { + test('Throwing an error does not trigger any hooks', async (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { stream } }) - t.teardown(() => app.close()) + t.after(() => app.close()) let errorSeen = false stream.on('data', (line) => { if (hookOrHandler === 'handler') { if (line.level === 40) { errorSeen = true - t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT') + t.assert.strictEqual(line.err.code, 'FST_ERR_REP_ALREADY_SENT') } } else { - t.not(line.level, 40) // there are no errors - t.not(line.level, 50) // there are no errors + t.assert.notStrictEqual(line.level, 40) // there are no errors + t.assert.notStrictEqual(line.level, 50) // there are no errors } }) - previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`))) + previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))) if (hookOrHandler === 'handler') { app.get('/', (req, reply) => { @@ -254,23 +251,22 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { reply.hijack() throw new Error('This wil be skipped') }) - app.get('/', (req, reply) => t.fail('Handler should not be called')) + app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) } - nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`))) + nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`))) - return Promise.race([ + await Promise.race([ app.inject({ method: 'GET', url: '/' }), new Promise((resolve, reject) => setTimeout(resolve, 1000)) - ]).then((err, res) => { - t.error(err) - if (hookOrHandler === 'handler') { - t.equal(errorSeen, true) - } - }) + ]) + + if (hookOrHandler === 'handler') { + t.assert.strictEqual(errorSeen, true) + } }) - test('Calling reply.send() after hijacking logs a warning', t => { + test('Calling reply.send() after hijacking logs a warning', async (t) => { const stream = split(JSON.parse) const app = Fastify({ logger: { @@ -283,11 +279,11 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { stream.on('data', (line) => { if (line.level === 40) { errorSeen = true - t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT') + t.assert.strictEqual(line.err.code, 'FST_ERR_REP_ALREADY_SENT') } }) - previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`))) + previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))) if (hookOrHandler === 'handler') { app.get('/', (req, reply) => { @@ -299,18 +295,17 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { reply.hijack() return reply.send('hello from reply.send()') }) - app.get('/', (req, reply) => t.fail('Handler should not be called')) + app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) } - nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`))) + nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`))) - return Promise.race([ + await Promise.race([ app.inject({ method: 'GET', url: '/' }), new Promise((resolve, reject) => setTimeout(resolve, 1000)) - ]).then((err, res) => { - t.error(err) - t.equal(errorSeen, true) - }) + ]) + + t.assert.strictEqual(errorSeen, true) }) }) } From 7090d75d3769d6e05ddc577d465c2f6b9e0d62db Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Thu, 15 May 2025 11:01:19 +0200 Subject: [PATCH 1022/1295] test: mv plugins from tap (#6088) --- test/plugin.2.test.js | 190 ++++++++++++++++++--------------- test/plugin.3.test.js | 91 ++++++++++------ test/plugin.4.test.js | 243 +++++++++++++++++++++--------------------- 3 files changed, 284 insertions(+), 240 deletions(-) diff --git a/test/plugin.2.test.js b/test/plugin.2.test.js index 1bfbccc4720..bdee9585731 100644 --- a/test/plugin.2.test.js +++ b/test/plugin.2.test.js @@ -1,36 +1,36 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../fastify') const sget = require('simple-get').concat const fp = require('fastify-plugin') +const { waitForCb } = require('./toolkit') -test('check dependencies - should not throw', t => { +test('check dependencies - should not throw', (t, testDone) => { t.plan(12) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.register(fp((i, o, n) => { i.decorate('test', () => {}) - t.ok(i.test) + t.assert.ok(i.test) n() })) instance.register(fp((i, o, n) => { try { i.decorate('otherTest', () => {}, ['test']) - t.ok(i.test) - t.ok(i.otherTest) + t.assert.ok(i.test) + t.assert.ok(i.otherTest) n() } catch (e) { - t.fail() + t.assert.fail() } })) instance.get('/', (req, reply) => { - t.ok(instance.test) - t.ok(instance.otherTest) + t.assert.ok(instance.test) + t.assert.ok(instance.otherTest) reply.send({ hello: 'world' }) }) @@ -38,27 +38,28 @@ test('check dependencies - should not throw', t => { }) fastify.ready(() => { - t.notOk(fastify.test) - t.notOk(fastify.otherTest) + t.assert.ok(!fastify.test) + t.assert.ok(!fastify.otherTest) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + testDone() }) }) }) -test('check dependencies - should throw', t => { +test('check dependencies - should throw', (t, testDone) => { t.plan(12) const fastify = Fastify() @@ -66,24 +67,24 @@ test('check dependencies - should throw', t => { instance.register(fp((i, o, n) => { try { i.decorate('otherTest', () => {}, ['test']) - t.fail() + t.assert.fail() } catch (e) { - t.equal(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') - t.equal(e.message, 'The decorator is missing dependency \'test\'.') + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'test\'.') } n() })) instance.register(fp((i, o, n) => { i.decorate('test', () => {}) - t.ok(i.test) - t.notOk(i.otherTest) + t.assert.ok(i.test) + t.assert.ok(!i.otherTest) n() })) instance.get('/', (req, reply) => { - t.ok(instance.test) - t.notOk(instance.otherTest) + t.assert.ok(instance.test) + t.assert.ok(!instance.otherTest) reply.send({ hello: 'world' }) }) @@ -91,183 +92,192 @@ test('check dependencies - should throw', t => { }) fastify.ready(() => { - t.notOk(fastify.test) + t.assert.ok(!fastify.test) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + testDone() }) }) }) -test('set the plugin name based on the plugin displayName symbol', t => { +test('set the plugin name based on the plugin displayName symbol', (t, testDone) => { t.plan(6) const fastify = Fastify() + t.after(() => fastify.close()) fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A') + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A') fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') done() }, { name: 'plugin-AB' })) fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') done() }, { name: 'plugin-AC' })) done() }, { name: 'plugin-A' })) fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC -> plugin-B') + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC -> plugin-B') done() }, { name: 'plugin-B' })) - t.equal(fastify.pluginName, 'fastify') + t.assert.strictEqual(fastify.pluginName, 'fastify') fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() + t.assert.ifError(err) + testDone() }) }) -test('plugin name will change when using no encapsulation', t => { +test('plugin name will change when using no encapsulation', (t, testDone) => { t.plan(6) const fastify = Fastify() + t.after(() => fastify.close()) fastify.register(fp((fastify, opts, done) => { // store it in a different variable will hold the correct name const pluginName = fastify.pluginName fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') done() }, { name: 'plugin-AB' })) fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') done() }, { name: 'plugin-AC' })) setImmediate(() => { // normally we would expect the name plugin-A // but we operate on the same instance in each plugin - t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') - t.equal(pluginName, 'fastify -> plugin-A') + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + t.assert.strictEqual(pluginName, 'fastify -> plugin-A') }) done() }, { name: 'plugin-A' })) - t.equal(fastify.pluginName, 'fastify') + t.assert.strictEqual(fastify.pluginName, 'fastify') fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() + t.assert.ifError(err) + testDone() }) }) -test('plugin name is undefined when accessing in no plugin context', t => { +test('plugin name is undefined when accessing in no plugin context', (t, testDone) => { t.plan(2) const fastify = Fastify() + t.after(() => fastify.close()) - t.equal(fastify.pluginName, 'fastify') + t.assert.strictEqual(fastify.pluginName, 'fastify') fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() + t.assert.ifError(err) + testDone() }) }) -test('set the plugin name based on the plugin function name', t => { +test('set the plugin name based on the plugin function name', (t, testDone) => { t.plan(5) const fastify = Fastify() + t.after(() => fastify.close()) fastify.register(function myPluginA (fastify, opts, done) { - t.equal(fastify.pluginName, 'myPluginA') + t.assert.strictEqual(fastify.pluginName, 'myPluginA') fastify.register(function myPluginAB (fastify, opts, done) { - t.equal(fastify.pluginName, 'myPluginAB') + t.assert.strictEqual(fastify.pluginName, 'myPluginAB') done() }) setImmediate(() => { // exact name due to encapsulation - t.equal(fastify.pluginName, 'myPluginA') + t.assert.strictEqual(fastify.pluginName, 'myPluginA') }) done() }) fastify.register(function myPluginB (fastify, opts, done) { - t.equal(fastify.pluginName, 'myPluginB') + t.assert.strictEqual(fastify.pluginName, 'myPluginB') done() }) fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() + t.assert.ifError(err) + testDone() }) }) -test('approximate a plugin name when no meta data is available', t => { +test('approximate a plugin name when no meta data is available', (t, testDone) => { t.plan(7) const fastify = Fastify() + t.after(() => fastify.close()) fastify.register((fastify, opts, done) => { // A - t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) - t.equal(fastify.pluginName.includes('// A'), true) + t.assert.strictEqual(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.assert.strictEqual(fastify.pluginName.includes('// A'), true) fastify.register((fastify, opts, done) => { // B - t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) - t.equal(fastify.pluginName.includes('// B'), true) + t.assert.strictEqual(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.assert.strictEqual(fastify.pluginName.includes('// B'), true) done() }) setImmediate(() => { - t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true) - t.equal(fastify.pluginName.includes('// A'), true) + t.assert.strictEqual(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.assert.strictEqual(fastify.pluginName.includes('// A'), true) }) done() }) fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() + t.assert.ifError(err) + testDone() }) }) -test('approximate a plugin name also when fastify-plugin has no meta data', t => { +test('approximate a plugin name also when fastify-plugin has no meta data', (t, testDone) => { t.plan(4) const fastify = Fastify() + t.after(() => fastify.close()) + // plugin name is got from current file name const pluginName = /plugin\.2\.test/ const pluginNameWithFunction = /plugin\.2\.test-auto-\d+ -> B/ fastify.register(fp((fastify, opts, done) => { - t.match(fastify.pluginName, pluginName) + t.assert.match(fastify.pluginName, pluginName) fastify.register(fp(function B (fastify, opts, done) { // function has name - t.match(fastify.pluginName, pluginNameWithFunction) + t.assert.match(fastify.pluginName, pluginNameWithFunction) done() })) setImmediate(() => { - t.match(fastify.pluginName, pluginNameWithFunction) + t.assert.match(fastify.pluginName, pluginNameWithFunction) }) done() })) fastify.listen({ port: 0 }, err => { - t.error(err) - fastify.close() + t.assert.ifError(err) + testDone() }) }) -test('plugin encapsulation', t => { +test('plugin encapsulation', (t, testDone) => { t.plan(10) const fastify = Fastify() + t.after(() => fastify.close()) fastify.register((instance, opts, done) => { instance.register(fp((i, o, n) => { @@ -296,31 +306,39 @@ test('plugin encapsulation', t => { }) fastify.ready(() => { - t.notOk(fastify.test) + t.assert.ok(!fastify.test) }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + const completion = waitForCb({ + steps: 2 + }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { plugin: 'first' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { plugin: 'first' }) + completion.stepIn() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/second' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { plugin: 'second' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { plugin: 'second' }) + completion.stepIn() }) + + completion.patience.then(testDone) }) }) diff --git a/test/plugin.3.test.js b/test/plugin.3.test.js index 3218e1359e9..ce518a86f53 100644 --- a/test/plugin.3.test.js +++ b/test/plugin.3.test.js @@ -1,12 +1,12 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('../fastify') const sget = require('simple-get').concat const fp = require('fastify-plugin') +const { waitForCb } = require('./toolkit') -test('if a plugin raises an error and there is not a callback to handle it, the server must not start', t => { +test('if a plugin raises an error and there is not a callback to handle it, the server must not start', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -15,14 +15,16 @@ test('if a plugin raises an error and there is not a callback to handle it, the }) fastify.listen({ port: 0 }, err => { - t.ok(err instanceof Error) - t.equal(err.message, 'err') + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'err') + testDone() }) }) -test('add hooks after route declaration', t => { +test('add hooks after route declaration', (t, testDone) => { t.plan(3) const fastify = Fastify() + t.after(() => fastify.close()) function plugin (instance, opts, done) { instance.decorateRequest('check', null) @@ -58,25 +60,25 @@ test('add hooks after route declaration', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.same(JSON.parse(body), { hook1: true, hook2: true, hook3: true }) - fastify.close() + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(body), { hook1: true, hook2: true, hook3: true }) + testDone() }) }) }) -test('nested plugins', t => { +test('nested plugins', (t, testDone) => { t.plan(5) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(function (fastify, opts, done) { fastify.register((fastify, opts, done) => { @@ -97,32 +99,39 @@ test('nested plugins', t => { }, { prefix: '/parent' }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + + const completion = waitForCb({ + steps: 2 + }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/parent/child1' }, (err, response, body) => { - t.error(err) - t.same(body.toString(), 'I am child 1') + t.assert.ifError(err) + t.assert.deepStrictEqual(body.toString(), 'I am child 1') + completion.stepIn() }) - sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/parent/child2' }, (err, response, body) => { - t.error(err) - t.same(body.toString(), 'I am child 2') + t.assert.ifError(err) + t.assert.deepStrictEqual(body.toString(), 'I am child 2') + completion.stepIn() }) + + completion.patience.then(testDone) }) }) -test('nested plugins awaited', t => { +test('nested plugins awaited', (t, testDone) => { t.plan(5) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(async function wrap (fastify, opts) { await fastify.register(async function child1 (fastify, opts) { @@ -139,27 +148,34 @@ test('nested plugins awaited', t => { }, { prefix: '/parent' }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + + const completion = waitForCb({ + steps: 2 + }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/parent/child1' }, (err, response, body) => { - t.error(err) - t.same(body.toString(), 'I am child 1') + t.assert.ifError(err) + t.assert.deepStrictEqual(body.toString(), 'I am child 1') + completion.stepIn() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/parent/child2' }, (err, response, body) => { - t.error(err) - t.same(body.toString(), 'I am child 2') + t.assert.ifError(err) + t.assert.deepStrictEqual(body.toString(), 'I am child 2') + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('plugin metadata - decorators', t => { +test('plugin metadata - decorators', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -179,7 +195,8 @@ test('plugin metadata - decorators', t => { fastify.register(plugin) fastify.ready(() => { - t.ok(fastify.plugin) + t.assert.ok(fastify.plugin) + testDone() }) function plugin (instance, opts, done) { @@ -188,7 +205,7 @@ test('plugin metadata - decorators', t => { } }) -test('plugin metadata - decorators - should throw', t => { +test('plugin metadata - decorators - should throw', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -206,7 +223,8 @@ test('plugin metadata - decorators - should throw', t => { fastify.register(plugin) fastify.ready((err) => { - t.equal(err.message, "The decorator 'plugin1' is not present in Request") + t.assert.strictEqual(err.message, "The decorator 'plugin1' is not present in Request") + testDone() }) function plugin (instance, opts, done) { @@ -215,7 +233,7 @@ test('plugin metadata - decorators - should throw', t => { } }) -test('plugin metadata - decorators - should throw with plugin name', t => { +test('plugin metadata - decorators - should throw with plugin name', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -234,7 +252,8 @@ test('plugin metadata - decorators - should throw with plugin name', t => { fastify.register(plugin) fastify.ready((err) => { - t.equal(err.message, "The decorator 'plugin1' required by 'the-plugin' is not present in Request") + t.assert.strictEqual(err.message, "The decorator 'plugin1' required by 'the-plugin' is not present in Request") + testDone() }) function plugin (instance, opts, done) { @@ -243,7 +262,7 @@ test('plugin metadata - decorators - should throw with plugin name', t => { } }) -test('plugin metadata - dependencies', t => { +test('plugin metadata - dependencies', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -261,7 +280,8 @@ test('plugin metadata - dependencies', t => { fastify.register(plugin) fastify.ready(() => { - t.pass('everything right') + t.assert.ok('everything right') + testDone() }) function dependency (instance, opts, done) { @@ -273,7 +293,7 @@ test('plugin metadata - dependencies', t => { } }) -test('plugin metadata - dependencies (nested)', t => { +test('plugin metadata - dependencies (nested)', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -291,7 +311,8 @@ test('plugin metadata - dependencies (nested)', t => { fastify.register(plugin) fastify.ready(() => { - t.pass('everything right') + t.assert.ok('everything right') + testDone() }) function dependency (instance, opts, done) { diff --git a/test/plugin.4.test.js b/test/plugin.4.test.js index d48222a215a..497d06cf1db 100644 --- a/test/plugin.4.test.js +++ b/test/plugin.4.test.js @@ -1,13 +1,12 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test, describe } = require('node:test') const Fastify = require('../fastify') const fp = require('fastify-plugin') const fakeTimer = require('@sinonjs/fake-timers') const { FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER } = require('../lib/errors') -test('pluginTimeout', t => { +test('pluginTimeout', (t, testDone) => { t.plan(5) const fastify = Fastify({ pluginTimeout: 10 @@ -16,16 +15,17 @@ test('pluginTimeout', t => { // to no call done on purpose }) fastify.ready((err) => { - t.ok(err) - t.equal(err.message, + t.assert.ok(err) + t.assert.strictEqual(err.message, "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // to no call done on purpose'. You may have forgotten to call 'done' function or to resolve a Promise") - t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') - t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.assert.ok(err.cause) + t.assert.strictEqual(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + testDone() }) }) -test('pluginTimeout - named function', t => { +test('pluginTimeout - named function', (t, testDone) => { t.plan(5) const fastify = Fastify({ pluginTimeout: 10 @@ -34,16 +34,17 @@ test('pluginTimeout - named function', t => { // to no call done on purpose }) fastify.ready((err) => { - t.ok(err) - t.equal(err.message, + t.assert.ok(err) + t.assert.strictEqual(err.message, "fastify-plugin: Plugin did not start in time: 'nameFunction'. You may have forgotten to call 'done' function or to resolve a Promise") - t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') - t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.assert.ok(err.cause) + t.assert.strictEqual(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + testDone() }) }) -test('pluginTimeout default', t => { +test('pluginTimeout default', (t, testDone) => { t.plan(5) const clock = fakeTimer.install({ shouldClearNativeTimers: true }) @@ -54,18 +55,19 @@ test('pluginTimeout default', t => { }) fastify.ready((err) => { - t.ok(err) - t.equal(err.message, + t.assert.ok(err) + t.assert.strictEqual(err.message, "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // default time elapsed without calling done'. You may have forgotten to call 'done' function or to resolve a Promise") - t.equal(err.code, 'FST_ERR_PLUGIN_TIMEOUT') - t.ok(err.cause) - t.equal(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.assert.ok(err.cause) + t.assert.strictEqual(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + testDone() }) - t.teardown(clock.uninstall) + t.after(clock.uninstall) }) -test('plugin metadata - version', t => { +test('plugin metadata - version', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -78,7 +80,8 @@ test('plugin metadata - version', t => { fastify.register(plugin) fastify.ready(() => { - t.pass('everything right') + t.assert.ok('everything right') + testDone() }) function plugin (instance, opts, done) { @@ -86,7 +89,7 @@ test('plugin metadata - version', t => { } }) -test('plugin metadata - version range', t => { +test('plugin metadata - version range', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -99,7 +102,8 @@ test('plugin metadata - version range', t => { fastify.register(plugin) fastify.ready(() => { - t.pass('everything right') + t.assert.ok('everything right') + testDone() }) function plugin (instance, opts, done) { @@ -107,7 +111,7 @@ test('plugin metadata - version range', t => { } }) -test('plugin metadata - version not matching requirement', t => { +test('plugin metadata - version not matching requirement', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -120,8 +124,9 @@ test('plugin metadata - version not matching requirement', t => { fastify.register(plugin) fastify.ready((err) => { - t.ok(err) - t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + testDone() }) function plugin (instance, opts, done) { @@ -129,7 +134,7 @@ test('plugin metadata - version not matching requirement', t => { } }) -test('plugin metadata - version not matching requirement 2', t => { +test('plugin metadata - version not matching requirement 2', (t, testDone) => { t.plan(2) const fastify = Fastify() Object.defineProperty(fastify, 'version', { @@ -145,8 +150,9 @@ test('plugin metadata - version not matching requirement 2', t => { fastify.register(plugin) fastify.ready((err) => { - t.ok(err) - t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + testDone() }) function plugin (instance, opts, done) { @@ -154,7 +160,7 @@ test('plugin metadata - version not matching requirement 2', t => { } }) -test('plugin metadata - version not matching requirement 3', t => { +test('plugin metadata - version not matching requirement 3', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -167,8 +173,9 @@ test('plugin metadata - version not matching requirement 3', t => { fastify.register(plugin) fastify.ready((err) => { - t.ok(err) - t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + testDone() }) function plugin (instance, opts, done) { @@ -176,7 +183,7 @@ test('plugin metadata - version not matching requirement 3', t => { } }) -test('plugin metadata - release candidate', t => { +test('plugin metadata - release candidate', (t, testDone) => { t.plan(2) const fastify = Fastify() Object.defineProperty(fastify, 'version', { @@ -191,8 +198,9 @@ test('plugin metadata - release candidate', t => { fastify.register(plugin) fastify.ready((err) => { - t.error(err) - t.pass('everything right') + t.assert.ifError(err) + t.assert.ok('everything right') + testDone() }) function plugin (instance, opts, done) { @@ -200,9 +208,9 @@ test('plugin metadata - release candidate', t => { } }) -test('fastify-rc loads prior version plugins', async t => { - t.test('baseline (rc)', t => { - t.plan(2) +describe('fastify-rc loads prior version plugins', async () => { + test('baseline (rc)', (t, testDone) => { + t.plan(1) const fastify = Fastify() Object.defineProperty(fastify, 'version', { @@ -221,8 +229,8 @@ test('fastify-rc loads prior version plugins', async t => { fastify.register(plugin) fastify.ready((err) => { - t.error(err) - t.pass('everything right') + t.assert.ifError(err) + testDone() }) function plugin (instance, opts, done) { @@ -234,8 +242,8 @@ test('fastify-rc loads prior version plugins', async t => { } }) - t.test('pre', t => { - t.plan(2) + test('pre', (t, testDone) => { + t.plan(1) const fastify = Fastify() Object.defineProperty(fastify, 'version', { value: '99.0.0-pre.1' }) @@ -245,15 +253,15 @@ test('fastify-rc loads prior version plugins', async t => { fastify.register(plugin) fastify.ready((err) => { - t.error(err) - t.pass() + t.assert.ifError(err) + testDone() }) function plugin (instance, opts, done) { done() } }) - t.test('alpha', t => { - t.plan(2) + test('alpha', (t, testDone) => { + t.plan(1) const fastify = Fastify() Object.defineProperty(fastify, 'version', { value: '99.0.0-pre.1' }) @@ -263,19 +271,17 @@ test('fastify-rc loads prior version plugins', async t => { fastify.register(plugin) fastify.ready((err) => { - t.error(err) - t.pass() + t.assert.ifError(err) + testDone() }) function plugin (instance, opts, done) { done() } }) }) -test('hasPlugin method exists as a function', t => { - t.plan(1) - +test('hasPlugin method exists as a function', (t) => { const fastify = Fastify() - t.equal(typeof fastify.hasPlugin, 'function') + t.assert.strictEqual(typeof fastify.hasPlugin, 'function') }) test('hasPlugin returns true if the specified plugin has been registered', async t => { @@ -284,60 +290,59 @@ test('hasPlugin returns true if the specified plugin has been registered', async const fastify = Fastify() function pluginA (fastify, opts, done) { - t.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(fastify.hasPlugin('plugin-A')) done() } pluginA[Symbol.for('fastify.display-name')] = 'plugin-A' fastify.register(pluginA) fastify.register(function pluginB (fastify, opts, done) { - t.ok(fastify.hasPlugin('pluginB')) + t.assert.ok(fastify.hasPlugin('pluginB')) done() }) fastify.register(function (fastify, opts, done) { // one line - t.ok(fastify.hasPlugin('function (fastify, opts, done) { -- // one line')) + t.assert.ok(fastify.hasPlugin('function (fastify, opts, done) { -- // one line')) done() }) await fastify.ready() - t.ok(fastify.hasPlugin('fastify')) + t.assert.ok(fastify.hasPlugin('fastify')) }) -test('hasPlugin returns false if the specified plugin has not been registered', t => { - t.plan(1) - +test('hasPlugin returns false if the specified plugin has not been registered', (t) => { const fastify = Fastify() - t.notOk(fastify.hasPlugin('pluginFoo')) + t.assert.ok(!fastify.hasPlugin('pluginFoo')) }) test('hasPlugin returns false when using encapsulation', async t => { t.plan(25) const fastify = Fastify() + t.after(() => fastify.close()) fastify.register(function pluginA (fastify, opts, done) { - t.ok(fastify.hasPlugin('pluginA')) - t.notOk(fastify.hasPlugin('pluginAA')) - t.notOk(fastify.hasPlugin('pluginAAA')) - t.notOk(fastify.hasPlugin('pluginAB')) - t.notOk(fastify.hasPlugin('pluginB')) + t.assert.ok(fastify.hasPlugin('pluginA')) + t.assert.ok(!fastify.hasPlugin('pluginAA')) + t.assert.ok(!fastify.hasPlugin('pluginAAA')) + t.assert.ok(!fastify.hasPlugin('pluginAB')) + t.assert.ok(!fastify.hasPlugin('pluginB')) fastify.register(function pluginAA (fastify, opts, done) { - t.notOk(fastify.hasPlugin('pluginA')) - t.ok(fastify.hasPlugin('pluginAA')) - t.notOk(fastify.hasPlugin('pluginAAA')) - t.notOk(fastify.hasPlugin('pluginAB')) - t.notOk(fastify.hasPlugin('pluginB')) + t.assert.ok(!fastify.hasPlugin('pluginA')) + t.assert.ok(fastify.hasPlugin('pluginAA')) + t.assert.ok(!fastify.hasPlugin('pluginAAA')) + t.assert.ok(!fastify.hasPlugin('pluginAB')) + t.assert.ok(!fastify.hasPlugin('pluginB')) fastify.register(function pluginAAA (fastify, opts, done) { - t.notOk(fastify.hasPlugin('pluginA')) - t.notOk(fastify.hasPlugin('pluginAA')) - t.ok(fastify.hasPlugin('pluginAAA')) - t.notOk(fastify.hasPlugin('pluginAB')) - t.notOk(fastify.hasPlugin('pluginB')) + t.assert.ok(!fastify.hasPlugin('pluginA')) + t.assert.ok(!fastify.hasPlugin('pluginAA')) + t.assert.ok(fastify.hasPlugin('pluginAAA')) + t.assert.ok(!fastify.hasPlugin('pluginAB')) + t.assert.ok(!fastify.hasPlugin('pluginB')) done() }) @@ -346,11 +351,11 @@ test('hasPlugin returns false when using encapsulation', async t => { }) fastify.register(function pluginAB (fastify, opts, done) { - t.notOk(fastify.hasPlugin('pluginA')) - t.notOk(fastify.hasPlugin('pluginAA')) - t.notOk(fastify.hasPlugin('pluginAAA')) - t.ok(fastify.hasPlugin('pluginAB')) - t.notOk(fastify.hasPlugin('pluginB')) + t.assert.ok(!fastify.hasPlugin('pluginA')) + t.assert.ok(!fastify.hasPlugin('pluginAA')) + t.assert.ok(!fastify.hasPlugin('pluginAAA')) + t.assert.ok(fastify.hasPlugin('pluginAB')) + t.assert.ok(!fastify.hasPlugin('pluginB')) done() }) @@ -359,11 +364,11 @@ test('hasPlugin returns false when using encapsulation', async t => { }) fastify.register(function pluginB (fastify, opts, done) { - t.notOk(fastify.hasPlugin('pluginA')) - t.notOk(fastify.hasPlugin('pluginAA')) - t.notOk(fastify.hasPlugin('pluginAAA')) - t.notOk(fastify.hasPlugin('pluginAB')) - t.ok(fastify.hasPlugin('pluginB')) + t.assert.ok(!fastify.hasPlugin('pluginA')) + t.assert.ok(!fastify.hasPlugin('pluginAA')) + t.assert.ok(!fastify.hasPlugin('pluginAAA')) + t.assert.ok(!fastify.hasPlugin('pluginAB')) + t.assert.ok(fastify.hasPlugin('pluginB')) done() }) @@ -377,26 +382,26 @@ test('hasPlugin returns true when using no encapsulation', async t => { const fastify = Fastify() fastify.register(fp((fastify, opts, done) => { - t.equal(fastify.pluginName, 'fastify -> plugin-AA') - t.ok(fastify.hasPlugin('plugin-AA')) - t.notOk(fastify.hasPlugin('plugin-A')) - t.notOk(fastify.hasPlugin('plugin-AAA')) - t.notOk(fastify.hasPlugin('plugin-AB')) - t.notOk(fastify.hasPlugin('plugin-B')) + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-AA') + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(!fastify.hasPlugin('plugin-A')) + t.assert.ok(!fastify.hasPlugin('plugin-AAA')) + t.assert.ok(!fastify.hasPlugin('plugin-AB')) + t.assert.ok(!fastify.hasPlugin('plugin-B')) fastify.register(fp((fastify, opts, done) => { - t.ok(fastify.hasPlugin('plugin-AA')) - t.ok(fastify.hasPlugin('plugin-A')) - t.notOk(fastify.hasPlugin('plugin-AAA')) - t.notOk(fastify.hasPlugin('plugin-AB')) - t.notOk(fastify.hasPlugin('plugin-B')) + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(!fastify.hasPlugin('plugin-AAA')) + t.assert.ok(!fastify.hasPlugin('plugin-AB')) + t.assert.ok(!fastify.hasPlugin('plugin-B')) fastify.register(fp((fastify, opts, done) => { - t.ok(fastify.hasPlugin('plugin-AA')) - t.ok(fastify.hasPlugin('plugin-A')) - t.ok(fastify.hasPlugin('plugin-AAA')) - t.notOk(fastify.hasPlugin('plugin-AB')) - t.notOk(fastify.hasPlugin('plugin-B')) + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(fastify.hasPlugin('plugin-AAA')) + t.assert.ok(!fastify.hasPlugin('plugin-AB')) + t.assert.ok(!fastify.hasPlugin('plugin-B')) done() }, { name: 'plugin-AAA' })) @@ -405,11 +410,11 @@ test('hasPlugin returns true when using no encapsulation', async t => { }, { name: 'plugin-A' })) fastify.register(fp((fastify, opts, done) => { - t.ok(fastify.hasPlugin('plugin-AA')) - t.ok(fastify.hasPlugin('plugin-A')) - t.ok(fastify.hasPlugin('plugin-AAA')) - t.ok(fastify.hasPlugin('plugin-AB')) - t.notOk(fastify.hasPlugin('plugin-B')) + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(fastify.hasPlugin('plugin-AAA')) + t.assert.ok(fastify.hasPlugin('plugin-AB')) + t.assert.ok(!fastify.hasPlugin('plugin-B')) done() }, { name: 'plugin-AB' })) @@ -418,11 +423,11 @@ test('hasPlugin returns true when using no encapsulation', async t => { }, { name: 'plugin-AA' })) fastify.register(fp((fastify, opts, done) => { - t.ok(fastify.hasPlugin('plugin-AA')) - t.ok(fastify.hasPlugin('plugin-A')) - t.ok(fastify.hasPlugin('plugin-AAA')) - t.ok(fastify.hasPlugin('plugin-AB')) - t.ok(fastify.hasPlugin('plugin-B')) + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(fastify.hasPlugin('plugin-AAA')) + t.assert.ok(fastify.hasPlugin('plugin-AB')) + t.assert.ok(fastify.hasPlugin('plugin-B')) done() }, { name: 'plugin-B' })) @@ -444,12 +449,12 @@ test('hasPlugin returns true when using encapsulation', async t => { fastify.register(plugin) fastify.register(async (server) => { - t.ok(server.hasPlugin(pluginName)) + t.assert.ok(server.hasPlugin(pluginName)) }) fastify.register(async function foo (server) { server.register(async function bar (server) { - t.ok(server.hasPlugin(pluginName)) + t.assert.ok(server.hasPlugin(pluginName)) }) }) @@ -471,8 +476,8 @@ test('registering anonymous plugin with mixed style should throw', async t => { await fastify.ready() t.fail('should throw') } catch (error) { - t.type(error, FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) - t.equal(error.message, 'The anonymousPlugin plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + t.assert.ok(error instanceof FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) + t.assert.strictEqual(error.message, 'The anonymousPlugin plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') } }) @@ -493,7 +498,7 @@ test('registering named plugin with mixed style should throw', async t => { await fastify.ready() t.fail('should throw') } catch (error) { - t.type(error, FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) - t.equal(error.message, 'The error-plugin plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + t.assert.ok(error instanceof FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) + t.assert.strictEqual(error.message, 'The error-plugin plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') } }) From c1fdf0e6d8ac98d5e78cc9d3c7848463872b56ab Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 16 May 2025 09:43:37 +0200 Subject: [PATCH 1023/1295] fix(ci): ignore alternative runtime result (#6125) Signed-off-by: Manuel Spigolon --- .github/workflows/ci-alternative-runtime.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index 2160c85a73c..75020a91c15 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -25,6 +25,7 @@ permissions: jobs: test-unit: runs-on: ${{ matrix.os }} + continue-on-error: true permissions: contents: read strategy: @@ -51,7 +52,6 @@ jobs: npm install --ignore-scripts - name: Run tests - continue-on-error: true run: | npm run unit From 121c6166e55f43bb8407f0992d3e3a2fb2e35781 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Fri, 16 May 2025 12:12:10 +0200 Subject: [PATCH 1024/1295] test: mv schema-* from tap (#6093) --- test/schema-serialization.test.js | 331 ++++++++++++---------- test/schema-special-usage.test.js | 297 +++++++++++--------- test/schema-validation.test.js | 447 ++++++++++++++++-------------- 3 files changed, 584 insertions(+), 491 deletions(-) diff --git a/test/schema-serialization.test.js b/test/schema-serialization.test.js index b319be02b06..72b881ffff1 100644 --- a/test/schema-serialization.test.js +++ b/test/schema-serialization.test.js @@ -1,12 +1,12 @@ 'use strict' -const t = require('tap') +const { test } = require('node:test') const Fastify = require('..') -const test = t.test +const { waitForCb } = require('./toolkit') const echoBody = (req, reply) => { reply.send(req.body) } -test('basic test', t => { +test('basic test', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -27,13 +27,14 @@ test('basic test', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.same(res.json(), { name: 'Foo', work: 'Bar' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { name: 'Foo', work: 'Bar' }) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('custom serializer options', t => { +test('custom serializer options', (t, testDone) => { t.plan(3) const fastify = Fastify({ @@ -54,13 +55,14 @@ test('custom serializer options', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.payload, '5', 'it must use the ceil rounding') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '5', 'it must use the ceil rounding') + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Different content types', t => { +test('Different content types', (t, testDone) => { t.plan(46) const fastify = Fastify() @@ -217,7 +219,7 @@ test('Different content types', t => { fastify.get('/test', { serializerCompiler: ({ contentType }) => { - t.equal(contentType, 'application/json') + t.assert.strictEqual(contentType, 'application/json') return data => JSON.stringify(data) }, schema: { @@ -270,92 +272,96 @@ test('Different content types', t => { } }) + const completion = waitForCb({ steps: 14 }) fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ name: 'Foo', image: 'profile picture', address: 'New Node' })) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ name: 'Foo', image: 'profile picture', address: 'New Node' })) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v1+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify([{ name: 'Boo', age: 18, verified: false }, { name: 'Woo', age: 30, verified: true }])) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify([{ name: 'Boo', age: 18, verified: false }, { name: 'Woo', age: 30, verified: true }])) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify([{ type: 'student', grade: 6 }, { type: 'student', grade: 9 }])) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify([{ type: 'student', grade: 6 }, { type: 'student', grade: 9 }])) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v2+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ fullName: 'Jhon Smith', phone: '01090000000' })) - t.equal(res.statusCode, 300) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ fullName: 'Jhon Smith', phone: '01090000000' })) + t.assert.strictEqual(res.statusCode, 300) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v3+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ firstName: 'New', lastName: 'Hoo', country: 'eg', city: 'node' })) - t.equal(res.statusCode, 300) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ firstName: 'New', lastName: 'Hoo', country: 'eg', city: 'node' })) + t.assert.strictEqual(res.statusCode, 300) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v4+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, '"[object Object]"') - t.equal(res.statusCode, 201) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '"[object Object]"') + t.assert.strictEqual(res.statusCode, 201) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v5+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, '"Processing exclusive content"') - t.equal(res.statusCode, 202) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '"Processing exclusive content"') + t.assert.strictEqual(res.statusCode, 202) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v6+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ details: 'validation error' })) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v7+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ details: 'validation error' })) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v8+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ desc: 'age is missing', details: 'validation error' })) - t.equal(res.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ desc: 'age is missing', details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 500) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v9+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ details: 'validation error' })) - t.equal(res.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 500) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/test', headers: { Code: '200' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ age: 18, city: 'AU' })) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ age: 18, city: 'AU' })) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/test', headers: { Code: '201' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ details: 'validation error' })) - t.equal(res.statusCode, 201) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 201) + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/test', headers: { Accept: 'application/vnd.v1+json' } }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ created: true })) - t.equal(res.statusCode, 201) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ created: true })) + t.assert.strictEqual(res.statusCode, 201) + completion.stepIn() }) + + completion.patience.then(testDone) }) -test('Invalid multiple content schema, throw FST_ERR_SCH_CONTENT_MISSING_SCHEMA error', t => { +test('Invalid multiple content schema, throw FST_ERR_SCH_CONTENT_MISSING_SCHEMA error', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -388,13 +394,14 @@ test('Invalid multiple content schema, throw FST_ERR_SCH_CONTENT_MISSING_SCHEMA }) fastify.ready((err) => { - t.equal(err.message, "Schema is missing for the content type 'type'") - t.equal(err.statusCode, 500) - t.equal(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + t.assert.strictEqual(err.message, "Schema is missing for the content type 'type'") + t.assert.strictEqual(err.statusCode, 500) + t.assert.strictEqual(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + testDone() }) }) -test('Use the same schema id in different places', t => { +test('Use the same schema id in different places', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -424,12 +431,13 @@ test('Use the same schema id in different places', t => { method: 'GET', url: '/123' }, (err, res) => { - t.error(err) - t.same(res.json(), [{ id: 1 }, { id: 2 }, {}]) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), [{ id: 1 }, { id: 2 }, {}]) + testDone() }) }) -test('Use shared schema and $ref with $id in response ($ref to $id)', t => { +test('Use shared schema and $ref with $id in response ($ref to $id)', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -479,32 +487,36 @@ test('Use shared schema and $ref with $id in response ($ref to $id)', t => { test: { id: Date.now() } } + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/', payload }, (err, res) => { - t.error(err) - t.same(res.json(), payload) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), payload) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { test: { id: Date.now() } } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { error: 'Bad Request', message: "body must have required property 'address'", statusCode: 400, code: 'FST_ERR_VALIDATION' }) + completion.stepIn() }) + + completion.patience.then(testDone) }) -test('Shared schema should be pass to serializer and validator ($ref to shared schema /definitions)', t => { +test('Shared schema should be pass to serializer and validator ($ref to shared schema /definitions)', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -592,8 +604,8 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s url: '/', payload: locations }, (err, res) => { - t.error(err) - t.same(res.json(), locations) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), locations) fastify.inject({ method: 'POST', @@ -603,19 +615,20 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s return _ }) }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { error: 'Bad Request', message: 'body/0/location/email must match format "email"', statusCode: 400, code: 'FST_ERR_VALIDATION' }) + testDone() }) }) }) -test('Custom setSerializerCompiler', t => { +test('Custom setSerializerCompiler', (t, testDone) => { t.plan(7) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -626,10 +639,10 @@ test('Custom setSerializerCompiler', t => { } fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { - t.equal(method, 'GET') - t.equal(url, '/foo/:id') - t.equal(httpStatus, '200') - t.same(schema, outSchema) + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(url, '/foo/:id') + t.assert.strictEqual(httpStatus, '200') + t.assert.deepStrictEqual(schema, outSchema) return data => JSON.stringify(data) }) @@ -644,7 +657,7 @@ test('Custom setSerializerCompiler', t => { } } }) - t.ok(instance.serializerCompiler, 'the serializer is set by the parent') + t.assert.ok(instance.serializerCompiler, 'the serializer is set by the parent') done() }, { prefix: '/foo' }) @@ -652,12 +665,13 @@ test('Custom setSerializerCompiler', t => { method: 'GET', url: '/foo/123' }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ id: 1 })) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ id: 1 })) + testDone() }) }) -test('Custom setSerializerCompiler returns bad serialized output', t => { +test('Custom setSerializerCompiler returns bad serialized output', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -669,7 +683,7 @@ test('Custom setSerializerCompiler returns bad serialized output', t => { fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { return data => { - t.pass('returning an invalid serialization') + t.assert.ok('returning an invalid serialization') return { not: 'a string' } } }) @@ -687,17 +701,18 @@ test('Custom setSerializerCompiler returns bad serialized output', t => { method: 'GET', url: '/123' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.strictSame(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE', message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.', statusCode: 500 }) + testDone() }) }) -test('Custom setSerializerCompiler with addSchema', t => { +test('Custom setSerializerCompiler with addSchema', (t, testDone) => { t.plan(6) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -708,10 +723,10 @@ test('Custom setSerializerCompiler with addSchema', t => { } fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { - t.equal(method, 'GET') - t.equal(url, '/foo/:id') - t.equal(httpStatus, '200') - t.same(schema, outSchema) + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(url, '/foo/:id') + t.assert.strictEqual(httpStatus, '200') + t.assert.deepStrictEqual(schema, outSchema) return _data => JSON.stringify({ id: 2 }) }) @@ -733,8 +748,9 @@ test('Custom setSerializerCompiler with addSchema', t => { method: 'GET', url: '/foo/123' }, (err, res) => { - t.error(err) - t.equal(res.payload, JSON.stringify({ id: 2 })) + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ id: 2 })) + testDone() }) }) @@ -777,23 +793,23 @@ test('Custom serializer per route', async t => { }) let res = await fastify.inject('/default') - t.equal(res.json().mean, 'default') + t.assert.strictEqual(res.json().mean, 'default') res = await fastify.inject('/custom') - t.equal(res.json().mean, 'custom') + t.assert.strictEqual(res.json().mean, 'custom') res = await fastify.inject('/route') - t.equal(res.json().mean, 'route') + t.assert.strictEqual(res.json().mean, 'route') - t.equal(hit, 4, 'the custom and route serializer has been called') + t.assert.strictEqual(hit, 4, 'the custom and route serializer has been called') }) -test('Reply serializer win over serializer ', t => { +test('Reply serializer win over serializer ', (t, testDone) => { t.plan(6) const fastify = Fastify() fastify.setReplySerializer(function (payload, statusCode) { - t.same(payload, { name: 'Foo', work: 'Bar', nick: 'Boo' }) + t.assert.deepStrictEqual(payload, { name: 'Foo', work: 'Bar', nick: 'Boo' }) return 'instance serializator' }) @@ -810,9 +826,9 @@ test('Reply serializer win over serializer ', t => { } }, serializerCompiler: ({ schema, method, url, httpPart }) => { - t.ok(method, 'the custom compiler has been created') + t.assert.ok(method, 'the custom compiler has been created') return () => { - t.fail('the serializer must not be called when there is a reply serializer') + t.assert.fail('the serializer must not be called when there is a reply serializer') return 'fail' } } @@ -821,18 +837,19 @@ test('Reply serializer win over serializer ', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.same(res.payload, 'instance serializator') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'instance serializator') + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Reply serializer win over serializer ', t => { +test('Reply serializer win over serializer ', (t, testDone) => { t.plan(6) const fastify = Fastify() fastify.setReplySerializer(function (payload, statusCode) { - t.same(payload, { name: 'Foo', work: 'Bar', nick: 'Boo' }) + t.assert.deepStrictEqual(payload, { name: 'Foo', work: 'Bar', nick: 'Boo' }) return 'instance serializator' }) @@ -849,9 +866,9 @@ test('Reply serializer win over serializer ', t => { } }, serializerCompiler: ({ schema, method, url, httpPart }) => { - t.ok(method, 'the custom compiler has been created') + t.assert.ok(method, 'the custom compiler has been created') return () => { - t.fail('the serializer must not be called when there is a reply serializer') + t.assert.fail('the serializer must not be called when there is a reply serializer') return 'fail' } } @@ -860,13 +877,14 @@ test('Reply serializer win over serializer ', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.same(res.payload, 'instance serializator') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'instance serializator') + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('The schema compiler recreate itself if needed', t => { +test('The schema compiler recreate itself if needed', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -896,7 +914,10 @@ test('The schema compiler recreate itself if needed', t => { done() }) - fastify.ready(err => { t.error(err) }) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) test('The schema changes the default error handler output', async t => { @@ -933,12 +954,12 @@ test('The schema changes the default error handler output', async t => { }) let res = await fastify.inject('/501') - t.equal(res.statusCode, 501) - t.same(res.json(), { message: '501 message' }) + t.assert.strictEqual(res.statusCode, 501) + t.assert.deepStrictEqual(res.json(), { message: '501 message' }) res = await fastify.inject('/500') - t.equal(res.statusCode, 500) - t.same(res.json(), { error: 'Internal Server Error', message: '500 message', customId: 42 }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { error: 'Internal Server Error', message: '500 message', customId: 42 }) }) test('do not crash if status code serializer errors', async t => { @@ -967,7 +988,7 @@ test('do not crash if status code serializer errors', async t => { } }, (request, reply) => { - t.fail('handler, should not be called') + t.assert.fail('handler, should not be called') } ) @@ -977,8 +998,8 @@ test('do not crash if status code serializer errors', async t => { notfoo: true } }) - t.equal(res.statusCode, 500) - t.same(res.json(), { + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { statusCode: 500, code: 'FST_ERR_FAILED_ERROR_SERIALIZATION', message: 'Failed to serialize an error. Error: "customCode" is required!. ' + @@ -1009,11 +1030,11 @@ test('custom schema serializer error, empty message', async t => { }) const res = await fastify.inject('/501') - t.equal(res.statusCode, 501) - t.same(res.json(), { message: '' }) + t.assert.strictEqual(res.statusCode, 501) + t.assert.deepStrictEqual(res.json(), { message: '' }) }) -test('error in custom schema serialize compiler, throw FST_ERR_SCH_SERIALIZATION_BUILD error', t => { +test('error in custom schema serialize compiler, throw FST_ERR_SCH_SERIALIZATION_BUILD error', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -1043,9 +1064,10 @@ test('error in custom schema serialize compiler, throw FST_ERR_SCH_SERIALIZATION }) fastify.ready((err) => { - t.equal(err.message, 'Failed building the serialization schema for GET: /, due to error CUSTOM_ERROR') - t.equal(err.statusCode, 500) - t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + t.assert.strictEqual(err.message, 'Failed building the serialization schema for GET: /, due to error CUSTOM_ERROR') + t.assert.strictEqual(err.statusCode, 500) + t.assert.strictEqual(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + testDone() }) }) @@ -1077,20 +1099,19 @@ test('Errors in serializer send to errorHandler', async t => { const res = await fastify.inject('/') - t.equal(res.statusCode, 500) + t.assert.strictEqual(res.statusCode, 500) - // t.same(savedError, new Error('"name" is required!')); - t.same(res.json(), { + // t.assert.deepStrictEqual(savedError, new Error('"name" is required!')); + t.assert.deepStrictEqual(res.json(), { statusCode: 500, error: 'Internal Server Error', message: '"name" is required!' }) - t.ok(savedError, 'error presents') - t.ok(savedError.serialization, 'Serialization sign presents') - t.end() + t.assert.ok(savedError, 'error presents') + t.assert.ok(savedError.serialization, 'Serialization sign presents') }) -test('capital X', t => { +test('capital X', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -1111,13 +1132,14 @@ test('capital X', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.same(res.json(), { name: 'Foo', work: 'Bar' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { name: 'Foo', work: 'Bar' }) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('allow default as status code and used as last fallback', t => { +test('allow default as status code and used as last fallback', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -1141,8 +1163,9 @@ test('allow default as status code and used as last fallback', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.same(res.json(), { name: 'Foo', work: 'Bar' }) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { name: 'Foo', work: 'Bar' }) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) diff --git a/test/schema-special-usage.test.js b/test/schema-special-usage.test.js index b3f341ede17..1d1cd627022 100644 --- a/test/schema-special-usage.test.js +++ b/test/schema-special-usage.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Joi = require('joi') const yup = require('yup') const AJV = require('ajv') @@ -8,8 +8,10 @@ const S = require('fluent-json-schema') const Fastify = require('..') const ajvMergePatch = require('ajv-merge-patch') const ajvErrors = require('ajv-errors') +const proxyquire = require('proxyquire') +const { waitForCb } = require('./toolkit') -test('Ajv plugins array parameter', t => { +test('Ajv plugins array parameter', (t, testDone) => { t.plan(3) const fastify = Fastify({ ajv: { @@ -50,13 +52,14 @@ test('Ajv plugins array parameter', t => { url: '/', payload: { foo: 99 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.json().message, 'body/foo should be <= 10@@@@should be multipleOf 2') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.json().message, 'body/foo should be <= 10@@@@should be multipleOf 2') + testDone() }) }) -test('Should handle root $merge keywords in header', t => { +test('Should handle root $merge keywords in header', (t, testDone) => { t.plan(5) const fastify = Fastify({ ajv: { @@ -86,14 +89,14 @@ test('Should handle root $merge keywords in header', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) }) fastify.inject({ @@ -101,13 +104,14 @@ test('Should handle root $merge keywords in header', t => { url: '/', headers: { q: 'foo' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) }) -test('Should handle root $patch keywords in header', t => { +test('Should handle root $patch keywords in header', (t, testDone) => { t.plan(5) const fastify = Fastify({ ajv: { @@ -143,7 +147,7 @@ test('Should handle root $patch keywords in header', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'GET', @@ -152,8 +156,8 @@ test('Should handle root $patch keywords in header', t => { q: 'foo' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) }) fastify.inject({ @@ -161,13 +165,14 @@ test('Should handle root $patch keywords in header', t => { url: '/', headers: { q: 10 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) }) -test('Should handle $merge keywords in body', t => { +test('Should handle $merge keywords in body', (t, testDone) => { t.plan(5) const fastify = Fastify({ ajv: { @@ -197,14 +202,14 @@ test('Should handle $merge keywords in body', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) fastify.inject({ method: 'POST', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) }) fastify.inject({ @@ -212,13 +217,14 @@ test('Should handle $merge keywords in body', t => { url: '/', payload: { q: 'foo' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) }) -test('Should handle $patch keywords in body', t => { +test('Should handle $patch keywords in body', (t, testDone) => { t.plan(5) const fastify = Fastify({ ajv: { @@ -252,29 +258,32 @@ test('Should handle $patch keywords in body', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/', payload: { q: 'foo' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { q: 10 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test("serializer read validator's schemas", t => { +test("serializer read validator's schemas", (t, testDone) => { t.plan(4) const ajvInstance = new AJV() @@ -303,7 +312,7 @@ test("serializer read validator's schemas", t => { const fastify = Fastify({ schemaController: { bucket: function factory (storeInit) { - t.notOk(storeInit, 'is always empty because fastify.addSchema is not called') + t.assert.ok(!storeInit, 'is always empty because fastify.addSchema is not called') return { getSchemas () { return { @@ -330,13 +339,14 @@ test("serializer read validator's schemas", t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() }) }) -test('setSchemaController in a plugin', t => { +test('setSchemaController in a plugin', (t, testDone) => { t.plan(5) const baseSchema = { $id: 'urn:schema:base', @@ -376,15 +386,16 @@ test('setSchemaController in a plugin', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() }) async function schemaPlugin (server) { server.setSchemaController({ bucket () { - t.pass('the bucket is created') + t.assert.ok('the bucket is created') return { addSchema (source) { ajvInstance.addSchema(source) @@ -402,7 +413,7 @@ test('setSchemaController in a plugin', t => { } }) server.setValidatorCompiler(function ({ schema }) { - t.pass('the querystring schema is compiled') + t.assert.ok('the querystring schema is compiled') return ajvInstance.compile(schema) }) } @@ -506,7 +517,7 @@ test('only response schema trigger AJV pollution #2', async t => { await fastify.ready() }) -test('setSchemaController in a plugin with head routes', t => { +test('setSchemaController in a plugin with head routes', (t, testDone) => { t.plan(6) const baseSchema = { $id: 'urn:schema:base', @@ -546,15 +557,16 @@ test('setSchemaController in a plugin with head routes', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() }) async function schemaPlugin (server) { server.setSchemaController({ bucket () { - t.pass('the bucket is created') + t.assert.ok('the bucket is created') return { addSchema (source) { ajvInstance.addSchema(source) @@ -575,11 +587,11 @@ test('setSchemaController in a plugin with head routes', t => { if (schema.$id) { const stored = ajvInstance.getSchema(schema.$id) if (stored) { - t.pass('the schema is reused') + t.assert.ok('the schema is reused') return stored } } - t.pass('the schema is compiled') + t.assert.ok('the schema is compiled') return ajvInstance.compile(schema) }) @@ -587,7 +599,7 @@ test('setSchemaController in a plugin with head routes', t => { schemaPlugin[Symbol.for('skip-override')] = true }) -test('multiple refs with the same ids', t => { +test('multiple refs with the same ids', (t, testDone) => { t.plan(3) const baseSchema = { $id: 'urn:schema:base', @@ -638,13 +650,14 @@ test('multiple refs with the same ids', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() }) }) -test('JOI validation overwrite request headers', t => { +test('JOI validation overwrite request headers', (t, testDone) => { t.plan(3) const schemaValidator = ({ schema }) => data => { const validationResult = schema.validate(data) @@ -666,12 +679,13 @@ test('JOI validation overwrite request headers', t => { }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { 'user-agent': 'lightMyRequest', host: 'localhost:80' }) + testDone() }) }) @@ -700,16 +714,16 @@ test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t => }) await fastify.ready() - t.pass('fastify is ready') + t.assert.ok('fastify is ready') }) test('The default schema compilers should not be called when overwritten by the user', async t => { - const Fastify = t.mockRequire('../', { + const Fastify = proxyquire('../', { '@fastify/ajv-compiler': () => { - t.fail('The default validator compiler should not be called') + t.assert.fail('The default validator compiler should not be called') }, '@fastify/fast-json-stringify-compiler': () => { - t.fail('The default serializer compiler should not be called') + t.assert.fail('The default serializer compiler should not be called') } }) @@ -717,13 +731,13 @@ test('The default schema compilers should not be called when overwritten by the schemaController: { compilersFactory: { buildValidator: function factory () { - t.pass('The custom validator compiler should be called') + t.assert.ok('The custom validator compiler should be called') return function validatorCompiler () { return () => { return true } } }, buildSerializer: function factory () { - t.pass('The custom serializer compiler should be called') + t.assert.ok('The custom serializer compiler should be called') return function serializerCompiler () { return () => { return true } } @@ -745,7 +759,7 @@ test('The default schema compilers should not be called when overwritten by the await fastify.ready() }) -test('Supports async JOI validation', t => { +test('Supports async JOI validation', (t, testDone) => { t.plan(7) const schemaValidator = ({ schema }) => async data => { @@ -766,7 +780,7 @@ test('Supports async JOI validation', t => { throw new Error('Invalid user-agent') } - t.equal(val, 'lightMyRequest') + t.assert.strictEqual(val, 'lightMyRequest') return val }), host: Joi.string().required() @@ -776,33 +790,37 @@ test('Supports async JOI validation', t => { reply.send(request.headers) }) + const completion = waitForCb({ steps: 2 }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { 'user-agent': 'lightMyRequest', host: 'localhost:80' }) + completion.stepIn() }) - fastify.inject({ url: '/', headers: { 'user-agent': 'invalid' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'Invalid user-agent (user-agent)' }) + completion.stepIn() }) + + completion.patience.then(testDone) }) -test('Supports async AJV validation', t => { +test('Supports async AJV validation', (t, testDone) => { t.plan(12) const fastify = Fastify({ @@ -861,63 +879,67 @@ test('Supports async AJV validation', t => { handler (req, reply) { reply.send(req.body) } }) + const completion = waitForCb({ steps: 4 }) + fastify.inject({ method: 'POST', url: '/', payload: { userId: 99 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'validation failed' }) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { userId: 500 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'custom error' }) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { userId: 42 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { userId: 42 }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { userId: 42 }) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { userId: 42, postId: 19 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'validation failed' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Check all the async AJV validation paths', t => { +test('Check all the async AJV validation paths', async (t) => { const fastify = Fastify({ exposeHeadRoutes: false, ajv: { @@ -1004,30 +1026,34 @@ test('Check all the async AJV validation paths', t => { response: 200 } ] - t.plan(testCases.length * 2) - testCases.forEach(validate) + t.plan(testCases.length) + for (const testCase of testCases) { + await validate(testCase) + } - function validate ({ + async function validate ({ params, body, querystring, headers, response }) { - fastify.inject({ - method: 'POST', - url: `/${params}`, - headers: { id: headers }, - query: { id: querystring }, - payload: { id: body } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, response) - }) + try { + const res = await fastify.inject({ + method: 'POST', + url: `/${params}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }) + t.assert.strictEqual(res.statusCode, response) + } catch (error) { + t.assert.fail('should not throw') + } } }) -test('Check mixed sync and async AJV validations', t => { +test('Check mixed sync and async AJV validations', async (t) => { const fastify = Fastify({ exposeHeadRoutes: false, ajv: { @@ -1179,10 +1205,12 @@ test('Check mixed sync and async AJV validations', t => { response: 400 } ] - t.plan(testCases.length * 2) - testCases.forEach(validate) + t.plan(testCases.length) + for (const testCase of testCases) { + await validate(testCase) + } - function validate ({ + async function validate ({ url, params, body, @@ -1190,20 +1218,22 @@ test('Check mixed sync and async AJV validations', t => { headers, response }) { - fastify.inject({ - method: 'POST', - url: `${url}/${params || ''}`, - headers: { id: headers }, - query: { id: querystring }, - payload: { id: body } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, response) - }) + try { + const res = await fastify.inject({ + method: 'POST', + url: `${url}/${params || ''}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }) + t.assert.strictEqual(res.statusCode, response) + } catch (error) { + t.assert.fail('should not fail') + } } }) -test('Check if hooks and attachValidation work with AJV validations', t => { +test('Check if hooks and attachValidation work with AJV validations', async (t) => { const fastify = Fastify({ exposeHeadRoutes: false, ajv: { @@ -1245,8 +1275,8 @@ test('Check if hooks and attachValidation work with AJV validations', t => { fastify.post('/:id', { preHandler: function hook (request, reply, done) { - t.equal(request.validationError.message, 'validation failed') - t.pass('preHandler called') + t.assert.strictEqual(request.validationError.message, 'validation failed') + t.assert.ok('preHandler called') reply.code(400).send(request.body) }, @@ -1290,26 +1320,29 @@ test('Check if hooks and attachValidation work with AJV validations', t => { response: 400 } ] - t.plan(testCases.length * 4) - testCases.forEach(validate) + t.plan(testCases.length * 3) + for (const testCase of testCases) { + await validate(testCase) + } - function validate ({ - url, + async function validate ({ params, body, querystring, headers, response }) { - fastify.inject({ - method: 'POST', - url: `/${params}`, - headers: { id: headers }, - query: { id: querystring }, - payload: { id: body } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, response) - }) + try { + const res = await fastify.inject({ + method: 'POST', + url: `/${params}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }) + t.assert.strictEqual(res.statusCode, response) + } catch (error) { + t.assert.fail('should not fail') + } } }) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index cb36ba774a0..ef8cd7fcd2f 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -1,11 +1,12 @@ 'use strict' -const { test } = require('tap') +const { test } = require('node:test') const Fastify = require('..') const { request } = require('undici') const AJV = require('ajv') const Schema = require('fluent-json-schema') +const { waitForCb } = require('./toolkit') const customSchemaCompilers = { body: new AJV({ @@ -70,7 +71,7 @@ const schemaArtist = { required: ['name', 'work'] } -test('Basic validation test', t => { +test('Basic validation test', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -82,6 +83,7 @@ test('Basic validation test', t => { reply.code(200).send(req.body.name) }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', payload: { @@ -90,23 +92,25 @@ test('Basic validation test', t => { }, url: '/' }, (err, res) => { - t.error(err) - t.same(res.payload, 'michelangelo') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'michelangelo') + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) - fastify.inject({ method: 'POST', payload: { name: 'michelangelo' }, url: '/' }, (err, res) => { - t.error(err) - t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Different schema per content type', t => { +test('Different schema per content type', (t, testDone) => { t.plan(12) const fastify = Fastify() @@ -135,6 +139,7 @@ test('Different schema per content type', t => { return reply.send(req.body) }) + const completion = waitForCb({ steps: 4 }) fastify.inject({ url: '/', method: 'POST', @@ -144,46 +149,48 @@ test('Different schema per content type', t => { work: 'sculptor, painter, architect and poet' } }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload).name, 'michelangelo') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload).name, 'michelangelo') + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) - fastify.inject({ url: '/', method: 'POST', headers: { 'Content-Type': 'application/json' }, body: { name: 'michelangelo' } }, (err, res) => { - t.error(err) - t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) - fastify.inject({ url: '/', method: 'POST', headers: { 'Content-Type': 'application/octet-stream' }, body: Buffer.from('AAAAAAAA') }, (err, res) => { - t.error(err) - t.same(res.payload, 'AAAAAAAA') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'AAAAAAAA') + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) - fastify.inject({ url: '/', method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: 'AAAAAAAA' }, (err, res) => { - t.error(err) - t.same(res.payload, 'AAAAAAAA') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'AAAAAAAA') + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Skip validation if no schema for content type', t => { +test('Skip validation if no schema for content type', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -201,20 +208,20 @@ test('Skip validation if no schema for content type', t => { }, async function (req, reply) { return reply.send(req.body) }) - fastify.inject({ url: '/', method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: 'AAAAAAAA' }, (err, res) => { - t.error(err) - t.same(res.payload, 'AAAAAAAA') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'AAAAAAAA') + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Skip validation if no content type schemas', t => { +test('Skip validation if no content type schemas', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -229,20 +236,20 @@ test('Skip validation if no content type schemas', t => { }, async function (req, reply) { return reply.send(req.body) }) - fastify.inject({ url: '/', method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: 'AAAAAAAA' }, (err, res) => { - t.error(err) - t.same(res.payload, 'AAAAAAAA') - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'AAAAAAAA') + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('External AJV instance', t => { +test('External AJV instance', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -255,7 +262,7 @@ test('External AJV instance', t => { fastify.addSchema(schemaBRefToA) fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { - t.pass('custom validator compiler called') + t.assert.ok('custom validator compiler called') return ajv.compile(schema) }) @@ -269,26 +276,29 @@ test('External AJV instance', t => { } }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/', payload: { foo: 42 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { foo: 'not a number' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Encapsulation', t => { +test('Encapsulation', (t, testDone) => { t.plan(21) const fastify = Fastify() @@ -302,7 +312,7 @@ test('Encapsulation', t => { fastify.register((instance, opts, done) => { const validator = ({ schema, method, url, httpPart }) => { - t.pass('custom validator compiler called') + t.assert.ok('custom validator compiler called') return ajv.compile(schema) } instance.setValidatorCompiler(validator) @@ -316,7 +326,7 @@ test('Encapsulation', t => { instance.register((instance, opts, done) => { instance.post('/two', { handler (req, reply) { - t.same(instance.validatorCompiler, validator) + t.assert.deepStrictEqual(instance.validatorCompiler, validator) reply.send({ foo: 'two' }) }, schema: { @@ -330,7 +340,7 @@ test('Encapsulation', t => { instance.post('/three', { validatorCompiler: anotherValidator, handler (req, reply) { - t.same(instance.validatorCompiler, validator, 'the route validator does not change the instance one') + t.assert.deepStrictEqual(instance.validatorCompiler, validator, 'the route validator does not change the instance one') reply.send({ foo: 'three' }) }, schema: { @@ -344,72 +354,75 @@ test('Encapsulation', t => { fastify.register((instance, opts, done) => { instance.post('/clean', function (req, reply) { - t.equal(instance.validatorCompiler, undefined) + t.assert.strictEqual(instance.validatorCompiler, undefined) reply.send({ foo: 'bar' }) }) done() }) + const completion = waitForCb({ steps: 6 }) fastify.inject({ method: 'POST', url: '/one', payload: { foo: 1 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { foo: 'one' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 'one' }) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/one', payload: { wrongFoo: 'bar' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/two', payload: { foo: 2 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { foo: 'two' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 'two' }) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/two', payload: { wrongFoo: 'bar' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/three', payload: { wrongFoo: 'but works' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { foo: 'three' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 'three' }) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/clean', payload: { wrongFoo: 'bar' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { foo: 'bar' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 'bar' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Triple $ref with a simple $id', t => { +test('Triple $ref with a simple $id', (t, testDone) => { t.plan(7) const fastify = Fastify() @@ -424,7 +437,7 @@ test('Triple $ref with a simple $id', t => { fastify.addSchema(schemaCRefToB) fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { - t.pass('custom validator compiler called') + t.assert.ok('custom validator compiler called') return ajv.compile(schema) }) @@ -438,28 +451,31 @@ test('Triple $ref with a simple $id', t => { } }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/', payload: { foo: 43 } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { foo: 105 }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 105 }) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { fool: 'bar' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json().message, "body must have required property 'foo'") + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json().message, "body must have required property 'foo'") + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Extending schema', t => { +test('Extending schema', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -499,7 +515,6 @@ test('Extending schema', t => { } } }) - fastify.inject({ method: 'POST', url: '/', @@ -510,10 +525,9 @@ test('Extending schema', t => { } } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) }) - fastify.inject({ method: 'POST', url: '/', @@ -525,12 +539,13 @@ test('Extending schema', t => { } } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Should work with nested ids', t => { +test('Should work with nested ids', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -560,6 +575,7 @@ test('Should work with nested ids', t => { } }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/123', @@ -567,11 +583,11 @@ test('Should work with nested ids', t => { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'number') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'number') + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/abc', @@ -579,14 +595,16 @@ test('Should work with nested ids', t => { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.equal(res.json().message, 'params/id must be number') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.json().message, 'params/id must be number') + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Use the same schema across multiple routes', t => { - t.plan(8) +test('Use the same schema across multiple routes', async (t) => { + t.plan(4) const fastify = Fastify() fastify.addSchema({ @@ -611,34 +629,35 @@ test('Use the same schema across multiple routes', t => { } }) - ;[ + const validTestCases = [ '/first/123', '/second/123' - ].forEach(url => { - fastify.inject({ + ] + + for (const url of validTestCases) { + const res = await fastify.inject({ url, method: 'GET' - }, (err, res) => { - t.error(err) - t.equal(res.payload, 'number') }) - }) - ;[ + t.assert.strictEqual(res.payload, 'number') + } + + const invalidTestCases = [ '/first/abc', '/second/abc' - ].forEach(url => { - fastify.inject({ + ] + + for (const url of invalidTestCases) { + const res = await fastify.inject({ url, method: 'GET' - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) }) - }) + t.assert.strictEqual(res.statusCode, 400) + } }) -test('JSON Schema validation keywords', t => { +test('JSON Schema validation keywords', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -660,31 +679,34 @@ test('JSON Schema validation keywords', t => { } }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'GET', url: '/127.0.0.1' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'string') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'string') + completion.stepIn() }) - fastify.inject({ method: 'GET', url: '/localhost' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'params/ip must match format "ipv4"' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Nested id calls', t => { +test('Nested id calls', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -714,33 +736,36 @@ test('Nested id calls', t => { } }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/', payload: { host: { ip: '127.0.0.1' } } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'string') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'string') + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { host: { ip: 'localhost' } } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { error: 'Bad Request', message: 'body/host/ip must match format "ipv4"', statusCode: 400, code: 'FST_ERR_VALIDATION' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Use the same schema id in different places', t => { +test('Use the same schema id in different places', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -761,18 +786,18 @@ test('Use the same schema id in different places', t => { } } }) - fastify.inject({ method: 'POST', url: '/', payload: { id: 42 } }, (err, res) => { - t.error(err) - t.same(res.json(), { id: 21 }) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { id: 21 }) + testDone() }) }) -test('Use shared schema and $ref with $id ($ref to $id)', t => { +test('Use shared schema and $ref with $id ($ref to $id)', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -815,6 +840,7 @@ test('Use shared schema and $ref with $id ($ref to $id)', t => { }) const id = Date.now() + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/', @@ -823,27 +849,29 @@ test('Use shared schema and $ref with $id ($ref to $id)', t => { test: { id } } }, (err, res) => { - t.error(err) - t.same(res.json(), { id }) + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { id }) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { test: { id } } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { error: 'Bad Request', message: "body must have required property 'address'", statusCode: 400, code: 'FST_ERR_VALIDATION' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Use items with $ref', t => { +test('Use items with $ref', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -865,26 +893,29 @@ test('Use items with $ref', t => { handler: (_, r) => { r.send('ok') } }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/', payload: [{ hello: 'world' }] }, (err, res) => { - t.error(err) - t.equal(res.payload, 'ok') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'ok') + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Use $ref to /definitions', t => { +test('Use $ref to /definitions', (t, testDone) => { t.plan(6) const fastify = Fastify() @@ -931,16 +962,17 @@ test('Use $ref to /definitions', t => { address: { city: 'New Node' }, test: { id: Date.now() } } + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/', payload }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), payload) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), payload) + completion.stepIn() }) - fastify.inject({ method: 'POST', url: '/', @@ -949,18 +981,20 @@ test('Use $ref to /definitions', t => { test: { id: 'wrong' } } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { error: 'Bad Request', message: 'body/test/id must be number', statusCode: 400, code: 'FST_ERR_VALIDATION' }) + completion.stepIn() }) + completion.patience.then(testDone) }) -test('Custom AJV settings - pt1', t => { +test('Custom AJV settings - pt1', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -974,11 +1008,10 @@ test('Custom AJV settings - pt1', t => { } }, handler: (req, reply) => { - t.equal(req.body.num, 12) + t.assert.strictEqual(req.body.num, 12) reply.send(req.body) } }) - fastify.inject({ method: 'POST', url: '/', @@ -986,13 +1019,14 @@ test('Custom AJV settings - pt1', t => { num: '12' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.json(), { num: 12 }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { num: 12 }) + testDone() }) }) -test('Custom AJV settings - pt2', t => { +test('Custom AJV settings - pt2', (t, testDone) => { t.plan(2) const fastify = Fastify({ ajv: { @@ -1015,7 +1049,6 @@ test('Custom AJV settings - pt2', t => { t.fail('the handler is not called because the "12" is not coerced to number') } }) - fastify.inject({ method: 'POST', url: '/', @@ -1023,12 +1056,13 @@ test('Custom AJV settings - pt2', t => { num: '12' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + testDone() }) }) -test('Custom AJV settings on different parameters - pt1', t => { +test('Custom AJV settings on different parameters - pt1', (t, testDone) => { t.plan(2) const fastify = Fastify() @@ -1054,7 +1088,6 @@ test('Custom AJV settings on different parameters - pt1', t => { t.fail('the handler is not called because the "12" is not coerced to number') } }) - fastify.inject({ method: 'POST', url: '/api/42', @@ -1062,12 +1095,13 @@ test('Custom AJV settings on different parameters - pt1', t => { num: '12' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + testDone() }) }) -test('Custom AJV settings on different parameters - pt2', t => { +test('Custom AJV settings on different parameters - pt2', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -1091,13 +1125,13 @@ test('Custom AJV settings on different parameters - pt2', t => { } }, handler: (req, reply) => { - t.same(typeof req.params.id, 'number') - t.same(typeof req.body.num, 'number') - t.same(req.params.id, 42) - t.same(req.body.num, 12) + t.assert.deepStrictEqual(typeof req.params.id, 'number') + t.assert.deepStrictEqual(typeof req.body.num, 'number') + t.assert.deepStrictEqual(req.params.id, 42) + t.assert.deepStrictEqual(req.body.num, 12) + testDone() } }) - fastify.inject({ method: 'POST', url: '/api/42', @@ -1107,7 +1141,7 @@ test('Custom AJV settings on different parameters - pt2', t => { }) }) -test("The same $id in route's schema must not overwrite others", t => { +test("The same $id in route's schema must not overwrite others", (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -1158,23 +1192,26 @@ test("The same $id in route's schema must not overwrite others", t => { handler: () => { return 'ok' } }) + const completion = waitForCb({ steps: 2 }) fastify.inject({ method: 'POST', url: '/user', body: {} }, (err, res) => { - t.error(err) - t.same(res.json().message, "body must have required property 'username'") + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json().message, "body must have required property 'username'") + completion.stepIn() }) - fastify.inject({ url: '/user/1', method: 'PATCH', body: {} }, (err, res) => { - t.error(err) - t.same(res.payload, 'ok') + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'ok') + completion.stepIn() }) + completion.patience.then(testDone) }) test('Custom validator compiler should not mutate schema', async t => { @@ -1183,7 +1220,7 @@ test('Custom validator compiler should not mutate schema', async t => { const fastify = Fastify() fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { - t.type(schema, Headers) + t.assert.ok(schema instanceof Headers) return () => { } }) @@ -1221,8 +1258,8 @@ test('Custom validator builder override by custom validator compiler', async t = } }, handler: (req, _reply) => { - t.same(typeof req.params.id, 'number') - t.same(req.params.id, 43) + t.assert.deepStrictEqual(typeof req.params.id, 'number') + t.assert.deepStrictEqual(req.params.id, 43) return 'ok' } }) @@ -1233,7 +1270,7 @@ test('Custom validator builder override by custom validator compiler', async t = method: 'POST', url: '/two/43' }) - t.equal(two.statusCode, 200) + t.assert.strictEqual(two.statusCode, 200) }) test('Custom validator builder override by custom validator compiler in child instance', async t => { @@ -1261,8 +1298,8 @@ test('Custom validator builder override by custom validator compiler in child in } }, handler: (req, _reply) => { - t.same(typeof req.params.id, 'number') - t.same(req.params.id, 43) + t.assert.deepStrictEqual(typeof req.params.id, 'number') + t.assert.deepStrictEqual(req.params.id, 43) return 'ok' } }) @@ -1281,8 +1318,8 @@ test('Custom validator builder override by custom validator compiler in child in } }, handler: (req, _reply) => { - t.same(typeof req.params.id, 'number') - t.same(req.params.id, 42) + t.assert.deepStrictEqual(typeof req.params.id, 'number') + t.assert.deepStrictEqual(req.params.id, 42) return 'ok' } }) @@ -1293,13 +1330,13 @@ test('Custom validator builder override by custom validator compiler in child in method: 'POST', url: '/one/42' }) - t.equal(one.statusCode, 200) + t.assert.strictEqual(one.statusCode, 200) const two = await fastify.inject({ method: 'POST', url: '/two/43' }) - t.equal(two.statusCode, 200) + t.assert.strictEqual(two.statusCode, 200) }) test('Schema validation when no content type is provided', async t => { @@ -1339,7 +1376,7 @@ test('Schema validation when no content type is provided', async t => { }, body: { invalid: 'string' } }) - t.equal(invalid.statusCode, 200) + t.assert.strictEqual(invalid.statusCode, 200) }) test('Schema validation will not be bypass by different content type', async t => { @@ -1365,7 +1402,7 @@ test('Schema validation will not be bypass by different content type', async t = }, async () => 'ok') await fastify.listen({ port: 0 }) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) const address = fastify.listeningOrigin const correct1 = await request(address, { @@ -1376,7 +1413,7 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ foo: 'string' }) }) - t.equal(correct1.statusCode, 200) + t.assert.strictEqual(correct1.statusCode, 200) await correct1.body.dump() const correct2 = await request(address, { @@ -1387,7 +1424,7 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ foo: 'string' }) }) - t.equal(correct2.statusCode, 200) + t.assert.strictEqual(correct2.statusCode, 200) await correct2.body.dump() const invalid1 = await request(address, { @@ -1398,8 +1435,8 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.equal(invalid1.statusCode, 400) - t.equal((await invalid1.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid1.statusCode, 400) + t.assert.strictEqual((await invalid1.body.json()).code, 'FST_ERR_VALIDATION') const invalid2 = await request(address, { method: 'POST', @@ -1409,8 +1446,8 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.equal(invalid2.statusCode, 400) - t.equal((await invalid2.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid2.statusCode, 400) + t.assert.strictEqual((await invalid2.body.json()).code, 'FST_ERR_VALIDATION') const invalid3 = await request(address, { method: 'POST', @@ -1420,8 +1457,8 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.equal(invalid3.statusCode, 400) - t.equal((await invalid3.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid3.statusCode, 400) + t.assert.strictEqual((await invalid3.body.json()).code, 'FST_ERR_VALIDATION') const invalid4 = await request(address, { method: 'POST', @@ -1431,8 +1468,8 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.equal(invalid4.statusCode, 400) - t.equal((await invalid4.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid4.statusCode, 400) + t.assert.strictEqual((await invalid4.body.json()).code, 'FST_ERR_VALIDATION') const invalid5 = await request(address, { method: 'POST', @@ -1442,8 +1479,8 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.equal(invalid5.statusCode, 400) - t.equal((await invalid5.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid5.statusCode, 400) + t.assert.strictEqual((await invalid5.body.json()).code, 'FST_ERR_VALIDATION') const invalid6 = await request(address, { method: 'POST', @@ -1453,8 +1490,8 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.equal(invalid6.statusCode, 415) - t.equal((await invalid6.body.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + t.assert.strictEqual(invalid6.statusCode, 415) + t.assert.strictEqual((await invalid6.body.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') const invalid7 = await request(address, { method: 'POST', @@ -1464,8 +1501,8 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.equal(invalid7.statusCode, 400) - t.equal((await invalid7.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid7.statusCode, 400) + t.assert.strictEqual((await invalid7.body.json()).code, 'FST_ERR_VALIDATION') const invalid8 = await request(address, { method: 'POST', @@ -1475,6 +1512,6 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.equal(invalid8.statusCode, 400) - t.equal((await invalid8.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid8.statusCode, 400) + t.assert.strictEqual((await invalid8.body.json()).code, 'FST_ERR_VALIDATION') }) From 0c7abb1eb55d8cae7c73789d8c400a5d1603d37b Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Fri, 16 May 2025 12:13:01 +0200 Subject: [PATCH 1025/1295] test: mv hooks-async from tap (#6084) --- test/async-await.test.js | 13 +- test/hooks-async.test.js | 465 +++++++++++++++++++++------------------ 2 files changed, 257 insertions(+), 221 deletions(-) diff --git a/test/async-await.test.js b/test/async-await.test.js index c90d1d44dfb..0f0a0cbe6ef 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -6,6 +6,7 @@ const Fastify = require('..') const split = require('split2') const pino = require('pino') const { sleep } = require('./helper') +const { waitForCb } = require('./toolkit') const statusCodes = require('node:http').STATUS_CODES const opts = { @@ -43,7 +44,7 @@ const optsWithHostnameAndPort = { } } } -test('async await', (t, done) => { +test('async await', (t, testDone) => { t.plan(16) const fastify = Fastify() try { @@ -79,6 +80,10 @@ test('async await', (t, done) => { t.assert.ifError(err) t.after(() => { fastify.close() }) + const completion = waitForCb({ + steps: 3 + }) + sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port @@ -87,6 +92,7 @@ test('async await', (t, done) => { t.assert.strictEqual(response.statusCode, 200) t.assert.strictEqual(response.headers['content-length'], '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) sget({ @@ -97,6 +103,7 @@ test('async await', (t, done) => { t.assert.strictEqual(response.statusCode, 200) t.assert.strictEqual(response.headers['content-length'], '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) sget({ @@ -108,8 +115,10 @@ test('async await', (t, done) => { const parsedBody = JSON.parse(body) t.assert.strictEqual(parsedBody.hostname, 'localhost') t.assert.strictEqual(parseInt(parsedBody.port), fastify.server.address().port) - done() + completion.stepIn() }) + + completion.patience.then(testDone) }) }) diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index 115cb32a212..e0eca1477d2 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -1,18 +1,17 @@ 'use strict' const { Readable } = require('node:stream') -const t = require('tap') -const test = t.test +const { test, describe } = require('node:test') const sget = require('simple-get').concat const Fastify = require('../fastify') const fs = require('node:fs') const { sleep } = require('./helper') +const { waitForCb } = require('./toolkit') process.removeAllListeners('warning') -test('async hooks', t => { +test('async hooks', (t, testDone) => { t.plan(21) - const fastify = Fastify({ exposeHeadRoutes: false }) fastify.addHook('onRequest', async function (request, reply) { await sleep(1) @@ -25,8 +24,8 @@ test('async hooks', t => { fastify.addHook('preHandler', async function (request, reply) { await sleep(1) - t.equal(request.test, 'the request is coming') - t.equal(reply.test, 'the reply has come') + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') if (request.raw.method === 'HEAD') { throw new Error('some error') } @@ -34,17 +33,21 @@ test('async hooks', t => { fastify.addHook('onSend', async function (request, reply, payload) { await sleep(1) - t.ok('onSend called') + t.assert.ok('onSend called') }) + const completion = waitForCb({ + steps: 3 + }) fastify.addHook('onResponse', async function (request, reply) { await sleep(1) - t.ok('onResponse called') + t.assert.ok('onResponse called') + completion.stepIn() }) fastify.get('/', function (request, reply) { - t.equal(request.test, 'the request is coming') - t.equal(reply.test, 'the reply has come') + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') reply.code(200).send({ hello: 'world' }) }) @@ -57,38 +60,38 @@ test('async hooks', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - sget({ method: 'HEAD', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) }) - sget({ method: 'DELETE', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) }) + + completion.patience.then(testDone) }) }) -test('modify payload', t => { +test('modify payload', (t, testDone) => { t.plan(10) const fastify = Fastify() const payload = { hello: 'world' } @@ -96,20 +99,20 @@ test('modify payload', t => { const anotherPayload = '"winter is coming"' fastify.addHook('onSend', async function (request, reply, thePayload) { - t.ok('onSend called') - t.same(JSON.parse(thePayload), payload) + t.assert.ok('onSend called') + t.assert.deepStrictEqual(JSON.parse(thePayload), payload) return thePayload.replace('world', 'modified') }) fastify.addHook('onSend', async function (request, reply, thePayload) { - t.ok('onSend called') - t.same(JSON.parse(thePayload), modifiedPayload) + t.assert.ok('onSend called') + t.assert.deepStrictEqual(JSON.parse(thePayload), modifiedPayload) return anotherPayload }) fastify.addHook('onSend', async function (request, reply, thePayload) { - t.ok('onSend called') - t.equal(thePayload, anotherPayload) + t.assert.ok('onSend called') + t.assert.deepStrictEqual(thePayload, anotherPayload) }) fastify.get('/', (req, reply) => { @@ -120,14 +123,15 @@ test('modify payload', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.payload, anotherPayload) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '18') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, anotherPayload) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '18') + testDone() }) }) -test('onRequest hooks should be able to block a request', t => { +test('onRequest hooks should be able to block a request', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -136,36 +140,37 @@ test('onRequest hooks should be able to block a request', t => { }) fastify.addHook('onRequest', async (req, reply) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('preHandler', async (req, reply) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', async (req, reply, payload) => { - t.ok('called') + t.assert.ok('called') }) fastify.addHook('onResponse', async (request, reply) => { - t.ok('called') + t.assert.ok('called') }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preParsing hooks should be able to modify the payload', t => { +test('preParsing hooks should be able to modify the payload', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -188,13 +193,14 @@ test('preParsing hooks should be able to modify the payload', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'another world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'another world' }) + testDone() }) }) -test('preParsing hooks should be able to supply statusCode', t => { +test('preParsing hooks should be able to supply statusCode', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -211,11 +217,11 @@ test('preParsing hooks should be able to supply statusCode', t => { }) fastify.addHook('onError', async (req, res, err) => { - t.equal(err.statusCode, 408) + t.assert.strictEqual(err.statusCode, 408) }) fastify.post('/', function (request, reply) { - t.fail('should not be called') + t.assert.fail('should not be called') }) fastify.inject({ @@ -223,17 +229,19 @@ test('preParsing hooks should be able to supply statusCode', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 408) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 408) + t.assert.deepStrictEqual(JSON.parse(res.payload), { statusCode: 408, error: 'Request Timeout', message: 'kaboom' }) + + testDone() }) }) -test('preParsing hooks should ignore statusCode 200 in stream error', t => { +test('preParsing hooks should ignore statusCode 200 in stream error', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -250,11 +258,11 @@ test('preParsing hooks should ignore statusCode 200 in stream error', t => { }) fastify.addHook('onError', async (req, res, err) => { - t.equal(err.statusCode, 400) + t.assert.strictEqual(err.statusCode, 400) }) fastify.post('/', function (request, reply) { - t.fail('should not be called') + t.assert.fail('should not be called') }) fastify.inject({ @@ -262,17 +270,18 @@ test('preParsing hooks should ignore statusCode 200 in stream error', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(res.payload), { statusCode: 400, error: 'Bad Request', message: 'kaboom' }) + testDone() }) }) -test('preParsing hooks should ignore non-number statusCode in stream error', t => { +test('preParsing hooks should ignore non-number statusCode in stream error', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -289,11 +298,11 @@ test('preParsing hooks should ignore non-number statusCode in stream error', t = }) fastify.addHook('onError', async (req, res, err) => { - t.equal(err.statusCode, 400) + t.assert.strictEqual(err.statusCode, 400) }) fastify.post('/', function (request, reply) { - t.fail('should not be called') + t.assert.fail('should not be called') }) fastify.inject({ @@ -301,17 +310,18 @@ test('preParsing hooks should ignore non-number statusCode in stream error', t = url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(res.payload), { statusCode: 400, error: 'Bad Request', message: 'kaboom' }) + testDone() }) }) -test('preParsing hooks should default to statusCode 400 if stream error', t => { +test('preParsing hooks should default to statusCode 400 if stream error', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -326,11 +336,11 @@ test('preParsing hooks should default to statusCode 400 if stream error', t => { }) fastify.addHook('onError', async (req, res, err) => { - t.equal(err.statusCode, 400) + t.assert.strictEqual(err.statusCode, 400) }) fastify.post('/', function (request, reply) { - t.fail('should not be called') + t.assert.fail('should not be called') }) fastify.inject({ @@ -338,20 +348,21 @@ test('preParsing hooks should default to statusCode 400 if stream error', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 400) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(res.payload), { statusCode: 400, error: 'Bad Request', message: 'kaboom' }) + testDone() }) }) -test('preParsing hooks should handle errors', t => { +test('preParsing hooks should handle errors', (t, testDone) => { t.plan(3) - const fastify = Fastify() + const fastify = Fastify() fastify.addHook('preParsing', async (req, reply, payload) => { const e = new Error('kaboom') e.statusCode = 501 @@ -367,13 +378,14 @@ test('preParsing hooks should handle errors', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 501) - t.same(JSON.parse(res.payload), { error: 'Not Implemented', message: 'kaboom', statusCode: 501 }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 501) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Implemented', message: 'kaboom', statusCode: 501 }) + testDone() }) }) -test('preHandler hooks should be able to block a request', t => { +test('preHandler hooks should be able to block a request', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -382,32 +394,33 @@ test('preHandler hooks should be able to block a request', t => { }) fastify.addHook('preHandler', async (req, reply) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', async (req, reply, payload) => { - t.equal(payload, 'hello') + t.assert.strictEqual(payload, 'hello') }) fastify.addHook('onResponse', async (request, reply) => { - t.ok('called') + t.assert.ok('called') }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preValidation hooks should be able to block a request', t => { +test('preValidation hooks should be able to block a request', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -416,38 +429,40 @@ test('preValidation hooks should be able to block a request', t => { }) fastify.addHook('preValidation', async (req, reply) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', async (req, reply, payload) => { - t.equal(payload, 'hello') + t.assert.strictEqual(payload, 'hello') }) fastify.addHook('onResponse', async (request, reply) => { - t.ok('called') + t.assert.ok('called') }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preValidation hooks should be able to change request body before validation', t => { +test('preValidation hooks should be able to change request body before validation', (t, testDone) => { t.plan(4) const fastify = Fastify() fastify.addHook('preValidation', async (req, _reply) => { const buff = Buffer.from(req.body.message, 'base64') req.body = JSON.parse(buff.toString('utf-8')) + t.assert.ok('has been called') }) fastify.post( @@ -469,7 +484,6 @@ test('preValidation hooks should be able to change request body before validatio } }, (req, reply) => { - t.pass() reply.status(200).send('hello') } ) @@ -481,13 +495,14 @@ test('preValidation hooks should be able to change request body before validatio message: Buffer.from(JSON.stringify({ foo: 'example', bar: 1 })).toString('base64') } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preSerialization hooks should be able to modify the payload', t => { +test('preSerialization hooks should be able to modify the payload', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -503,13 +518,14 @@ test('preSerialization hooks should be able to modify the payload', t => { url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'another world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'another world' }) + testDone() }) }) -test('preSerialization hooks should handle errors', t => { +test('preSerialization hooks should handle errors', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -525,18 +541,19 @@ test('preSerialization hooks should handle errors', t => { url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(JSON.parse(res.payload), { error: 'Internal Server Error', message: 'kaboom', statusCode: 500 }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', message: 'kaboom', statusCode: 500 }) + testDone() }) }) -test('preValidation hooks should handle throwing null', t => { +test('preValidation hooks should handle throwing null', (t, testDone) => { t.plan(4) const fastify = Fastify() fastify.setErrorHandler(async (error, request, reply) => { - t.ok(error instanceof Error) + t.assert.ok(error instanceof Error) await reply.send(error) }) @@ -545,24 +562,25 @@ test('preValidation hooks should handle throwing null', t => { throw null }) - fastify.get('/', function (request, reply) { t.fail('the handler must not be called') }) + fastify.get('/', function (request, reply) { t.assert.fail('the handler must not be called') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(res.json(), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { error: 'Internal Server Error', code: 'FST_ERR_SEND_UNDEFINED_ERR', message: 'Undefined error has occurred', statusCode: 500 }) + testDone() }) }) -test('preValidation hooks should handle throwing a string', t => { +test('preValidation hooks should handle throwing a string', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -571,19 +589,20 @@ test('preValidation hooks should handle throwing a string', t => { throw 'this is an error' }) - fastify.get('/', function (request, reply) { t.fail('the handler must not be called') }) + fastify.get('/', function (request, reply) { t.assert.fail('the handler must not be called') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.equal(res.payload, 'this is an error') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.payload, 'this is an error') + testDone() }) }) -test('onRequest hooks should be able to block a request (last hook)', t => { +test('onRequest hooks should be able to block a request (last hook)', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -592,32 +611,33 @@ test('onRequest hooks should be able to block a request (last hook)', t => { }) fastify.addHook('preHandler', async (req, reply) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', async (req, reply, payload) => { - t.ok('called') + t.assert.ok('called') }) fastify.addHook('onResponse', async (request, reply) => { - t.ok('called') + t.assert.ok('called') }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preHandler hooks should be able to block a request (last hook)', t => { +test('preHandler hooks should be able to block a request (last hook)', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -626,28 +646,29 @@ test('preHandler hooks should be able to block a request (last hook)', t => { }) fastify.addHook('onSend', async (req, reply, payload) => { - t.equal(payload, 'hello') + t.assert.strictEqual(payload, 'hello') }) fastify.addHook('onResponse', async (request, reply) => { - t.ok('called') + t.assert.ok('called') }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('onRequest respond with a stream', t => { +test('onRequest respond with a stream', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -663,40 +684,41 @@ test('onRequest respond with a stream', t => { }) fastify.addHook('onRequest', async (req, res) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('preHandler', async (req, reply) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', async (req, reply, payload) => { - t.ok('called') + t.assert.ok('called') }) fastify.addHook('onResponse', async (request, reply) => { - t.ok('called') + t.assert.ok('called') }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('preHandler respond with a stream', t => { +test('preHandler respond with a stream', (t, testDone) => { t.plan(7) const fastify = Fastify() fastify.addHook('onRequest', async (req, res) => { - t.ok('called') + t.assert.ok('called') }) // we are calling `reply.send` inside the `preHandler` hook with a stream, @@ -706,92 +728,96 @@ test('preHandler respond with a stream', t => { fastify.addHook('preHandler', async (req, reply) => { const stream = fs.createReadStream(__filename, 'utf8') reply.raw.once('finish', () => { - t.equal(order.shift(), 2) + t.assert.strictEqual(order.shift(), 2) }) return reply.send(stream) }) fastify.addHook('preHandler', async (req, reply) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', async (req, reply, payload) => { - t.equal(order.shift(), 1) - t.equal(typeof payload.pipe, 'function') + t.assert.strictEqual(order.shift(), 1) + t.assert.strictEqual(typeof payload.pipe, 'function') }) fastify.addHook('onResponse', async (request, reply) => { - t.ok('called') + t.assert.ok('called') }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Should log a warning if is an async function with `done`', t => { - t.test('2 arguments', t => { - t.plan(2) +describe('Should log a warning if is an async function with `done`', () => { + test('2 arguments', t => { const fastify = Fastify() try { - fastify.addHook('onRequestAbort', async (req, done) => {}) + fastify.addHook('onRequestAbort', async (req, done) => { + t.assert.fail('should have not be called') + }) } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) - t.test('3 arguments', t => { - t.plan(2) + test('3 arguments', t => { const fastify = Fastify() try { - fastify.addHook('onRequest', async (req, reply, done) => {}) + fastify.addHook('onRequest', async (req, reply, done) => { + t.assert.fail('should have not be called') + }) } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) - t.test('4 arguments', t => { - t.plan(6) + test('4 arguments', t => { const fastify = Fastify() try { - fastify.addHook('onSend', async (req, reply, payload, done) => {}) + fastify.addHook('onSend', async (req, reply, payload, done) => { + t.assert.fail('should have not be called') + }) } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } try { - fastify.addHook('preSerialization', async (req, reply, payload, done) => {}) + fastify.addHook('preSerialization', async (req, reply, payload, done) => { + t.assert.fail('should have not be called') + }) } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } try { - fastify.addHook('onError', async (req, reply, payload, done) => {}) + fastify.addHook('onError', async (req, reply, payload, done) => { + t.assert.fail('should have not be called') + }) } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) - - t.end() }) test('early termination, onRequest async', async t => { - t.plan(2) - const app = Fastify() app.addHook('onRequest', async (req, reply) => { @@ -800,12 +826,12 @@ test('early termination, onRequest async', async t => { }) app.get('/', (req, reply) => { - t.fail('should not happen') + t.assert.fail('should not happen') }) const res = await app.inject('/') - t.equal(res.statusCode, 200) - t.equal(res.body.toString(), 'hello world') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body.toString(), 'hello world') }) test('The this should be the same of the encapsulation level', async t => { @@ -813,9 +839,9 @@ test('The this should be the same of the encapsulation level', async t => { fastify.addHook('onRequest', async function (req, reply) { if (req.raw.url === '/nested') { - t.equal(this.foo, 'bar') + t.assert.strictEqual(this.foo, 'bar') } else { - t.equal(this.foo, undefined) + t.assert.strictEqual(this.foo, undefined) } }) @@ -833,12 +859,13 @@ test('The this should be the same of the encapsulation level', async t => { await fastify.inject({ method: 'GET', path: '/nested' }) }) -test('preSerializationEnd should handle errors if the serialize method throws', t => { - t.test('works with sync preSerialization', t => { - t.plan(2) +describe('preSerializationEnd should handle errors if the serialize method throws', () => { + test('works with sync preSerialization', (t, testDone) => { + t.plan(3) const fastify = Fastify() fastify.addHook('preSerialization', (request, reply, payload, done) => { + t.assert.ok('called') done(null, payload) }) @@ -851,16 +878,18 @@ test('preSerializationEnd should handle errors if the serialize method throws', method: 'POST', url: '/' }, (err, res) => { - t.error(err) - t.not(res.statusCode, 200) + t.assert.ifError(err) + t.assert.notEqual(res.statusCode, 200) + testDone() }) }) - t.test('works with async preSerialization', t => { - t.plan(2) + test('works with async preSerialization', (t, testDone) => { + t.plan(3) const fastify = Fastify() fastify.addHook('preSerialization', async (request, reply, payload) => { + t.assert.ok('called') return payload }) @@ -873,16 +902,15 @@ test('preSerializationEnd should handle errors if the serialize method throws', method: 'POST', url: '/' }, (err, res) => { - t.error(err) - t.not(res.statusCode, 200) + t.assert.ifError(err) + t.assert.notEqual(res.statusCode, 200) + testDone() }) }) - - t.end() }) -t.test('nested hooks to do not crash on 404', t => { - t.plan(2) +test('nested hooks to do not crash on 404', (t, testDone) => { + t.plan(3) const fastify = Fastify() fastify.get('/hello', (req, reply) => { @@ -895,22 +923,26 @@ t.test('nested hooks to do not crash on 404', t => { }) fastify.setNotFoundHandler(async (request, reply) => { + t.assert.ok('called') reply.statusCode = 404 return { status: 'nested-not-found' } }) fastify.setErrorHandler(async (error, request, reply) => { + t.assert.fail('should have not be called') reply.statusCode = 500 return { status: 'nested-error', error } }) }, { prefix: '/nested' }) fastify.setNotFoundHandler(async (request, reply) => { + t.assert.fail('should have not be called') reply.statusCode = 404 return { status: 'not-found' } }) fastify.setErrorHandler(async (error, request, reply) => { + t.assert.fail('should have not be called') reply.statusCode = 500 return { status: 'error', error } }) @@ -919,13 +951,13 @@ t.test('nested hooks to do not crash on 404', t => { method: 'GET', url: '/nested/something' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 404) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + testDone() }) }) test('Register an hook (preHandler) as route option should fail if mixing async and callback style', t => { - t.plan(2) const fastify = Fastify() try { @@ -942,15 +974,14 @@ test('Register an hook (preHandler) as route option should fail if mixing async return { hello: 'world' } } ) - t.fail('preHandler mixing async and callback style') + t.assert.fail('preHandler mixing async and callback style') } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) test('Register an hook (onSend) as route option should fail if mixing async and callback style', t => { - t.plan(2) const fastify = Fastify() try { @@ -967,15 +998,14 @@ test('Register an hook (onSend) as route option should fail if mixing async and return { hello: 'world' } } ) - t.fail('onSend mixing async and callback style') + t.assert.fail('onSend mixing async and callback style') } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) test('Register an hook (preSerialization) as route option should fail if mixing async and callback style', t => { - t.plan(2) const fastify = Fastify() try { @@ -992,15 +1022,14 @@ test('Register an hook (preSerialization) as route option should fail if mixing return { hello: 'world' } } ) - t.fail('preSerialization mixing async and callback style') + t.assert.fail('preSerialization mixing async and callback style') } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) test('Register an hook (onError) as route option should fail if mixing async and callback style', t => { - t.plan(2) const fastify = Fastify() try { @@ -1017,15 +1046,14 @@ test('Register an hook (onError) as route option should fail if mixing async and return { hello: 'world' } } ) - t.fail('onError mixing async and callback style') + t.assert.fail('onError mixing async and callback style') } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) test('Register an hook (preParsing) as route option should fail if mixing async and callback style', t => { - t.plan(2) const fastify = Fastify() try { @@ -1042,15 +1070,14 @@ test('Register an hook (preParsing) as route option should fail if mixing async return { hello: 'world' } } ) - t.fail('preParsing mixing async and callback style') + t.assert.fail('preParsing mixing async and callback style') } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) -test('Register an hook (onRequestAbort) as route option should fail if mixing async and callback style', t => { - t.plan(2) +test('Register an hook (onRequestAbort) as route option should fail if mixing async and callback style', (t) => { const fastify = Fastify() try { @@ -1067,9 +1094,9 @@ test('Register an hook (onRequestAbort) as route option should fail if mixing as return { hello: 'world' } } ) - t.fail('onRequestAbort mixing async and callback style') + t.assert.fail('onRequestAbort mixing async and callback style') } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') - t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') } }) From 16b2e4d018885a732aa0702bf708f4f790f5ef7f Mon Sep 17 00:00:00 2001 From: Sahachai Date: Sat, 17 May 2025 18:50:04 +0700 Subject: [PATCH 1026/1295] fix(types): add missing version to request.routeOptions (#6126) Co-authored-by: Frazer Smith --- test/types/request.test-d.ts | 1 + types/request.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 2642b675ed7..7dd58fdc5ce 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -78,6 +78,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.routeOptions.schema) expectType(request.routeOptions.handler) expectType(request.routeOptions.url) + expectType(request.routeOptions.version) expectType(request.headers) request.headers = {} diff --git a/types/request.d.ts b/types/request.d.ts index 0ea1adfb7c3..0ee81d3ecd0 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -32,6 +32,7 @@ export interface RequestRouteOptions Date: Mon, 19 May 2025 19:59:01 +0200 Subject: [PATCH 1027/1295] docs: remove fastify-sentry plugin (#6131) Remove @immobiliarelabs/fastify-sentry community plugin since it's no longer maintained. Signed-off-by: dnlup --- docs/Guides/Ecosystem.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index c8a1d414ff0..aed9d702b05 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -193,11 +193,6 @@ section. - [`@immobiliarelabs/fastify-metrics`](https://github.com/immobiliare/fastify-metrics) Minimalistic and opinionated plugin that collects usage/process metrics and dispatches to [statsd](https://github.com/statsd/statsd). -- [`@immobiliarelabs/fastify-sentry`](https://github.com/immobiliare/fastify-sentry) - Sentry errors handler that just works! Install, add your DSN and you're good - to go! - A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine - on Fastify - [`@inaiat/fastify-papr`](https://github.com/inaiat/fastify-papr) A plugin to integrate [Papr](https://github.com/plexinc/papr), the MongoDB ORM for TypeScript & MongoDB, with Fastify. From c771d184d88117ee3ecc18cce85a616105edf68a Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Wed, 21 May 2025 10:25:32 +0200 Subject: [PATCH 1028/1295] docs: add community plugins disclaimer (#6132) --- docs/Guides/Ecosystem.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index aed9d702b05..72d52bfc0a9 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -152,6 +152,15 @@ section. #### [Community](#community) +> 🛈 Note: +> Fastify community plugins are part of the broader community efforts, +> and we are thankful for these contributions. However, they are not +> maintained by the Fastify team. +> Use them at your own discretion. +> If you find malicious code, please +> [open an issue](https://github.com/fastify/fastify/issues/new/choose) or +> submit a PR to remove the plugin from the list. + - [`@aaroncadillac/crudify-mongo`](https://github.com/aaroncadillac/crudify-mongo) A simple way to add a crud in your fastify project. - [`@applicazza/fastify-nextjs`](https://github.com/applicazza/fastify-nextjs) From 832bdeaafa708121ebbf1f507d1c2c15f74d8d82 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 21 May 2025 17:29:22 +0100 Subject: [PATCH 1029/1295] docs: use cross-platform compatible info emoji (#6134) --- docs/Guides/Ecosystem.md | 2 +- docs/Reference/ContentTypeParser.md | 2 +- docs/Reference/Hooks.md | 28 +++++++++---------- docs/Reference/Logging.md | 6 ++-- docs/Reference/Middleware.md | 2 +- docs/Reference/Reply.md | 12 ++++---- docs/Reference/Request.md | 2 +- docs/Reference/Routes.md | 6 ++-- docs/Reference/Server.md | 18 ++++++------ .../Reference/Validation-and-Serialization.md | 2 +- 10 files changed, 40 insertions(+), 40 deletions(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 72d52bfc0a9..4a4f7b63009 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -152,7 +152,7 @@ section. #### [Community](#community) -> 🛈 Note: +> ℹ️ Note: > Fastify community plugins are part of the broader community efforts, > and we are thankful for these contributions. However, they are not > maintained by the Fastify team. diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index 69e3e94df7c..1352f65a5f4 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -152,7 +152,7 @@ fastify.addContentTypeParser('text/xml', function (request, payload, done) { }) ``` -> 🛈 Note: `function(req, done)` and `async function(req)` are +> ℹ️ Note: `function(req, done)` and `async function(req)` are > still supported but deprecated. #### Body Parser diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index a69f9f85fdf..10e23c60e65 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -34,7 +34,7 @@ are Request/Reply hooks and application hooks: - [Using Hooks to Inject Custom Properties](#using-hooks-to-inject-custom-properties) - [Diagnostics Channel Hooks](#diagnostics-channel-hooks) -> 🛈 Note: The `done` callback is not available when using `async`/`await` or +> ℹ️ Note: The `done` callback is not available when using `async`/`await` or > returning a `Promise`. If you do invoke a `done` callback in this situation > unexpected behavior may occur, e.g. duplicate invocation of handlers. @@ -68,7 +68,7 @@ fastify.addHook('onRequest', async (request, reply) => { }) ``` -> 🛈 Note: In the [onRequest](#onrequest) hook, `request.body` will always be +> ℹ️ Note: In the [onRequest](#onrequest) hook, `request.body` will always be > `undefined`, because the body parsing happens before the > [preValidation](#prevalidation) hook. @@ -98,16 +98,16 @@ fastify.addHook('preParsing', async (request, reply, payload) => { }) ``` -> 🛈 Note: In the [preParsing](#preparsing) hook, `request.body` will always be +> ℹ️ Note: In the [preParsing](#preparsing) hook, `request.body` will always be > `undefined`, because the body parsing happens before the > [preValidation](#prevalidation) hook. -> 🛈 Note: You should also add a `receivedEncodedLength` property to the +> ℹ️ Note: You should also add a `receivedEncodedLength` property to the > returned stream. This property is used to correctly match the request payload > with the `Content-Length` header value. Ideally, this property should be updated > on each received chunk. -> 🛈 Note: The size of the returned stream is checked to not exceed the limit +> ℹ️ Note: The size of the returned stream is checked to not exceed the limit > set in [`bodyLimit`](./Server.md#bodylimit) option. ### preValidation @@ -166,7 +166,7 @@ fastify.addHook('preSerialization', async (request, reply, payload) => { }) ``` -> 🛈 Note: The hook is NOT called if the payload is a `string`, a `Buffer`, a +> ℹ️ Note: The hook is NOT called if the payload is a `string`, a `Buffer`, a > `stream`, or `null`. ### onError @@ -196,7 +196,7 @@ user *(Note that the default error handler always sends the error back to the user)*. -> 🛈 Note: Unlike the other hooks, passing an error to the `done` function is not +> ℹ️ Note: Unlike the other hooks, passing an error to the `done` function is not > supported. ### onSend @@ -233,7 +233,7 @@ fastify.addHook('onSend', (request, reply, payload, done) => { > to `0`, whereas the `Content-Length` header will not be set if the payload is > `null`. -> 🛈 Note: If you change the payload, you may only change it to a `string`, a +> ℹ️ Note: If you change the payload, you may only change it to a `string`, a > `Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`. @@ -256,7 +256,7 @@ The `onResponse` hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example, to gather statistics. -> 🛈 Note: Setting `disableRequestLogging` to `true` will disable any error log +> ℹ️ Note: Setting `disableRequestLogging` to `true` will disable any error log > inside the `onResponse` hook. In this case use `try - catch` to log errors. ### onTimeout @@ -298,7 +298,7 @@ The `onRequestAbort` hook is executed when a client closes the connection before the entire request has been processed. Therefore, you will not be able to send data to the client. -> 🛈 Note: Client abort detection is not completely reliable. +> ℹ️ Note: Client abort detection is not completely reliable. > See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md) ### Manage Errors from a hook @@ -452,7 +452,7 @@ fastify.addHook('onListen', async function () { }) ``` -> 🛈 Note: This hook will not run when the server is started using +> ℹ️ Note: This hook will not run when the server is started using > fastify.inject()` or `fastify.ready()`. ### onClose @@ -576,7 +576,7 @@ This hook can be useful if you are developing a plugin that needs to know when a plugin context is formed, and you want to operate in that specific context, thus this hook is encapsulated. -> 🛈 Note: This hook will not be called if a plugin is wrapped inside +> ℹ️ Note: This hook will not be called if a plugin is wrapped inside > [`fastify-plugin`](https://github.com/fastify/fastify-plugin). ```js fastify.decorate('data', []) @@ -774,7 +774,7 @@ fastify.route({ }) ``` -> 🛈 Note: Both options also accept an array of functions. +> ℹ️ Note: Both options also accept an array of functions. ## Using Hooks to Inject Custom Properties @@ -861,7 +861,7 @@ channel.subscribe(function ({ fastify }) { }) ``` -> 🛈 Note: The TracingChannel class API is currently experimental and may undergo +> ℹ️ Note: The TracingChannel class API is currently experimental and may undergo > breaking changes even in semver-patch releases of Node.js. Five other events are published on a per-request basis following the diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 83388f03e57..f7a25368d6b 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -157,7 +157,7 @@ const fastify = require('fastify')({ }); ``` -> 🛈 Note: In some cases, the [`Reply`](./Reply.md) object passed to the `res` +> ℹ️ Note: In some cases, the [`Reply`](./Reply.md) object passed to the `res` > serializer cannot be fully constructed. When writing a custom `res` > serializer, check for the existence of any properties on `reply` aside from > `statusCode`, which is always present. For example, verify the existence of @@ -184,7 +184,7 @@ const fastify = require('fastify')({ }); ``` -> 🛈 Note: The body cannot be serialized inside a `req` method because the +> ℹ️ Note: The body cannot be serialized inside a `req` method because the request is serialized when the child logger is created. At that time, the body is not yet parsed. @@ -199,7 +199,7 @@ app.addHook('preHandler', function (req, reply, done) { }) ``` -> 🛈 Note: Ensure serializers never throw errors, as this can cause the Node +> ℹ️ Note: Ensure serializers never throw errors, as this can cause the Node > process to exit. See the > [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) for more > information. diff --git a/docs/Reference/Middleware.md b/docs/Reference/Middleware.md index 4a64a197d58..5d4d7b03cfc 100644 --- a/docs/Reference/Middleware.md +++ b/docs/Reference/Middleware.md @@ -50,7 +50,7 @@ that already has the Fastify [Request](./Request.md#request) and To run middleware under certain paths, pass the path as the first parameter to `use`. -> 🛈 Note: This does not support routes with parameters +> ℹ️ Note: This does not support routes with parameters > (e.g. `/user/:id/comments`) and wildcards are not supported in multiple paths. ```js diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 0b7a8748b67..183c29d8203 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -151,7 +151,7 @@ fastify.get('/', async function (req, rep) { Sets a response header. If the value is omitted or undefined, it is coerced to `''`. -> 🛈 Note: The header's value must be properly encoded using +> ℹ️ Note: The header's value must be properly encoded using > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) > or similar modules such as > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid characters @@ -260,10 +260,10 @@ requires heavy resources to be sent after the `data`, for example, `Server-Timing` and `Etag`. It can ensure the client receives the response data as soon as possible. -> 🛈 Note: The header `Transfer-Encoding: chunked` will be added once you use +> ℹ️ Note: The header `Transfer-Encoding: chunked` will be added once you use > the trailer. It is a hard requirement for using trailer in Node.js. -> 🛈 Note: Any error passed to `done` callback will be ignored. If you interested +> ℹ️ Note: Any error passed to `done` callback will be ignored. If you interested > in the error, you can turn on `debug` level logging.* ```js @@ -314,7 +314,7 @@ reply.getTrailer('server-timing') // undefined Redirects a request to the specified URL, the status code is optional, default to `302` (if status code is not already set by calling `code`). -> 🛈 Note: The input URL must be properly encoded using +> ℹ️ Note: The input URL must be properly encoded using > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) > or similar modules such as > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid URLs will @@ -823,7 +823,7 @@ automatically create an error structured as the following: You can add custom properties to the Error object, such as `headers`, that will be used to enhance the HTTP response. -> 🛈 Note: If you are passing an error to `send` and the statusCode is less than +> ℹ️ Note: If you are passing an error to `send` and the statusCode is less than > 400, Fastify will automatically set it at 500. Tip: you can simplify errors by using the @@ -871,7 +871,7 @@ fastify.get('/', { If you want to customize error handling, check out [`setErrorHandler`](./Server.md#seterrorhandler) API. -> 🛈 Note: you are responsible for logging when customizing the error handler. +> ℹ️ Note: you are responsible for logging when customizing the error handler. API: diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 80c904845f4..846a7d62078 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -84,7 +84,7 @@ This operation adds new values to the request headers, accessible via For performance reasons, `Symbol('fastify.RequestAcceptVersion')` may be added to headers on `not found` routes. -> 🛈 Note: Schema validation may mutate the `request.headers` and +> ℹ️ Note: Schema validation may mutate the `request.headers` and > `request.raw.headers` objects, causing the headers to become empty. ```js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 328cb053c51..64d17a49498 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -137,7 +137,7 @@ fastify.route(options) * `reply` is defined in [Reply](./Reply.md). -> 🛈 Note: The documentation for `onRequest`, `preParsing`, `preValidation`, +> ℹ️ Note: The documentation for `onRequest`, `preParsing`, `preValidation`, > `preHandler`, `preSerialization`, `onSend`, and `onResponse` is detailed in > [Hooks](./Hooks.md). To send a response before the request is handled by the > `handler`, see [Respond to a request from @@ -233,7 +233,7 @@ const opts = { fastify.get('/', opts) ``` -> 🛈 Note: Specifying the handler in both `options` and as the third parameter to +> ℹ️ Note: Specifying the handler in both `options` and as the third parameter to > the shortcut method throws a duplicate `handler` error. ### Url building @@ -402,7 +402,7 @@ This approach supports both `callback-style` and `async-await` with minimal trade-off. However, it is recommended to use only one style for consistent error handling within your application. -> 🛈 Note: Every async function returns a promise by itself. +> ℹ️ Note: Every async function returns a promise by itself. ### Route Prefixing diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 13b1520034f..801c1c47879 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -193,7 +193,7 @@ to understand the effect of this option. This option only applies when HTTP/1.1 is in use. Also, when `serverFactory` option is specified, this option is ignored. -> 🛈 Note: +> ℹ️ Note: > At the time of writing, only node >= v16.10.0 supports this option. ### `requestTimeout` @@ -211,7 +211,7 @@ It must be set to a non-zero value (e.g. 120 seconds) to protect against potenti Denial-of-Service attacks in case the server is deployed without a reverse proxy in front. -> 🛈 Note: +> ℹ️ Note: > At the time of writing, only node >= v14.11.0 supports this option ### `ignoreTrailingSlash` @@ -529,7 +529,7 @@ Especially in distributed systems, you may want to override the default ID generation behavior as shown below. For generating `UUID`s you may want to check out [hyperid](https://github.com/mcollina/hyperid). -> 🛈 Note: +> ℹ️ Note: > `genReqId` will be not called if the header set in > [requestIdHeader](#requestidheader) is available (defaults to > 'request-id'). @@ -581,7 +581,7 @@ fastify.get('/', (request, reply) => { }) ``` -> 🛈 Note: +> ℹ️ Note: > If a request contains multiple `x-forwarded-host` or `x-forwarded-proto` > headers, it is only the last one that is used to derive `request.hostname` > and `request.protocol`. @@ -736,7 +736,7 @@ Fastify provides default error handlers for the most common use cases. It is possible to override one or more of those handlers with custom code using this option. -> 🛈 Note: +> ℹ️ Note: > Only `FST_ERR_BAD_URL` and `FST_ERR_ASYNC_CONSTRAINT` are implemented at present. ```js @@ -789,7 +789,7 @@ function defaultClientErrorHandler (err, socket) { } ``` -> 🛈 Note: +> ℹ️ Note: > `clientErrorHandler` operates with raw sockets. The handler is expected to > return a properly formed HTTP response that includes a status line, HTTP headers > and a message body. Before attempting to write the socket, the handler should @@ -1357,7 +1357,7 @@ Set the schema error formatter for all routes. See Set the schema serializer compiler for all routes. See [#schema-serializer](./Validation-and-Serialization.md#schema-serializer). -> 🛈 Note: +> ℹ️ Note: > [`setReplySerializer`](#set-reply-serializer) has priority if set! #### validatorCompiler @@ -1490,7 +1490,7 @@ lifecycle](./Lifecycle.md#lifecycle). *async-await* is supported as well. You can also register [`preValidation`](./Hooks.md#route-hooks) and [`preHandler`](./Hooks.md#route-hooks) hooks for the 404 handler. -> 🛈 Note: +> ℹ️ Note: > The `preValidation` hook registered using this method will run for a > route that Fastify does not recognize and **not** when a route handler manually > calls [`reply.callNotFound`](./Reply.md#call-not-found). In which case, only @@ -1526,7 +1526,7 @@ plugins are registered. If you would like to augment the behavior of the default arguments `fastify.setNotFoundHandler()` within the context of these registered plugins. -> 🛈 Note: +> ℹ️ Note: > Some config properties from the request object will be > undefined inside the custom not found handler. E.g.: > `request.routeOptions.url`, `routeOptions.method` and `routeOptions.config`. diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 5755d6a0364..703192fb805 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -432,7 +432,7 @@ fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { return ajv.compile(schema) }) ``` -> 🛈 Note: When using a custom validator instance, add schemas to the validator +> ℹ️ Note: When using a custom validator instance, add schemas to the validator > instead of Fastify. Fastify's `addSchema` method will not recognize the custom > validator. From 8e3a59ead5631383adc5ecad448b3ef3790ebcac Mon Sep 17 00:00:00 2001 From: Cangit Date: Sun, 25 May 2025 16:15:11 +0200 Subject: [PATCH 1030/1295] perf: nits in reply.js (#6136) --- lib/reply.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/reply.js b/lib/reply.js index bdee9e2629c..1aaeaf17419 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -215,12 +215,8 @@ Reply.prototype.send = function (payload) { Reply.prototype.getHeader = function (key) { key = key.toLowerCase() - const res = this.raw - let value = this[kReplyHeaders][key] - if (value === undefined && res.hasHeader(key)) { - value = res.getHeader(key) - } - return value + const value = this[kReplyHeaders][key] + return value !== undefined ? value : this.raw.getHeader(key) } Reply.prototype.getHeaders = function () { @@ -315,12 +311,12 @@ Reply.prototype.removeTrailer = function (key) { } Reply.prototype.code = function (code) { - const intValue = Number(code) - if (isNaN(intValue) || intValue < 100 || intValue > 599) { + const statusCode = +code + if (!(statusCode >= 100 && statusCode <= 599)) { throw new FST_ERR_BAD_STATUS_CODE(code || String(code)) } - this.raw.statusCode = intValue + this.raw.statusCode = statusCode this[kReplyHasStatusCode] = true return this } @@ -500,11 +496,11 @@ function preSerializationHook (reply, payload) { preSerializationHookEnd ) } else { - preSerializationHookEnd(null, reply.request, reply, payload) + preSerializationHookEnd(null, undefined, reply, payload) } } -function preSerializationHookEnd (err, request, reply, payload) { +function preSerializationHookEnd (err, _request, reply, payload) { if (err != null) { onErrorHook(reply, err) return From 3797d944a84da1da40d2ce7d7d6d174797e270c1 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Wed, 28 May 2025 17:33:18 +0200 Subject: [PATCH 1031/1295] docs: join core team (#6142) * docs: join core team * feat: update package.json --- README.md | 2 ++ package.json | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index a7279c28a14..6f873b527f5 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,8 @@ listed in alphabetical order. * [__Vincent Le Goff__](https://github.com/zekth) * [__Luciano Mammino__](https://github.com/lmammino), , +* [__Jean Michelet__](https://github.com/jean-michelet), + * [__KaKa Ng__](https://github.com/climba03003), * [__Luis Orbaiceta__](https://github.com/luisorbaiceta), diff --git a/package.json b/package.json index 7a892971280..b4bfbff8528 100644 --- a/package.json +++ b/package.json @@ -141,6 +141,11 @@ "name": "KaKa Ng", "email": "kaka@kakang.dev", "url": "https://github.com/climba03003" + }, + { + "name": "Jean Michelet", + "email": "jean.antoine.michelet@gmail.com", + "url": "https://github.com/jean-michelet" } ], "license": "MIT", From 52eea4b1b84ae6634c22eabf34e1e4547a330b31 Mon Sep 17 00:00:00 2001 From: Piotr Date: Thu, 29 May 2025 21:54:34 +0200 Subject: [PATCH 1032/1295] Fix typo in docs (#6145) Signed-off-by: Piotr --- docs/Reference/Reply.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 183c29d8203..c456b60011a 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -279,14 +279,14 @@ const { createHash } = require('node:crypto') reply.trailer('content-md5', function(reply, payload, done) { const hash = createHash('md5') hash.update(payload) - done(null, hash.disgest('hex')) + done(null, hash.digest('hex')) }) // when you prefer async-await reply.trailer('content-md5', async function(reply, payload) { const hash = createHash('md5') hash.update(payload) - return hash.disgest('hex') + return hash.digest('hex') }) ``` From e05597b36f93f76439cadc3a5024cedab69e4140 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Fri, 30 May 2025 15:35:07 +0200 Subject: [PATCH 1033/1295] test: mv hooks from tap (#6087) * test: mv hooks from tap * fix: dont trust socket.onclose * refactor: remove sequence --- test/hooks.test.js | 1679 ++++++++++++++++++++++++-------------------- 1 file changed, 910 insertions(+), 769 deletions(-) diff --git a/test/hooks.test.js b/test/hooks.test.js index c753cff4160..100adf54c82 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -1,7 +1,6 @@ 'use strict' -const t = require('tap') -const test = t.test +const { test } = require('node:test') const sget = require('simple-get').concat const stream = require('node:stream') const Fastify = require('..') @@ -13,88 +12,89 @@ const payload = { hello: 'world' } const proxyquire = require('proxyquire') const { connect } = require('node:net') const { sleep, getServerUrl } = require('./helper') +const { waitForCb } = require('./toolkit.js') process.removeAllListeners('warning') -test('hooks', t => { +test('hooks', (t, testDone) => { t.plan(49) const fastify = Fastify({ exposeHeadRoutes: false }) try { fastify.addHook('preHandler', function (request, reply, done) { - t.equal(request.test, 'the request is coming') - t.equal(reply.test, 'the reply has come') + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') if (request.raw.method === 'HEAD') { done(new Error('some error')) } else { done() } }) - t.pass() + t.assert.ok('should pass') } catch (e) { - t.fail() + t.assert.fail() } try { fastify.addHook('preHandler', null) } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') - t.equal(e.message, 'preHandler hook should be a function, instead got null') - t.pass() + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.assert.strictEqual(e.message, 'preHandler hook should be a function, instead got null') + t.assert.ok('should pass') } try { fastify.addHook('preParsing') } catch (e) { - t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') - t.equal(e.message, 'preParsing hook should be a function, instead got undefined') - t.pass() + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.assert.strictEqual(e.message, 'preParsing hook should be a function, instead got undefined') + t.assert.ok('should pass') } try { fastify.addHook('preParsing', function (request, reply, payload, done) { request.preParsing = true - t.equal(request.test, 'the request is coming') - t.equal(reply.test, 'the reply has come') + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') done() }) - t.pass() + t.assert.ok('should pass') } catch (e) { - t.fail() + t.assert.fail() } try { fastify.addHook('preParsing', function (request, reply, payload, done) { request.preParsing = true - t.equal(request.test, 'the request is coming') - t.equal(reply.test, 'the reply has come') + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') done() }) - t.pass() + t.assert.ok('should pass') } catch (e) { - t.fail() + t.assert.fail() } try { fastify.addHook('preValidation', function (request, reply, done) { - t.equal(request.preParsing, true) - t.equal(request.test, 'the request is coming') - t.equal(reply.test, 'the reply has come') + t.assert.strictEqual(request.preParsing, true) + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') done() }) - t.pass() + t.assert.ok('should pass') } catch (e) { - t.fail() + t.assert.fail() } try { fastify.addHook('preSerialization', function (request, reply, payload, done) { - t.ok('preSerialization called') + t.assert.ok('preSerialization called') done() }) - t.pass() + t.assert.ok('should pass') } catch (e) { - t.fail() + t.assert.fail() } try { @@ -107,18 +107,18 @@ test('hooks', t => { done() } }) - t.pass() + t.assert.ok('should pass') } catch (e) { - t.fail() + t.assert.fail() } fastify.addHook('onResponse', function (request, reply, done) { - t.ok('onResponse called') + t.assert.ok('onResponse called') done() }) fastify.addHook('onSend', function (req, reply, thePayload, done) { - t.ok('onSend called') + t.assert.ok('onSend called') done() }) @@ -126,12 +126,12 @@ test('hooks', t => { method: 'GET', url: '/', handler: function (req, reply) { - t.equal(req.test, 'the request is coming') - t.equal(reply.test, 'the reply has come') + t.assert.strictEqual(req.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') reply.code(200).send(payload) }, onResponse: function (req, reply, done) { - t.ok('onResponse inside hook') + t.assert.ok('onResponse inside hook') }, response: { 200: { @@ -149,44 +149,47 @@ test('hooks', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) + const completion = waitForCb({ steps: 3 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) - sget({ method: 'HEAD', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + completion.stepIn() }) - sget({ method: 'DELETE', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('onRequest hook should support encapsulation / 1', t => { +test('onRequest hook should support encapsulation / 1', (t, testDone) => { t.plan(5) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.addHook('onRequest', (req, reply, done) => { - t.equal(req.raw.url, '/plugin') + t.assert.strictEqual(req.raw.url, '/plugin') done() }) @@ -202,17 +205,18 @@ test('onRequest hook should support encapsulation / 1', t => { }) fastify.inject('/root', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) - fastify.inject('/plugin', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + fastify.inject('/plugin', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) }) }) -test('onRequest hook should support encapsulation / 2', (t) => { +test('onRequest hook should support encapsulation / 2', (t, testDone) => { t.plan(3) const fastify = Fastify() let pluginInstance @@ -226,20 +230,21 @@ test('onRequest hook should support encapsulation / 2', (t) => { }) fastify.ready(err => { - t.error(err) - t.equal(fastify[symbols.kHooks].onRequest.length, 1) - t.equal(pluginInstance[symbols.kHooks].onRequest.length, 2) + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].onRequest.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].onRequest.length, 2) + testDone() }) }) -test('onRequest hook should support encapsulation / 3', t => { +test('onRequest hook should support encapsulation / 3', (t, testDone) => { t.plan(20) const fastify = Fastify() fastify.decorate('hello', 'world') fastify.addHook('onRequest', function (req, reply, done) { - t.ok(this.hello) - t.ok(this.hello2) + t.assert.ok(this.hello) + t.assert.ok(this.hello2) req.first = true done() }) @@ -247,24 +252,24 @@ test('onRequest hook should support encapsulation / 3', t => { fastify.decorate('hello2', 'world') fastify.get('/first', (req, reply) => { - t.ok(req.first) - t.notOk(req.second) + t.assert.ok(req.first) + t.assert.ok(!req.second) reply.send({ hello: 'world' }) }) fastify.register((instance, opts, done) => { instance.decorate('hello3', 'world') instance.addHook('onRequest', function (req, reply, done) { - t.ok(this.hello) - t.ok(this.hello2) - t.ok(this.hello3) + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok(this.hello3) req.second = true done() }) instance.get('/second', (req, reply) => { - t.ok(req.first) - t.ok(req.second) + t.assert.ok(req.first) + t.assert.ok(req.second) reply.send({ hello: 'world' }) }) @@ -272,61 +277,64 @@ test('onRequest hook should support encapsulation / 3', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) - t.teardown(() => { fastify.close() }) + t.assert.ifError(err) + t.after(() => { fastify.close() }) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) - sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('preHandler hook should support encapsulation / 5', t => { +test('preHandler hook should support encapsulation / 5', (t, testDone) => { t.plan(17) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('preHandler', function (req, res, done) { - t.ok(this.hello) + t.assert.ok(this.hello) req.first = true done() }) fastify.get('/first', (req, reply) => { - t.ok(req.first) - t.notOk(req.second) + t.assert.ok(req.first) + t.assert.ok(!req.second) reply.send({ hello: 'world' }) }) fastify.register((instance, opts, done) => { instance.decorate('hello2', 'world') instance.addHook('preHandler', function (req, res, done) { - t.ok(this.hello) - t.ok(this.hello2) + t.assert.ok(this.hello) + t.assert.ok(this.hello2) req.second = true done() }) instance.get('/second', (req, reply) => { - t.ok(req.first) - t.ok(req.second) + t.assert.ok(req.first) + t.assert.ok(req.second) reply.send({ hello: 'world' }) }) @@ -334,37 +342,40 @@ test('preHandler hook should support encapsulation / 5', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) - sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('onRoute hook should be called / 1', t => { +test('onRoute hook should be called / 1', (t, testDone) => { t.plan(2) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', () => { - t.pass() + t.assert.ok('should pass') }) instance.get('/', opts, function (req, reply) { reply.send() @@ -373,23 +384,24 @@ test('onRoute hook should be called / 1', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should be called / 2', t => { +test('onRoute hook should be called / 2', (t, testDone) => { t.plan(5) let firstHandler = 0 let secondHandler = 0 const fastify = Fastify({ exposeHeadRoutes: false }) fastify.addHook('onRoute', (route) => { - t.pass() + t.assert.ok('should pass') firstHandler++ }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', (route) => { - t.pass() + t.assert.ok('should pass') secondHandler++ }) instance.get('/', opts, function (req, reply) { @@ -398,16 +410,17 @@ test('onRoute hook should be called / 2', t => { done() }) .after(() => { - t.equal(firstHandler, 1) - t.equal(secondHandler, 1) + t.assert.strictEqual(firstHandler, 1) + t.assert.strictEqual(secondHandler, 1) }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should be called / 3', t => { +test('onRoute hook should be called / 3', (t, testDone) => { t.plan(5) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -416,18 +429,18 @@ test('onRoute hook should be called / 3', t => { } fastify.addHook('onRoute', (route) => { - t.pass() + t.assert.ok('should pass') }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', (route) => { - t.pass() + t.assert.ok('should pass') }) instance.get('/a', handler) done() }) .after((err, done) => { - t.error(err) + t.assert.ifError(err) setTimeout(() => { fastify.get('/b', handler) done() @@ -435,21 +448,22 @@ test('onRoute hook should be called / 3', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should be called (encapsulation support) / 4', t => { +test('onRoute hook should be called (encapsulation support) / 4', (t, testDone) => { t.plan(4) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.addHook('onRoute', () => { - t.pass() + t.assert.ok('should pass') }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', () => { - t.pass() + t.assert.ok('should pass') }) instance.get('/nested', opts, function (req, reply) { reply.send() @@ -462,11 +476,12 @@ test('onRoute hook should be called (encapsulation support) / 4', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should be called (encapsulation support) / 5', t => { +test('onRoute hook should be called (encapsulation support) / 5', (t, testDone) => { t.plan(2) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -476,7 +491,7 @@ test('onRoute hook should be called (encapsulation support) / 5', t => { fastify.register((instance, opts, done) => { instance.addHook('onRoute', () => { - t.pass() + t.assert.ok('should pass') }) instance.get('/nested', opts, function (req, reply) { reply.send() @@ -489,11 +504,12 @@ test('onRoute hook should be called (encapsulation support) / 5', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should be called (encapsulation support) / 6', t => { +test('onRoute hook should be called (encapsulation support) / 6', (t, testDone) => { t.plan(1) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -502,25 +518,26 @@ test('onRoute hook should be called (encapsulation support) / 6', t => { }) fastify.addHook('onRoute', () => { - t.fail('This should not be called') + t.assert.fail('This should not be called') }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute should keep the context', t => { +test('onRoute should keep the context', (t, testDone) => { t.plan(4) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.register((instance, opts, done) => { instance.decorate('test', true) instance.addHook('onRoute', onRoute) - t.ok(instance.prototype === fastify.prototype) + t.assert.ok(instance.prototype === fastify.prototype) function onRoute (route) { - t.ok(this.test) - t.equal(this, instance) + t.assert.ok(this.test) + t.assert.strictEqual(this, instance) } instance.get('/', opts, function (req, reply) { @@ -531,26 +548,27 @@ test('onRoute should keep the context', t => { }) fastify.close((err) => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should pass correct route', t => { +test('onRoute hook should pass correct route', (t, testDone) => { t.plan(9) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.addHook('onRoute', (route) => { - t.equal(route.method, 'GET') - t.equal(route.url, '/') - t.equal(route.path, '/') - t.equal(route.routePath, '/') + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/') + t.assert.strictEqual(route.path, '/') + t.assert.strictEqual(route.routePath, '/') }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', (route) => { - t.equal(route.method, 'GET') - t.equal(route.url, '/') - t.equal(route.path, '/') - t.equal(route.routePath, '/') + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/') + t.assert.strictEqual(route.path, '/') + t.assert.strictEqual(route.routePath, '/') }) instance.get('/', opts, function (req, reply) { reply.send() @@ -559,28 +577,29 @@ test('onRoute hook should pass correct route', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should pass correct route with custom prefix', t => { +test('onRoute hook should pass correct route with custom prefix', (t, testDone) => { t.plan(11) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.addHook('onRoute', function (route) { - t.equal(route.method, 'GET') - t.equal(route.url, '/v1/foo') - t.equal(route.path, '/v1/foo') - t.equal(route.routePath, '/foo') - t.equal(route.prefix, '/v1') + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/v1/foo') + t.assert.strictEqual(route.path, '/v1/foo') + t.assert.strictEqual(route.routePath, '/foo') + t.assert.strictEqual(route.prefix, '/v1') }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', function (route) { - t.equal(route.method, 'GET') - t.equal(route.url, '/v1/foo') - t.equal(route.path, '/v1/foo') - t.equal(route.routePath, '/foo') - t.equal(route.prefix, '/v1') + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/v1/foo') + t.assert.strictEqual(route.path, '/v1/foo') + t.assert.strictEqual(route.routePath, '/foo') + t.assert.strictEqual(route.prefix, '/v1') }) instance.get('/foo', opts, function (req, reply) { reply.send() @@ -589,20 +608,21 @@ test('onRoute hook should pass correct route with custom prefix', t => { }, { prefix: '/v1' }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should pass correct route with custom options', t => { +test('onRoute hook should pass correct route with custom options', (t, testDone) => { t.plan(6) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', function (route) { - t.equal(route.method, 'GET') - t.equal(route.url, '/foo') - t.equal(route.logLevel, 'info') - t.equal(route.bodyLimit, 100) - t.type(route.logSerializers.test, 'function') + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/foo') + t.assert.strictEqual(route.logLevel, 'info') + t.assert.strictEqual(route.bodyLimit, 100) + t.assert.ok(typeof route.logSerializers.test === 'function') }) instance.get('/foo', { logLevel: 'info', @@ -617,19 +637,20 @@ test('onRoute hook should pass correct route with custom options', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should receive any route option', t => { +test('onRoute hook should receive any route option', (t, testDone) => { t.plan(5) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', function (route) { - t.equal(route.method, 'GET') - t.equal(route.url, '/foo') - t.equal(route.routePath, '/foo') - t.equal(route.auth, 'basic') + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/foo') + t.assert.strictEqual(route.routePath, '/foo') + t.assert.strictEqual(route.auth, 'basic') }) instance.get('/foo', { auth: 'basic' }, function (req, reply) { reply.send() @@ -638,19 +659,20 @@ test('onRoute hook should receive any route option', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should preserve system route configuration', t => { +test('onRoute hook should preserve system route configuration', (t, testDone) => { t.plan(5) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', function (route) { - t.equal(route.method, 'GET') - t.equal(route.url, '/foo') - t.equal(route.routePath, '/foo') - t.equal(route.handler.length, 2) + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/foo') + t.assert.strictEqual(route.routePath, '/foo') + t.assert.strictEqual(route.handler.length, 2) }) instance.get('/foo', { url: '/bar', method: 'POST' }, function (req, reply) { reply.send() @@ -659,11 +681,12 @@ test('onRoute hook should preserve system route configuration', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRoute hook should preserve handler function in options of shorthand route system configuration', t => { +test('onRoute hook should preserve handler function in options of shorthand route system configuration', (t, testDone) => { t.plan(2) const handler = (req, reply) => {} @@ -671,19 +694,20 @@ test('onRoute hook should preserve handler function in options of shorthand rout const fastify = Fastify({ exposeHeadRoutes: false }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', function (route) { - t.equal(route.handler, handler) + t.assert.strictEqual(route.handler, handler) }) instance.get('/foo', { handler }) done() }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) // issue ref https://github.com/fastify/fastify-compress/issues/140 -test('onRoute hook should be called once when prefixTrailingSlash', t => { +test('onRoute hook should be called once when prefixTrailingSlash', (t, testDone) => { t.plan(3) let onRouteCalled = 0 @@ -719,21 +743,22 @@ test('onRoute hook should be called once when prefixTrailingSlash', t => { }, { prefix: '/prefix' }) fastify.ready(err => { - t.error(err) - t.equal(onRouteCalled, 1) // onRoute hook was called once - t.equal(routePatched, 1) // and plugin acted once and avoided redundant route patching + t.assert.ifError(err) + t.assert.strictEqual(onRouteCalled, 1) // onRoute hook was called once + t.assert.strictEqual(routePatched, 1) // and plugin acted once and avoided redundant route patching + testDone() }) }) -test('onRoute hook should able to change the route url', t => { +test('onRoute hook should able to change the route url', (t, testDone) => { t.plan(5) const fastify = Fastify({ exposeHeadRoutes: false }) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.register((instance, opts, done) => { instance.addHook('onRoute', (route) => { - t.equal(route.url, '/foo') + t.assert.strictEqual(route.url, '/foo') route.url = encodeURI(route.url) }) @@ -745,20 +770,21 @@ test('onRoute hook should able to change the route url', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: getServerUrl(fastify) + encodeURI('/foo') }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(body.toString(), 'here /foo') + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(body.toString(), 'here /foo') + testDone() }) }) }) -test('onRoute hook that throws should be caught', t => { +test('onRoute hook that throws should be caught', (t, testDone) => { t.plan(1) const fastify = Fastify({ exposeHeadRoutes: false }) @@ -772,30 +798,30 @@ test('onRoute hook that throws should be caught', t => { reply.send() }) - t.fail('onRoute should throw sync if error') + t.assert.fail('onRoute should throw sync if error') } catch (error) { - t.ok(error) + t.assert.ok(error) } done() }) - fastify.ready() + fastify.ready(testDone) }) -test('onRoute hook with many prefix', t => { +test('onRoute hook with many prefix', (t, testDone) => { t.plan(3) const fastify = Fastify({ exposeHeadRoutes: false }) const handler = (req, reply) => { reply.send({}) } const onRouteChecks = [ - { routePath: '/anotherPath', prefix: '/two', url: '/one/two/anotherPath' }, + { routePath: '/anotherPath', prefix: '/one/two', url: '/one/two/anotherPath' }, { routePath: '/aPath', prefix: '/one', url: '/one/aPath' } ] fastify.register((instance, opts, done) => { - instance.addHook('onRoute', (route) => { - t.match(route, onRouteChecks.pop()) + instance.addHook('onRoute', ({ routePath, prefix, url }) => { + t.assert.deepStrictEqual({ routePath, prefix, url }, onRouteChecks.pop()) }) instance.route({ method: 'GET', url: '/aPath', handler }) @@ -806,15 +832,18 @@ test('onRoute hook with many prefix', t => { done() }, { prefix: '/one' }) - fastify.ready(err => { t.error(err) }) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('onRoute hook should not be called when it registered after route', t => { +test('onRoute hook should not be called when it registered after route', (t, testDone) => { t.plan(3) const fastify = Fastify() fastify.addHook('onRoute', () => { - t.pass() + t.assert.ok('should pass') }) fastify.get('/', function (req, reply) { @@ -822,15 +851,16 @@ test('onRoute hook should not be called when it registered after route', t => { }) fastify.addHook('onRoute', () => { - t.fail('should not be called') + t.assert.fail('should not be called') }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onResponse hook should log request error', t => { +test('onResponse hook should log request error', (t, testDone) => { t.plan(4) let fastify = null @@ -843,12 +873,12 @@ test('onResponse hook should log request error', t => { } }) } catch (e) { - t.fail() + t.assert.fail() } logStream.once('data', line => { - t.equal(line.msg, 'request errored') - t.equal(line.level, 50) + t.assert.strictEqual(line.msg, 'request errored') + t.assert.strictEqual(line.level, 50) }) fastify.addHook('onResponse', (request, reply, done) => { @@ -860,18 +890,19 @@ test('onResponse hook should log request error', t => { }) fastify.inject('/root', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('onResponse hook should support encapsulation / 1', t => { +test('onResponse hook should support encapsulation / 1', (t, testDone) => { t.plan(5) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.addHook('onResponse', (request, reply, done) => { - t.equal(reply.plugin, true) + t.assert.strictEqual(reply.plugin, true) done() }) @@ -888,17 +919,18 @@ test('onResponse hook should support encapsulation / 1', t => { }) fastify.inject('/root', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) }) fastify.inject('/plugin', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('onResponse hook should support encapsulation / 2', t => { +test('onResponse hook should support encapsulation / 2', (t, testDone) => { t.plan(3) const fastify = Fastify() let pluginInstance @@ -912,21 +944,22 @@ test('onResponse hook should support encapsulation / 2', t => { }) fastify.ready(err => { - t.error(err) - t.equal(fastify[symbols.kHooks].onResponse.length, 1) - t.equal(pluginInstance[symbols.kHooks].onResponse.length, 2) + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].onResponse.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].onResponse.length, 2) + testDone() }) }) -test('onResponse hook should support encapsulation / 3', t => { +test('onResponse hook should support encapsulation / 3', (t, testDone) => { t.plan(16) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('onResponse', function (request, reply, done) { - t.ok(this.hello) - t.ok('onResponse called') + t.assert.ok(this.hello) + t.assert.ok('onResponse called') done() }) @@ -937,9 +970,9 @@ test('onResponse hook should support encapsulation / 3', t => { fastify.register((instance, opts, done) => { instance.decorate('hello2', 'world') instance.addHook('onResponse', function (request, reply, done) { - t.ok(this.hello) - t.ok(this.hello2) - t.ok('onResponse called') + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok('onResponse called') done() }) @@ -951,31 +984,34 @@ test('onResponse hook should support encapsulation / 3', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) - sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('onSend hook should support encapsulation / 1', t => { +test('onSend hook should support encapsulation / 1', (t, testDone) => { t.plan(3) const fastify = Fastify() let pluginInstance @@ -989,21 +1025,22 @@ test('onSend hook should support encapsulation / 1', t => { }) fastify.ready(err => { - t.error(err) - t.equal(fastify[symbols.kHooks].onSend.length, 1) - t.equal(pluginInstance[symbols.kHooks].onSend.length, 2) + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].onSend.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].onSend.length, 2) + testDone() }) }) -test('onSend hook should support encapsulation / 2', t => { +test('onSend hook should support encapsulation / 2', (t, testDone) => { t.plan(16) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('onSend', function (request, reply, thePayload, done) { - t.ok(this.hello) - t.ok('onSend called') + t.assert.ok(this.hello) + t.assert.ok('onSend called') done() }) @@ -1014,9 +1051,9 @@ test('onSend hook should support encapsulation / 2', t => { fastify.register((instance, opts, done) => { instance.decorate('hello2', 'world') instance.addHook('onSend', function (request, reply, thePayload, done) { - t.ok(this.hello) - t.ok(this.hello2) - t.ok('onSend called') + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok('onSend called') done() }) @@ -1028,31 +1065,35 @@ test('onSend hook should support encapsulation / 2', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('onSend hook is called after payload is serialized and headers are set', t => { +test('onSend hook is called after payload is serialized and headers are set', (t, testDone) => { t.plan(30) const fastify = Fastify() @@ -1060,8 +1101,8 @@ test('onSend hook is called after payload is serialized and headers are set', t const thePayload = { hello: 'world' } instance.addHook('onSend', function (request, reply, payload, done) { - t.same(JSON.parse(payload), thePayload) - t.equal(reply[symbols.kReplyHeaders]['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(payload), thePayload) + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/json; charset=utf-8') done() }) @@ -1074,8 +1115,8 @@ test('onSend hook is called after payload is serialized and headers are set', t fastify.register((instance, opts, done) => { instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(payload, 'some text') - t.equal(reply[symbols.kReplyHeaders]['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(payload, 'some text') + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'text/plain; charset=utf-8') done() }) @@ -1090,8 +1131,8 @@ test('onSend hook is called after payload is serialized and headers are set', t const thePayload = Buffer.from('buffer payload') instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(payload, thePayload) - t.equal(reply[symbols.kReplyHeaders]['content-type'], 'application/octet-stream') + t.assert.strictEqual(payload, thePayload) + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/octet-stream') done() }) @@ -1112,8 +1153,8 @@ test('onSend hook is called after payload is serialized and headers are set', t }) instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(payload, thePayload) - t.equal(reply[symbols.kReplyHeaders]['content-type'], 'application/octet-stream') + t.assert.strictEqual(payload, thePayload) + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/octet-stream') done() }) @@ -1129,8 +1170,8 @@ test('onSend hook is called after payload is serialized and headers are set', t const serializedPayload = 'serialized' instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(payload, serializedPayload) - t.equal(reply[symbols.kReplyHeaders]['content-type'], 'text/custom') + t.assert.strictEqual(payload, serializedPayload) + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'text/custom') done() }) @@ -1144,58 +1185,65 @@ test('onSend hook is called after payload is serialized and headers are set', t done() }) + const completion = waitForCb({ steps: 5 }) fastify.inject({ method: 'GET', url: '/json' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) - t.equal(res.headers['content-length'], '17') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.headers['content-length'], '17') + completion.stepIn() }) fastify.inject({ method: 'GET', url: '/text' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.payload, 'some text') - t.equal(res.headers['content-length'], '9') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.payload, 'some text') + t.assert.strictEqual(res.headers['content-length'], '9') + completion.stepIn() }) fastify.inject({ method: 'GET', url: '/buffer' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.payload, 'buffer payload') - t.equal(res.headers['content-length'], '14') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.payload, 'buffer payload') + t.assert.strictEqual(res.headers['content-length'], '14') + completion.stepIn() }) fastify.inject({ method: 'GET', url: '/stream' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.payload, 'stream payload') - t.equal(res.headers['transfer-encoding'], 'chunked') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.payload, 'stream payload') + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + completion.stepIn() }) fastify.inject({ method: 'GET', url: '/custom-serializer' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(res.payload, 'serialized') - t.equal(res.headers['content-type'], 'text/custom') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.payload, 'serialized') + t.assert.strictEqual(res.headers['content-type'], 'text/custom') + completion.stepIn() }) + completion.patience.then(testDone) }) -test('modify payload', t => { +test('modify payload', (t, testDone) => { t.plan(10) const fastify = Fastify() const payload = { hello: 'world' } @@ -1203,21 +1251,21 @@ test('modify payload', t => { const anotherPayload = '"winter is coming"' fastify.addHook('onSend', function (request, reply, thePayload, done) { - t.ok('onSend called') - t.same(JSON.parse(thePayload), payload) + t.assert.ok('onSend called') + t.assert.deepStrictEqual(JSON.parse(thePayload), payload) thePayload = thePayload.replace('world', 'modified') done(null, thePayload) }) fastify.addHook('onSend', function (request, reply, thePayload, done) { - t.ok('onSend called') - t.same(JSON.parse(thePayload), modifiedPayload) + t.assert.ok('onSend called') + t.assert.deepStrictEqual(JSON.parse(thePayload), modifiedPayload) done(null, anotherPayload) }) fastify.addHook('onSend', function (request, reply, thePayload, done) { - t.ok('onSend called') - t.equal(thePayload, anotherPayload) + t.assert.ok('onSend called') + t.assert.strictEqual(thePayload, anotherPayload) done() }) @@ -1229,19 +1277,20 @@ test('modify payload', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.payload, anotherPayload) - t.equal(res.statusCode, 200) - t.equal(res.headers['content-length'], '18') + t.assert.ifError(err) + t.assert.strictEqual(res.payload, anotherPayload) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '18') + testDone() }) }) -test('clear payload', t => { +test('clear payload', (t, testDone) => { t.plan(6) const fastify = Fastify() fastify.addHook('onSend', function (request, reply, payload, done) { - t.ok('onSend called') + t.assert.ok('onSend called') reply.code(304) done(null, null) }) @@ -1254,25 +1303,26 @@ test('clear payload', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 304) - t.equal(res.payload, '') - t.equal(res.headers['content-length'], undefined) - t.equal(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 304) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + testDone() }) }) -test('onSend hook throws', t => { +test('onSend hook throws', (t, testDone) => { t.plan(11) const Fastify = proxyquire('..', { './lib/schemas.js': { getSchemaSerializer: (param1, param2, param3) => { - t.equal(param3, 'application/json; charset=utf-8', 'param3 should be "application/json; charset=utf-8"') + t.assert.strictEqual(param3, 'application/json; charset=utf-8', 'param3 should be "application/json; charset=utf-8"') } } }) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('onSend', function (request, reply, payload, done) { if (request.raw.method === 'DELETE') { done(new Error('some error')) @@ -1323,42 +1373,48 @@ test('onSend hook throws', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 4 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) sget({ method: 'POST', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + completion.stepIn() }) sget({ method: 'DELETE', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + completion.stepIn() }) sget({ method: 'PUT', url: 'http://127.0.0.1:' + fastify.server.address().port }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('onSend hook should receive valid request and reply objects if onRequest hook fails', t => { +test('onSend hook should receive valid request and reply objects if onRequest hook fails', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -1370,8 +1426,8 @@ test('onSend hook should receive valid request and reply objects if onRequest ho }) fastify.addHook('onSend', function (request, reply, payload, done) { - t.equal(request.testDecorator, 'testDecoratorVal') - t.equal(reply.testDecorator, 'testDecoratorVal') + t.assert.strictEqual(request.testDecorator, 'testDecoratorVal') + t.assert.strictEqual(reply.testDecorator, 'testDecoratorVal') done() }) @@ -1383,12 +1439,13 @@ test('onSend hook should receive valid request and reply objects if onRequest ho method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + testDone() }) }) -test('onSend hook should receive valid request and reply objects if a custom content type parser fails', t => { +test('onSend hook should receive valid request and reply objects if a custom content type parser fails', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -1400,8 +1457,8 @@ test('onSend hook should receive valid request and reply objects if a custom con }) fastify.addHook('onSend', function (request, reply, payload, done) { - t.equal(request.testDecorator, 'testDecoratorVal') - t.equal(reply.testDecorator, 'testDecoratorVal') + t.assert.strictEqual(request.testDecorator, 'testDecoratorVal') + t.assert.strictEqual(reply.testDecorator, 'testDecoratorVal') done() }) @@ -1414,12 +1471,13 @@ test('onSend hook should receive valid request and reply objects if a custom con url: '/', payload: 'body' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + testDone() }) }) -test('Content-Length header should be updated if onSend hook modifies the payload', t => { +test('Content-Length header should be updated if onSend hook modifies the payload', (t, testDone) => { t.plan(2) const instance = Fastify() @@ -1435,36 +1493,37 @@ test('Content-Length header should be updated if onSend hook modifies the payloa method: 'GET', url: '/' }, (err, res) => { - t.error(err) + t.assert.ifError(err) const payloadLength = Buffer.byteLength(res.body) const contentLength = Number(res.headers['content-length']) - t.equal(payloadLength, contentLength) + t.assert.strictEqual(payloadLength, contentLength) + testDone() }) }) -test('cannot add hook after binding', t => { - t.plan(2) +test('cannot add hook after binding', (t, testDone) => { + t.plan(1) const instance = Fastify() - t.teardown(() => instance.close()) + t.after(() => instance.close()) instance.get('/', function (request, reply) { reply.send({ hello: 'world' }) }) instance.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) try { instance.addHook('onRequest', () => {}) - t.fail() + t.assert.fail() } catch (e) { - t.pass() + testDone() } }) }) -test('onRequest hooks should be able to block a request', t => { +test('onRequest hooks should be able to block a request', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -1474,38 +1533,39 @@ test('onRequest hooks should be able to block a request', t => { }) fastify.addHook('onRequest', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('preHandler', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preValidation hooks should be able to block a request', t => { +test('preValidation hooks should be able to block a request', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -1515,38 +1575,39 @@ test('preValidation hooks should be able to block a request', t => { }) fastify.addHook('preValidation', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('preHandler', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preValidation hooks should be able to change request body before validation', t => { +test('preValidation hooks should be able to change request body before validation', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -1575,7 +1636,7 @@ test('preValidation hooks should be able to change request body before validatio } }, (req, reply) => { - t.pass() + t.assert.ok('should pass') reply.status(200).send('hello') } ) @@ -1587,13 +1648,14 @@ test('preValidation hooks should be able to change request body before validatio message: Buffer.from(JSON.stringify({ foo: 'example', bar: 1 })).toString('base64') } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preParsing hooks should be able to block a request', t => { +test('preParsing hooks should be able to block a request', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -1603,38 +1665,39 @@ test('preParsing hooks should be able to block a request', t => { }) fastify.addHook('preParsing', (req, reply, payload, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('preHandler', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preHandler hooks should be able to block a request', t => { +test('preHandler hooks should be able to block a request', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -1644,34 +1707,35 @@ test('preHandler hooks should be able to block a request', t => { }) fastify.addHook('preHandler', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.equal(payload, 'hello') + t.assert.strictEqual(payload, 'hello') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('onRequest hooks should be able to block a request (last hook)', t => { +test('onRequest hooks should be able to block a request (last hook)', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -1681,34 +1745,35 @@ test('onRequest hooks should be able to block a request (last hook)', t => { }) fastify.addHook('preHandler', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preHandler hooks should be able to block a request (last hook)', t => { +test('preHandler hooks should be able to block a request (last hook)', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -1718,30 +1783,31 @@ test('preHandler hooks should be able to block a request (last hook)', t => { }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.equal(payload, 'hello') + t.assert.strictEqual(payload, 'hello') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('preParsing hooks should handle errors', t => { +test('preParsing hooks should handle errors', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -1760,13 +1826,14 @@ test('preParsing hooks should handle errors', t => { url: '/', payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 501) - t.same(JSON.parse(res.payload), { error: 'Not Implemented', message: 'kaboom', statusCode: 501 }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 501) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Implemented', message: 'kaboom', statusCode: 501 }) + testDone() }) }) -test('onRequest respond with a stream', t => { +test('onRequest respond with a stream', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -1778,42 +1845,43 @@ test('onRequest respond with a stream', t => { }) fastify.addHook('onRequest', (req, res, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('preHandler', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('preHandler respond with a stream', t => { +test('preHandler respond with a stream', (t, testDone) => { t.plan(7) const fastify = Fastify() fastify.addHook('onRequest', (req, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) @@ -1825,46 +1893,47 @@ test('preHandler respond with a stream', t => { const stream = fs.createReadStream(__filename, 'utf8') reply.send(stream) reply.raw.once('finish', () => { - t.equal(order.shift(), 2) + t.assert.strictEqual(order.shift(), 2) done() }) }) fastify.addHook('preHandler', (req, reply, done) => { - t.fail('this should not be called') + t.assert.fail('this should not be called') }) fastify.addHook('onSend', (req, reply, payload, done) => { - t.equal(order.shift(), 1) - t.equal(typeof payload.pipe, 'function') + t.assert.strictEqual(order.shift(), 1) + t.assert.strictEqual(typeof payload.pipe, 'function') done() }) fastify.addHook('onResponse', (request, reply, done) => { - t.ok('called') + t.assert.ok('called') done() }) fastify.get('/', function (request, reply) { - t.fail('we should not be here') + t.assert.fail('we should not be here') }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('Register an hook after a plugin inside a plugin', t => { +test('Register an hook after a plugin inside a plugin', (t, testDone) => { t.plan(6) const fastify = Fastify() fastify.register(fp(function (instance, opts, done) { instance.addHook('preHandler', function (req, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) @@ -1877,12 +1946,12 @@ test('Register an hook after a plugin inside a plugin', t => { fastify.register(fp(function (instance, opts, done) { instance.addHook('preHandler', function (req, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) instance.addHook('preHandler', function (req, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) @@ -1893,25 +1962,26 @@ test('Register an hook after a plugin inside a plugin', t => { url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('Register an hook after a plugin inside a plugin (with preHandler option)', t => { +test('Register an hook after a plugin inside a plugin (with preHandler option)', (t, testDone) => { t.plan(7) const fastify = Fastify() fastify.register(fp(function (instance, opts, done) { instance.addHook('preHandler', function (req, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) instance.get('/', { preHandler: (req, reply, done) => { - t.ok('called') + t.assert.ok('called') done() } }, function (request, reply) { @@ -1923,12 +1993,12 @@ test('Register an hook after a plugin inside a plugin (with preHandler option)', fastify.register(fp(function (instance, opts, done) { instance.addHook('preHandler', function (req, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) instance.addHook('preHandler', function (req, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) @@ -1939,13 +2009,14 @@ test('Register an hook after a plugin inside a plugin (with preHandler option)', url: '/', method: 'GET' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('Register hooks inside a plugin after an encapsulated plugin', t => { +test('Register hooks inside a plugin after an encapsulated plugin', (t, testDone) => { t.plan(7) const fastify = Fastify() @@ -1959,22 +2030,22 @@ test('Register hooks inside a plugin after an encapsulated plugin', t => { fastify.register(fp(function (instance, opts, done) { instance.addHook('onRequest', function (req, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) instance.addHook('preHandler', function (request, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) instance.addHook('onSend', function (request, reply, payload, done) { - t.ok('called') + t.assert.ok('called') done() }) instance.addHook('onResponse', function (request, reply, done) { - t.ok('called') + t.assert.ok('called') done() }) @@ -1982,31 +2053,32 @@ test('Register hooks inside a plugin after an encapsulated plugin', t => { })) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('onRequest hooks should run in the order in which they are defined', t => { +test('onRequest hooks should run in the order in which they are defined', (t, testDone) => { t.plan(9) const fastify = Fastify() fastify.register(function (instance, opts, done) { instance.addHook('onRequest', function (req, reply, done) { - t.equal(req.previous, undefined) + t.assert.strictEqual(req.previous, undefined) req.previous = 1 done() }) instance.get('/', function (request, reply) { - t.equal(request.previous, 5) + t.assert.strictEqual(request.previous, 5) reply.send({ hello: 'world' }) }) instance.register(fp(function (i, opts, done) { i.addHook('onRequest', function (req, reply, done) { - t.equal(req.previous, 1) + t.assert.strictEqual(req.previous, 1) req.previous = 2 done() }) @@ -2018,14 +2090,14 @@ test('onRequest hooks should run in the order in which they are defined', t => { fastify.register(fp(function (instance, opts, done) { instance.addHook('onRequest', function (req, reply, done) { - t.equal(req.previous, 2) + t.assert.strictEqual(req.previous, 2) req.previous = 3 done() }) instance.register(fp(function (i, opts, done) { i.addHook('onRequest', function (req, reply, done) { - t.equal(req.previous, 3) + t.assert.strictEqual(req.previous, 3) req.previous = 4 done() }) @@ -2033,7 +2105,7 @@ test('onRequest hooks should run in the order in which they are defined', t => { })) instance.addHook('onRequest', function (req, reply, done) { - t.equal(req.previous, 4) + t.assert.strictEqual(req.previous, 4) req.previous = 5 done() }) @@ -2042,31 +2114,32 @@ test('onRequest hooks should run in the order in which they are defined', t => { })) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('preHandler hooks should run in the order in which they are defined', t => { +test('preHandler hooks should run in the order in which they are defined', (t, testDone) => { t.plan(9) const fastify = Fastify() fastify.register(function (instance, opts, done) { instance.addHook('preHandler', function (request, reply, done) { - t.equal(request.previous, undefined) + t.assert.strictEqual(request.previous, undefined) request.previous = 1 done() }) instance.get('/', function (request, reply) { - t.equal(request.previous, 5) + t.assert.strictEqual(request.previous, 5) reply.send({ hello: 'world' }) }) instance.register(fp(function (i, opts, done) { i.addHook('preHandler', function (request, reply, done) { - t.equal(request.previous, 1) + t.assert.strictEqual(request.previous, 1) request.previous = 2 done() }) @@ -2078,14 +2151,14 @@ test('preHandler hooks should run in the order in which they are defined', t => fastify.register(fp(function (instance, opts, done) { instance.addHook('preHandler', function (request, reply, done) { - t.equal(request.previous, 2) + t.assert.strictEqual(request.previous, 2) request.previous = 3 done() }) instance.register(fp(function (i, opts, done) { i.addHook('preHandler', function (request, reply, done) { - t.equal(request.previous, 3) + t.assert.strictEqual(request.previous, 3) request.previous = 4 done() }) @@ -2093,7 +2166,7 @@ test('preHandler hooks should run in the order in which they are defined', t => })) instance.addHook('preHandler', function (request, reply, done) { - t.equal(request.previous, 4) + t.assert.strictEqual(request.previous, 4) request.previous = 5 done() }) @@ -2102,19 +2175,20 @@ test('preHandler hooks should run in the order in which they are defined', t => })) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('onSend hooks should run in the order in which they are defined', t => { +test('onSend hooks should run in the order in which they are defined', (t, testDone) => { t.plan(8) const fastify = Fastify() fastify.register(function (instance, opts, done) { instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(request.previous, undefined) + t.assert.strictEqual(request.previous, undefined) request.previous = 1 done() }) @@ -2125,7 +2199,7 @@ test('onSend hooks should run in the order in which they are defined', t => { instance.register(fp(function (i, opts, done) { i.addHook('onSend', function (request, reply, payload, done) { - t.equal(request.previous, 1) + t.assert.strictEqual(request.previous, 1) request.previous = 2 done() }) @@ -2137,14 +2211,14 @@ test('onSend hooks should run in the order in which they are defined', t => { fastify.register(fp(function (instance, opts, done) { instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(request.previous, 2) + t.assert.strictEqual(request.previous, 2) request.previous = 3 done() }) instance.register(fp(function (i, opts, done) { i.addHook('onSend', function (request, reply, payload, done) { - t.equal(request.previous, 3) + t.assert.strictEqual(request.previous, 3) request.previous = 4 done() }) @@ -2152,7 +2226,7 @@ test('onSend hooks should run in the order in which they are defined', t => { })) instance.addHook('onSend', function (request, reply, payload, done) { - t.equal(request.previous, 4) + t.assert.strictEqual(request.previous, 4) done(null, '5') }) @@ -2160,19 +2234,20 @@ test('onSend hooks should run in the order in which they are defined', t => { })) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), 5) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), 5) + testDone() }) }) -test('onResponse hooks should run in the order in which they are defined', t => { +test('onResponse hooks should run in the order in which they are defined', (t, testDone) => { t.plan(8) const fastify = Fastify() fastify.register(function (instance, opts, done) { instance.addHook('onResponse', function (request, reply, done) { - t.equal(reply.previous, undefined) + t.assert.strictEqual(reply.previous, undefined) reply.previous = 1 done() }) @@ -2183,7 +2258,7 @@ test('onResponse hooks should run in the order in which they are defined', t => instance.register(fp(function (i, opts, done) { i.addHook('onResponse', function (request, reply, done) { - t.equal(reply.previous, 1) + t.assert.strictEqual(reply.previous, 1) reply.previous = 2 done() }) @@ -2195,14 +2270,14 @@ test('onResponse hooks should run in the order in which they are defined', t => fastify.register(fp(function (instance, opts, done) { instance.addHook('onResponse', function (request, reply, done) { - t.equal(reply.previous, 2) + t.assert.strictEqual(reply.previous, 2) reply.previous = 3 done() }) instance.register(fp(function (i, opts, done) { i.addHook('onResponse', function (request, reply, done) { - t.equal(reply.previous, 3) + t.assert.strictEqual(reply.previous, 3) reply.previous = 4 done() }) @@ -2210,7 +2285,7 @@ test('onResponse hooks should run in the order in which they are defined', t => })) instance.addHook('onResponse', function (request, reply, done) { - t.equal(reply.previous, 4) + t.assert.strictEqual(reply.previous, 4) done() }) @@ -2218,13 +2293,14 @@ test('onResponse hooks should run in the order in which they are defined', t => })) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.same(JSON.parse(res.payload), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() }) }) -test('onRequest, preHandler, and onResponse hooks that resolve to a value do not cause an error', t => { +test('onRequest, preHandler, and onResponse hooks that resolve to a value do not cause an error', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -2243,13 +2319,14 @@ test('onRequest, preHandler, and onResponse hooks that resolve to a value do not }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('If a response header has been set inside an hook it should not be overwritten by the final response handler', t => { +test('If a response header has been set inside an hook it should not be overwritten by the final response handler', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -2263,15 +2340,16 @@ test('If a response header has been set inside an hook it should not be overwrit }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.headers['x-custom-header'], 'hello') - t.equal(res.headers['content-type'], 'text/plain; charset=utf-8') - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.headers['x-custom-header'], 'hello') + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('If the content type has been set inside an hook it should not be changed', t => { +test('If the content type has been set inside an hook it should not be changed', (t, testDone) => { t.plan(5) const fastify = Fastify() @@ -2281,27 +2359,28 @@ test('If the content type has been set inside an hook it should not be changed', }) fastify.get('/', (request, reply) => { - t.ok(reply[symbols.kReplyHeaders]['content-type']) + t.assert.ok(reply[symbols.kReplyHeaders]['content-type']) reply.send('hello') }) fastify.inject('/', (err, res) => { - t.error(err) - t.equal(res.headers['content-type'], 'text/html') - t.equal(res.statusCode, 200) - t.equal(res.payload, 'hello') + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'text/html') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() }) }) -test('request in onRequest, preParsing, preValidation and onResponse', t => { +test('request in onRequest, preParsing, preValidation and onResponse', (t, testDone) => { t.plan(18) const fastify = Fastify() fastify.addHook('onRequest', function (request, reply, done) { - t.same(request.body, undefined) - t.same(request.query, { key: 'value' }) - t.same(request.params, { greeting: 'hello' }) - t.same(request.headers, { + t.assert.deepStrictEqual(request.body, undefined) + t.assert.deepStrictEqual(request.query.key, 'value') + t.assert.deepStrictEqual(request.params.greeting, 'hello') + t.assert.deepStrictEqual(request.headers, { 'content-length': '17', 'content-type': 'application/json', host: 'localhost:80', @@ -2312,10 +2391,10 @@ test('request in onRequest, preParsing, preValidation and onResponse', t => { }) fastify.addHook('preParsing', function (request, reply, payload, done) { - t.same(request.body, undefined) - t.same(request.query, { key: 'value' }) - t.same(request.params, { greeting: 'hello' }) - t.same(request.headers, { + t.assert.deepStrictEqual(request.body, undefined) + t.assert.deepStrictEqual(request.query.key, 'value') + t.assert.deepStrictEqual(request.params.greeting, 'hello') + t.assert.deepStrictEqual(request.headers, { 'content-length': '17', 'content-type': 'application/json', host: 'localhost:80', @@ -2326,10 +2405,10 @@ test('request in onRequest, preParsing, preValidation and onResponse', t => { }) fastify.addHook('preValidation', function (request, reply, done) { - t.same(request.body, { hello: 'world' }) - t.same(request.query, { key: 'value' }) - t.same(request.params, { greeting: 'hello' }) - t.same(request.headers, { + t.assert.deepStrictEqual(request.body, { hello: 'world' }) + t.assert.deepStrictEqual(request.query.key, 'value') + t.assert.deepStrictEqual(request.params.greeting, 'hello') + t.assert.deepStrictEqual(request.headers, { 'content-length': '17', 'content-type': 'application/json', host: 'localhost:80', @@ -2340,10 +2419,10 @@ test('request in onRequest, preParsing, preValidation and onResponse', t => { }) fastify.addHook('onResponse', function (request, reply, done) { - t.same(request.body, { hello: 'world' }) - t.same(request.query, { key: 'value' }) - t.same(request.params, { greeting: 'hello' }) - t.same(request.headers, { + t.assert.deepStrictEqual(request.body, { hello: 'world' }) + t.assert.deepStrictEqual(request.query.key, 'value') + t.assert.deepStrictEqual(request.params.greeting, 'hello') + t.assert.deepStrictEqual(request.headers, { 'content-length': '17', 'content-type': 'application/json', host: 'localhost:80', @@ -2363,18 +2442,19 @@ test('request in onRequest, preParsing, preValidation and onResponse', t => { headers: { 'x-custom': 'hello' }, payload: { hello: 'world' } }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() }) }) -test('preValidation hook should support encapsulation / 1', t => { +test('preValidation hook should support encapsulation / 1', (t, testDone) => { t.plan(5) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.addHook('preValidation', (req, reply, done) => { - t.equal(req.raw.url, '/plugin') + t.assert.strictEqual(req.raw.url, '/plugin') done() }) @@ -2390,17 +2470,17 @@ test('preValidation hook should support encapsulation / 1', t => { }) fastify.inject('/root', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - }) - - fastify.inject('/plugin', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + fastify.inject('/plugin', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) }) }) -test('preValidation hook should support encapsulation / 2', t => { +test('preValidation hook should support encapsulation / 2', (t, testDone) => { t.plan(3) const fastify = Fastify() let pluginInstance @@ -2414,21 +2494,22 @@ test('preValidation hook should support encapsulation / 2', t => { }) fastify.ready(err => { - t.error(err) - t.equal(fastify[symbols.kHooks].preValidation.length, 1) - t.equal(pluginInstance[symbols.kHooks].preValidation.length, 2) + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].preValidation.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].preValidation.length, 2) + testDone() }) }) -test('preValidation hook should support encapsulation / 3', t => { +test('preValidation hook should support encapsulation / 3', (t, testDone) => { t.plan(20) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('preValidation', function (req, reply, done) { - t.ok(this.hello) - t.ok(this.hello2) + t.assert.ok(this.hello) + t.assert.ok(this.hello2) req.first = true done() }) @@ -2436,24 +2517,24 @@ test('preValidation hook should support encapsulation / 3', t => { fastify.decorate('hello2', 'world') fastify.get('/first', (req, reply) => { - t.ok(req.first) - t.notOk(req.second) + t.assert.ok(req.first) + t.assert.ok(!req.second) reply.send({ hello: 'world' }) }) fastify.register((instance, opts, done) => { instance.decorate('hello3', 'world') instance.addHook('preValidation', function (req, reply, done) { - t.ok(this.hello) - t.ok(this.hello2) - t.ok(this.hello3) + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok(this.hello3) req.second = true done() }) instance.get('/second', (req, reply) => { - t.ok(req.first) - t.ok(req.second) + t.assert.ok(req.first) + t.assert.ok(req.second) reply.send({ hello: 'world' }) }) @@ -2461,31 +2542,34 @@ test('preValidation hook should support encapsulation / 3', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) - sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('onError hook', t => { +test('onError hook', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -2493,7 +2577,7 @@ test('onError hook', t => { const err = new Error('kaboom') fastify.addHook('onError', (request, reply, error, done) => { - t.match(error, err) + t.assert.deepStrictEqual(error, err) done() }) @@ -2505,16 +2589,17 @@ test('onError hook', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', message: 'kaboom', statusCode: 500 }) + testDone() }) }) -test('reply.send should throw if called inside the onError hook', t => { +test('reply.send should throw if called inside the onError hook', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -2524,9 +2609,9 @@ test('reply.send should throw if called inside the onError hook', t => { fastify.addHook('onError', (request, reply, error, done) => { try { reply.send() - t.fail('Should throw') + t.assert.fail('Should throw') } catch (err) { - t.equal(err.code, 'FST_ERR_SEND_INSIDE_ONERR') + t.assert.strictEqual(err.code, 'FST_ERR_SEND_INSIDE_ONERR') } done() }) @@ -2539,57 +2624,55 @@ test('reply.send should throw if called inside the onError hook', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', message: 'kaboom', statusCode: 500 }) + testDone() }) }) -test('onError hook with setErrorHandler', t => { - t.test('Send error', t => { - t.plan(3) +test('onError hook with setErrorHandler', (t, testDone) => { + t.plan(3) - const fastify = Fastify() + const fastify = Fastify() - const external = new Error('ouch') - const internal = new Error('kaboom') + const external = new Error('ouch') + const internal = new Error('kaboom') - fastify.setErrorHandler((_, req, reply) => { - reply.send(external) - }) + fastify.setErrorHandler((_, req, reply) => { + reply.send(external) + }) - fastify.addHook('onError', (request, reply, error, done) => { - t.match(error, internal) - done() - }) + fastify.addHook('onError', (request, reply, error, done) => { + t.assert.deepStrictEqual(error, internal) + done() + }) - fastify.get('/', (req, reply) => { - reply.send(internal) - }) + fastify.get('/', (req, reply) => { + reply.send(internal) + }) - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - t.same(JSON.parse(res.payload), { - error: 'Internal Server Error', - message: 'ouch', - statusCode: 500 - }) + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + message: 'ouch', + statusCode: 500 }) + testDone() }) - - t.end() }) -test('preParsing hook should run before parsing and be able to modify the payload', t => { +test('preParsing hook should run before parsing and be able to modify the payload', (t, testDone) => { t.plan(5) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('preParsing', function (req, reply, payload, done) { const modified = new stream.Readable() @@ -2608,7 +2691,7 @@ test('preParsing hook should run before parsing and be able to modify the payloa }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -2616,18 +2699,19 @@ test('preParsing hook should run before parsing and be able to modify the payloa body: { hello: 'world' }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + JSON.stringify(body).length) - t.same(body, { hello: 'another world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + JSON.stringify(body).length) + t.assert.deepStrictEqual(body, { hello: 'another world' }) + testDone() }) }) }) -test('preParsing hooks should run in the order in which they are defined', t => { +test('preParsing hooks should run in the order in which they are defined', (t, testDone) => { t.plan(5) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('preParsing', function (req, reply, payload, done) { const modified = new stream.Readable() @@ -2651,7 +2735,7 @@ test('preParsing hooks should run in the order in which they are defined', t => }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'POST', @@ -2659,18 +2743,19 @@ test('preParsing hooks should run in the order in which they are defined', t => body: { hello: 'world' }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + JSON.stringify(body).length) - t.same(body, { hello: 'another world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + JSON.stringify(body).length) + t.assert.deepStrictEqual(body, { hello: 'another world' }) + testDone() }) }) }) -test('preParsing hooks should support encapsulation', t => { +test('preParsing hooks should support encapsulation', (t, testDone) => { t.plan(9) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('preParsing', function (req, reply, payload, done) { const modified = new stream.Readable() @@ -2701,41 +2786,44 @@ test('preParsing hooks should support encapsulation', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'POST', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first', body: { hello: 'world' }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + JSON.stringify(body).length) - t.same(body, { hello: 'another world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + JSON.stringify(body).length) + t.assert.deepStrictEqual(body, { hello: 'another world' }) + completion.stepIn() }) - sget({ method: 'POST', url: 'http://127.0.0.1:' + fastify.server.address().port + '/second', body: { hello: 'world' }, json: true }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + JSON.stringify(body).length) - t.same(body, { hello: 'encapsulated world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + JSON.stringify(body).length) + t.assert.deepStrictEqual(body, { hello: 'encapsulated world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('preParsing hook should support encapsulation / 1', t => { +test('preParsing hook should support encapsulation / 1', (t, testDone) => { t.plan(5) const fastify = Fastify() fastify.register((instance, opts, done) => { instance.addHook('preParsing', (req, reply, payload, done) => { - t.equal(req.raw.url, '/plugin') + t.assert.strictEqual(req.raw.url, '/plugin') done() }) @@ -2751,17 +2839,17 @@ test('preParsing hook should support encapsulation / 1', t => { }) fastify.inject('/root', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) - }) - - fastify.inject('/plugin', (err, res) => { - t.error(err) - t.equal(res.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + fastify.inject('/plugin', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) }) }) -test('preParsing hook should support encapsulation / 2', t => { +test('preParsing hook should support encapsulation / 2', (t, testDone) => { t.plan(3) const fastify = Fastify() let pluginInstance @@ -2775,21 +2863,22 @@ test('preParsing hook should support encapsulation / 2', t => { }) fastify.ready(err => { - t.error(err) - t.equal(fastify[symbols.kHooks].preParsing.length, 1) - t.equal(pluginInstance[symbols.kHooks].preParsing.length, 2) + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].preParsing.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].preParsing.length, 2) + testDone() }) }) -test('preParsing hook should support encapsulation / 3', t => { +test('preParsing hook should support encapsulation / 3', (t, testDone) => { t.plan(20) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') fastify.addHook('preParsing', function (req, reply, payload, done) { - t.ok(this.hello) - t.ok(this.hello2) + t.assert.ok(this.hello) + t.assert.ok(this.hello2) req.first = true done() }) @@ -2797,24 +2886,24 @@ test('preParsing hook should support encapsulation / 3', t => { fastify.decorate('hello2', 'world') fastify.get('/first', (req, reply) => { - t.ok(req.first) - t.notOk(req.second) + t.assert.ok(req.first) + t.assert.ok(!req.second) reply.send({ hello: 'world' }) }) fastify.register((instance, opts, done) => { instance.decorate('hello3', 'world') instance.addHook('preParsing', function (req, reply, payload, done) { - t.ok(this.hello) - t.ok(this.hello2) - t.ok(this.hello3) + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok(this.hello3) req.second = true done() }) instance.get('/second', (req, reply) => { - t.ok(req.first) - t.ok(req.second) + t.assert.ok(req.first) + t.assert.ok(req.second) reply.send({ hello: 'world' }) }) @@ -2822,34 +2911,37 @@ test('preParsing hook should support encapsulation / 3', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) - sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('preSerialization hook should run before serialization and be able to modify the payload', t => { +test('preSerialization hook should run before serialization and be able to modify the payload', (t, testDone) => { t.plan(5) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { payload.hello += '1' @@ -2884,30 +2976,31 @@ test('preSerialization hook should run before serialization and be able to modif }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world1', world: 'ok' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world1', world: 'ok' }) + testDone() }) }) }) -test('preSerialization hook should be able to throw errors which are validated against schema response', t => { +test('preSerialization hook should be able to throw errors which are validated against schema response', (t, testDone) => { const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { done(new Error('preSerialization aborted')) }) fastify.setErrorHandler((err, request, reply) => { - t.equal(err.message, 'preSerialization aborted') + t.assert.strictEqual(err.message, 'preSerialization aborted') err.world = 'error' reply.send(err) }) @@ -2935,32 +3028,32 @@ test('preSerialization hook should be able to throw errors which are validated a }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { world: 'error' }) - t.end() + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { world: 'error' }) + testDone() }) }) }) -test('preSerialization hook which returned error should still run onError hooks', t => { +test('preSerialization hook which returned error should still run onError hooks', (t, testDone) => { t.plan(4) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { done(new Error('preSerialization aborted')) }) fastify.addHook('onError', function (req, reply, payload, done) { - t.pass() + t.assert.ok('should pass') done() }) @@ -2969,22 +3062,23 @@ test('preSerialization hook which returned error should still run onError hooks' }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 500) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 500) + testDone() }) }) }) -test('preSerialization hooks should run in the order in which they are defined', t => { +test('preSerialization hooks should run in the order in which they are defined', (t, testDone) => { t.plan(5) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { payload.hello += '2' @@ -3003,24 +3097,25 @@ test('preSerialization hooks should run in the order in which they are defined', }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world21' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world21' }) + testDone() }) }) }) -test('preSerialization hooks should support encapsulation', t => { +test('preSerialization hooks should support encapsulation', (t, testDone) => { t.plan(9) const fastify = Fastify() - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('preSerialization', function (req, reply, payload, done) { payload.hello += '1' @@ -3047,39 +3142,42 @@ test('preSerialization hooks should support encapsulation', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world1' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world1' }) + completion.stepIn() }) - sget({ method: 'GET', url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world12' }) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.headers['content-length'], '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world12' }) + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('onRegister hook should be called / 1', t => { +test('onRegister hook should be called / 1', (t, testDone) => { t.plan(5) const fastify = Fastify() fastify.addHook('onRegister', function (instance, opts, done) { - t.ok(this.addHook) - t.ok(instance.addHook) - t.same(opts, pluginOpts) - t.notOk(done) + t.assert.ok(this.addHook) + t.assert.ok(instance.addHook) + t.assert.deepStrictEqual(opts, pluginOpts) + t.assert.ok(!done) }) const pluginOpts = { prefix: 'hello', custom: 'world' } @@ -3087,16 +3185,19 @@ test('onRegister hook should be called / 1', t => { done() }, pluginOpts) - fastify.ready(err => { t.error(err) }) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) }) -test('onRegister hook should be called / 2', t => { +test('onRegister hook should be called / 2', (t, testDone) => { t.plan(7) const fastify = Fastify() fastify.addHook('onRegister', function (instance) { - t.ok(this.addHook) - t.ok(instance.addHook) + t.assert.ok(this.addHook) + t.assert.ok(instance.addHook) }) fastify.register((instance, opts, done) => { @@ -3111,11 +3212,12 @@ test('onRegister hook should be called / 2', t => { }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRegister hook should be called / 3', t => { +test('onRegister hook should be called / 3', (t, testDone) => { t.plan(4) const fastify = Fastify() @@ -3129,24 +3231,25 @@ test('onRegister hook should be called / 3', t => { instance.data.push(1) instance.register((instance, opts, done) => { instance.data.push(2) - t.same(instance.data, [1, 2]) + t.assert.deepStrictEqual(instance.data, [1, 2]) done() }) - t.same(instance.data, [1]) + t.assert.deepStrictEqual(instance.data, [1]) done() }) fastify.register((instance, opts, done) => { - t.same(instance.data, []) + t.assert.deepStrictEqual(instance.data, []) done() }) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('onRegister hook should be called (encapsulation)', t => { +test('onRegister hook should be called (encapsulation)', (t, testDone) => { t.plan(1) const fastify = Fastify() @@ -3156,17 +3259,18 @@ test('onRegister hook should be called (encapsulation)', t => { plugin[Symbol.for('skip-override')] = true fastify.addHook('onRegister', (instance, opts) => { - t.fail('This should not be called') + t.assert.fail('This should not be called') }) fastify.register(plugin) fastify.ready(err => { - t.error(err) + t.assert.ifError(err) + testDone() }) }) -test('early termination, onRequest', t => { +test('early termination, onRequest', (t, testDone) => { t.plan(3) const app = Fastify() @@ -3177,17 +3281,18 @@ test('early termination, onRequest', t => { }) app.get('/', (req, reply) => { - t.fail('should not happen') + t.assert.fail('should not happen') }) app.inject('/', function (err, res) { - t.error(err) - t.equal(res.statusCode, 200) - t.equal(res.body.toString(), 'hello world') + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body.toString(), 'hello world') + testDone() }) }) -test('reply.send should throw if undefined error is thrown', t => { +test('reply.send should throw if undefined error is thrown', (t, testDone) => { /* eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}] */ t.plan(3) @@ -3205,18 +3310,19 @@ test('reply.send should throw if undefined error is thrown', t => { method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', code: 'FST_ERR_SEND_UNDEFINED_ERR', message: 'Undefined error has occurred', statusCode: 500 }) + testDone() }) }) -test('reply.send should throw if undefined error is thrown at preParsing hook', t => { +test('reply.send should throw if undefined error is thrown at preParsing hook', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -3232,18 +3338,19 @@ test('reply.send should throw if undefined error is thrown at preParsing hook', method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', code: 'FST_ERR_SEND_UNDEFINED_ERR', message: 'Undefined error has occurred', statusCode: 500 }) + testDone() }) }) -test('reply.send should throw if undefined error is thrown at onSend hook', t => { +test('reply.send should throw if undefined error is thrown at onSend hook', (t, testDone) => { t.plan(3) const fastify = Fastify() @@ -3259,24 +3366,25 @@ test('reply.send should throw if undefined error is thrown at onSend hook', t => method: 'GET', url: '/' }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - t.same(JSON.parse(res.payload), { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', code: 'FST_ERR_SEND_UNDEFINED_ERR', message: 'Undefined error has occurred', statusCode: 500 }) + testDone() }) }) -test('onTimeout should be triggered', t => { +test('onTimeout should be triggered', (t, testDone) => { t.plan(6) const fastify = Fastify({ connectionTimeout: 500 }) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('onTimeout', function (req, res, done) { - t.ok('called', 'onTimeout') + t.assert.ok('called', 'onTimeout') done() }) @@ -3289,32 +3397,36 @@ test('onTimeout should be triggered', t => { }) fastify.listen({ port: 0 }, (err, address) => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: address }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completion.stepIn() }) sget({ method: 'GET', url: `${address}/timeout` }, (err, response, body) => { - t.type(err, Error) - t.equal(err.message, 'socket hang up') + t.assert.ok(err, Error) + t.assert.strictEqual(err.message, 'socket hang up') + completion.stepIn() }) + completion.patience.then(testDone) }) }) -test('onTimeout should be triggered and socket _meta is set', t => { +test('onTimeout should be triggered and socket _meta is set', (t, testDone) => { t.plan(6) const fastify = Fastify({ connectionTimeout: 500 }) - t.teardown(() => { fastify.close() }) + t.after(() => { fastify.close() }) fastify.addHook('onTimeout', function (req, res, done) { - t.ok('called', 'onTimeout') + t.assert.ok('called', 'onTimeout') done() }) @@ -3328,22 +3440,26 @@ test('onTimeout should be triggered and socket _meta is set', t => { }) fastify.listen({ port: 0 }, (err, address) => { - t.error(err) + t.assert.ifError(err) + const completion = waitForCb({ steps: 2 }) sget({ method: 'GET', url: address }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + completion.stepIn() }) sget({ method: 'GET', url: `${address}/timeout` }, (err, response, body) => { - t.type(err, Error) - t.equal(err.message, 'socket hang up') + t.assert.ok(err, Error) + t.assert.strictEqual(err.message, 'socket hang up') + completion.stepIn() }) + completion.patience.then(testDone) }) }) @@ -3352,7 +3468,7 @@ test('registering invalid hooks should throw an error', async t => { const fastify = Fastify() - t.throws(() => { + t.assert.throws(() => { fastify.route({ method: 'GET', path: '/invalidHook', @@ -3361,9 +3477,11 @@ test('registering invalid hooks should throw an error', async t => { return 'hello world' } }) - }, new Error('onRequest hook should be a function, instead got [object Undefined]')) + }, { + message: 'onRequest hook should be a function, instead got [object Undefined]' + }) - t.throws(() => { + t.assert.throws(() => { fastify.route({ method: 'GET', path: '/invalidHook', @@ -3372,7 +3490,7 @@ test('registering invalid hooks should throw an error', async t => { return 'hello world' } }) - }, new Error('onRequest hook should be a function, instead got [object Null]')) + }, { message: 'onRequest hook should be a function, instead got [object Null]' }) // undefined is ok fastify.route({ @@ -3384,7 +3502,7 @@ test('registering invalid hooks should throw an error', async t => { } }) - t.throws(() => { + t.assert.throws(() => { fastify.addHook('onRoute', (routeOptions) => { routeOptions.onSend = [undefined] }) @@ -3392,35 +3510,39 @@ test('registering invalid hooks should throw an error', async t => { fastify.get('/', function (request, reply) { reply.send('hello world') }) - }, new Error('onSend hook should be a function, instead got [object Undefined]')) + }, { message: 'onSend hook should be a function, instead got [object Undefined]' }) }) -test('onRequestAbort should be triggered', t => { +test('onRequestAbort should be triggered', (t, testDone) => { const fastify = Fastify() let order = 0 t.plan(7) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) + + const completion = waitForCb({ steps: 2 }) + completion.patience.then(testDone) fastify.addHook('onRequestAbort', function (req, done) { - t.equal(++order, 1, 'called in hook') - t.ok(req.pendingResolve, 'request has pendingResolve') + t.assert.strictEqual(++order, 1, 'called in hook') + t.assert.ok(req.pendingResolve, 'request has pendingResolve') req.pendingResolve() + completion.stepIn() done() }) fastify.addHook('onError', function hook (request, reply, error, done) { - t.fail('onError should not be called') + t.assert.fail('onError should not be called') done() }) fastify.addHook('onSend', function hook (request, reply, payload, done) { - t.equal(payload, '{"hello":"world"}', 'onSend should be called') + t.assert.strictEqual(payload, '{"hello":"world"}', 'onSend should be called') done(null, payload) }) fastify.addHook('onResponse', function hook (request, reply, done) { - t.fail('onResponse should not be called') + t.assert.fail('onResponse should not be called') done() }) @@ -3428,21 +3550,22 @@ test('onRequestAbort should be triggered', t => { method: 'GET', path: '/', async handler (request, reply) { - t.pass('handler called') + t.assert.ok('handler called') let resolvePromise const promise = new Promise(resolve => { resolvePromise = resolve }) request.pendingResolve = resolvePromise await promise - t.pass('handler promise resolved') + t.assert.ok('handler promise resolved') return { hello: 'world' } }, async onRequestAbort (req) { - t.equal(++order, 2, 'called in route') + t.assert.strictEqual(++order, 2, 'called in route') + completion.stepIn() } }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const socket = connect(fastify.server.address().port) @@ -3452,17 +3575,21 @@ test('onRequestAbort should be triggered', t => { }) }) -test('onRequestAbort should support encapsulation', t => { +test('onRequestAbort should support encapsulation', (t, testDone) => { const fastify = Fastify() let order = 0 let child t.plan(6) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) + + const completion = waitForCb({ steps: 2 }) + completion.patience.then(testDone) fastify.addHook('onRequestAbort', function (req, done) { - t.equal(++order, 1, 'called in root') - t.strictSame(this.pluginName, child.pluginName) + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.deepStrictEqual(this.pluginName, child.pluginName) + completion.stepIn() done() }) @@ -3470,8 +3597,9 @@ test('onRequestAbort should support encapsulation', t => { child = _child fastify.addHook('onRequestAbort', async function (req) { - t.equal(++order, 2, 'called in child') - t.strictSame(this.pluginName, child.pluginName) + t.assert.strictEqual(++order, 2, 'called in child') + t.assert.deepStrictEqual(this.pluginName, child.pluginName) + completion.stepIn() }) child.route({ @@ -3482,13 +3610,13 @@ test('onRequestAbort should support encapsulation', t => { return { hello: 'world' } }, async onRequestAbort (_req) { - t.equal(++order, 3, 'called in route') + t.assert.strictEqual(++order, 3, 'called in route') } }) }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const socket = connect(fastify.server.address().port) @@ -3498,14 +3626,17 @@ test('onRequestAbort should support encapsulation', t => { }) }) -test('onRequestAbort should handle errors / 1', t => { +test('onRequestAbort should handle errors / 1', (t, testDone) => { const fastify = Fastify() t.plan(2) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) fastify.addHook('onRequestAbort', function (req, done) { - process.nextTick(() => t.pass()) + process.nextTick(() => { + t.assert.ok('should pass') + testDone() + }) done(new Error('KABOOM!')) }) @@ -3519,7 +3650,7 @@ test('onRequestAbort should handle errors / 1', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const socket = connect(fastify.server.address().port) @@ -3529,14 +3660,17 @@ test('onRequestAbort should handle errors / 1', t => { }) }) -test('onRequestAbort should handle errors / 2', t => { +test('onRequestAbort should handle errors / 2', (t, testDone) => { const fastify = Fastify() t.plan(2) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) fastify.addHook('onRequestAbort', function (req, done) { - process.nextTick(() => t.pass()) + process.nextTick(() => { + t.assert.ok('should pass') + testDone() + }) throw new Error('KABOOM!') }) @@ -3550,7 +3684,7 @@ test('onRequestAbort should handle errors / 2', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const socket = connect(fastify.server.address().port) @@ -3560,14 +3694,17 @@ test('onRequestAbort should handle errors / 2', t => { }) }) -test('onRequestAbort should handle async errors / 1', t => { +test('onRequestAbort should handle async errors / 1', (t, testDone) => { const fastify = Fastify() t.plan(2) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) fastify.addHook('onRequestAbort', async function (req) { - process.nextTick(() => t.pass()) + process.nextTick(() => { + t.assert.ok('should pass') + testDone() + }) throw new Error('KABOOM!') }) @@ -3581,7 +3718,7 @@ test('onRequestAbort should handle async errors / 1', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const socket = connect(fastify.server.address().port) @@ -3591,14 +3728,18 @@ test('onRequestAbort should handle async errors / 1', t => { }) }) -test('onRequestAbort should handle async errors / 2', t => { +test('onRequestAbort should handle async errors / 2', (t, testDone) => { const fastify = Fastify() t.plan(2) - t.teardown(() => fastify.close()) + t.after(() => fastify.close()) fastify.addHook('onRequestAbort', async function (req) { - process.nextTick(() => t.pass()) + process.nextTick(() => { + t.assert.ok('should pass') + testDone() + }) + return Promise.reject() }) @@ -3612,7 +3753,7 @@ test('onRequestAbort should handle async errors / 2', t => { }) fastify.listen({ port: 0 }, err => { - t.error(err) + t.assert.ifError(err) const socket = connect(fastify.server.address().port) From 8204c6294f91a76d99962474ca322c27b7fb59b0 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 30 May 2025 18:04:26 +0200 Subject: [PATCH 1034/1295] investigate CI problem (#6147) --- test/issue-4959.test.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/issue-4959.test.js b/test/issue-4959.test.js index be297375aa9..271bc5d9ccf 100644 --- a/test/issue-4959.test.js +++ b/test/issue-4959.test.js @@ -3,7 +3,14 @@ const { test } = require('node:test') const http = require('node:http') const Fastify = require('../fastify') - +const { setTimeout } = require('node:timers') + +/* +* Ensure that a socket error during the request does not cause the +* onSend hook to be called multiple times. +* +* @see https://github.com/fastify/fastify/issues/4959 +*/ function runBadClientCall (reqOptions, payload) { let innerResolve, innerReject const promise = new Promise((resolve, reject) => { @@ -25,7 +32,9 @@ function runBadClientCall (reqOptions, payload) { // Kill the socket immediately (before sending data) req.on('socket', (socket) => { - setTimeout(() => { socket.destroy() }, 5) + socket.on('connect', () => { + setTimeout(() => { socket.destroy() }, 0) + }) }) req.on('error', innerResolve) req.write(postData) @@ -34,7 +43,7 @@ function runBadClientCall (reqOptions, payload) { return promise } -test('should handle a soket error', async (t) => { +test('should handle a socket error', async (t) => { t.plan(4) const fastify = Fastify() From ff3dd4194cad0c600e67b48f2e02850072a90827 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 14:54:03 +0000 Subject: [PATCH 1035/1295] chore: Bump markdownlint-cli2 from 0.17.2 to 0.18.1 (#6150) Bumps [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) from 0.17.2 to 0.18.1. - [Changelog](https://github.com/DavidAnson/markdownlint-cli2/blob/main/CHANGELOG.md) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.17.2...v0.18.1) --- updated-dependencies: - dependency-name: markdownlint-cli2 dependency-version: 0.18.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b4bfbff8528..32dfb20f2d1 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "joi": "^17.12.3", "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", - "markdownlint-cli2": "^0.17.1", + "markdownlint-cli2": "^0.18.1", "neostandard": "^0.12.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", From e73481cc38a2c5532d022f1e796aca3b0e728e18 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Mon, 2 Jun 2025 01:50:37 +0200 Subject: [PATCH 1036/1295] chore: remove dependencie tap and others updated (#6148) * chore: remove dependecie tap and others updated * chore: removed .taprc file * chore: update dependabot.yml --------- Co-authored-by: Frazer Smith --- .github/dependabot.yml | 3 --- .taprc | 7 ------- package.json | 1 - test/decorator-namespace.test._js_ | 7 +++---- test/helper.js | 2 +- 5 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 .taprc diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 72a0d6f1967..871f58a50e7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -46,6 +46,3 @@ updates: patterns: - "ajv" - "ajv-*" - ignore: - - dependency-name: tap - update-types: ["version-update:semver-major"] diff --git a/.taprc b/.taprc deleted file mode 100644 index fac1cebf508..00000000000 --- a/.taprc +++ /dev/null @@ -1,7 +0,0 @@ -# vim: set filetype=yaml : -node-arg: - - '--allow-natives-syntax' - -include: - - 'test/**/*.test.js' - - 'test/**/*.test.mjs' diff --git a/package.json b/package.json index 32dfb20f2d1..d682d937eb1 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,6 @@ "proxyquire": "^2.1.3", "simple-get": "^4.0.1", "split2": "^4.2.0", - "tap": "^21.0.0", "tsd": "^0.32.0", "typescript": "~5.8.2", "undici": "^6.13.0", diff --git a/test/decorator-namespace.test._js_ b/test/decorator-namespace.test._js_ index 5f8cbf76c01..d23783c2549 100644 --- a/test/decorator-namespace.test._js_ +++ b/test/decorator-namespace.test._js_ @@ -2,8 +2,7 @@ /* eslint no-prototype-builtins: 0 */ -const t = require('tap') -const test = t.test +const { test } = require('node:test') const Fastify = require('..') const fp = require('fastify-plugin') @@ -24,8 +23,8 @@ test('plugin namespace', async t => { }, { namepace: 'foo' }) // ! but outside the plugin the decorator would be app.foo.utility() - t.ok(app.foo.utility) + t.assert.ok(app.foo.utility) const res = await app.inject('/') - t.same(res.json(), { utility: 'utility' }) + t.assert.deepStrictEqual(res.json(), { utility: 'utility' }) }) diff --git a/test/helper.js b/test/helper.js index eb7e7935af2..65abcc5cc3b 100644 --- a/test/helper.js +++ b/test/helper.js @@ -12,7 +12,7 @@ module.exports.sleep = promisify(setTimeout) /** * @param method HTTP request method - * @param t tap instance + * @param t node:test instance * @param isSetErrorHandler true: using setErrorHandler */ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { From 750976cc4cb09c3aedc00b27a971601856a5eafe Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 5 Jun 2025 00:39:57 +0200 Subject: [PATCH 1037/1295] fix: hook async flaky (#6155) --- test/hooks-async.test.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index e0eca1477d2..026a931ce00 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -10,7 +10,7 @@ const { waitForCb } = require('./toolkit') process.removeAllListeners('warning') -test('async hooks', (t, testDone) => { +test('async hooks', t => { t.plan(21) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.addHook('onRequest', async function (request, reply) { @@ -37,7 +37,7 @@ test('async hooks', (t, testDone) => { }) const completion = waitForCb({ - steps: 3 + steps: 6 }) fastify.addHook('onResponse', async function (request, reply) { await sleep(1) @@ -71,6 +71,7 @@ test('async hooks', (t, testDone) => { t.assert.strictEqual(response.statusCode, 200) t.assert.strictEqual(response.headers['content-length'], '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + completion.stepIn() }) sget({ method: 'HEAD', @@ -78,6 +79,7 @@ test('async hooks', (t, testDone) => { }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 500) + completion.stepIn() }) sget({ method: 'DELETE', @@ -85,10 +87,11 @@ test('async hooks', (t, testDone) => { }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 500) + completion.stepIn() }) - - completion.patience.then(testDone) }) + + return completion.patience }) test('modify payload', (t, testDone) => { From 6e2e0e0c50adc8ebd742531a8d8d83dbadfdf793 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 00:43:19 +0200 Subject: [PATCH 1038/1295] chore: Bump lycheeverse/lychee-action from 2.4.0 to 2.4.1 (#6151) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.4.0 to 2.4.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/1d97d84f0bc547f7b25f4c2170d87d810dc2fb2c...82202e5e9c2f4ef1a55a3d02563e1cb6041e5332) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 62ee7d16ead..4ef9aa2fb47 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -24,7 +24,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@1d97d84f0bc547f7b25f4c2170d87d810dc2fb2c + uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 with: fail: true # As external links behavior is not predictable, we check only internal links From a4098cc2786fd4d62919988fb6b0d7c5c69ca37c Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 5 Jun 2025 00:44:27 +0200 Subject: [PATCH 1039/1295] chore: removing simple-get from allow-unsafe-regex (#6154) * chore: removing simple-get * chore: removed body --------- Co-authored-by: Aras Abbasi --- test/allow-unsafe-regex.test.js | 67 ++++++++++----------------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/test/allow-unsafe-regex.test.js b/test/allow-unsafe-regex.test.js index 5f48eed8c37..50e2b4ffb99 100644 --- a/test/allow-unsafe-regex.test.js +++ b/test/allow-unsafe-regex.test.js @@ -2,10 +2,9 @@ const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat -test('allow unsafe regex', (t, done) => { - t.plan(4) +test('allow unsafe regex', async t => { + t.plan(2) const fastify = Fastify({ allowUnsafeRegex: false @@ -16,25 +15,15 @@ test('allow unsafe regex', (t, done) => { reply.send({ foo: req.params.foo }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/1234' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { - foo: '1234' - }) - done() - }) - }) + await fastify.listen({ port: 0 }) + + const result = await fetch(`http://localhost:${fastify.server.address().port}/1234`) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { foo: '1234' }) }) -test('allow unsafe regex not match', (t, done) => { - t.plan(3) +test('allow unsafe regex not match', async t => { + t.plan(1) const fastify = Fastify({ allowUnsafeRegex: false @@ -45,18 +34,10 @@ test('allow unsafe regex not match', (t, done) => { reply.send({ foo: req.params.foo }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/a1234' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) - }) + const result = await fetch(`http://localhost:${fastify.server.address().port}/a1234`) + t.assert.strictEqual(result.status, 404) }) test('allow unsafe regex not safe', (t, done) => { @@ -89,8 +70,8 @@ test('allow unsafe regex not safe by default', (t, done) => { done() }) -test('allow unsafe regex allow unsafe', (t, done) => { - t.plan(5) +test('allow unsafe regex allow unsafe', async t => { + t.plan(3) const fastify = Fastify({ allowUnsafeRegex: true @@ -103,19 +84,9 @@ test('allow unsafe regex allow unsafe', (t, done) => { }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/1234' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { - foo: '1234' - }) - done() - }) - }) + await fastify.listen({ port: 0 }) + + const result = await fetch(`http://localhost:${fastify.server.address().port}/1234`) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { foo: '1234' }) }) From 1642ec7799219e83155885e23fa2eea5f8216ec7 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 5 Jun 2025 09:11:29 +0200 Subject: [PATCH 1040/1295] chore: remove simple get on 404s test file (#6153) * chore: remove simple get from 3 tests * chore: error object * chore: another test converted * chore: root unsupported method * chore: root framework-unsupported method * chore: root unsupported route * chore: convert tests * chore: removed json parse * chore: removed method from fetch * chore: body * Update test/404s.test.js Co-authored-by: Aras Abbasi Signed-off-by: Matteo Pietro Dazzi --------- Signed-off-by: Matteo Pietro Dazzi Co-authored-by: Aras Abbasi --- test/404s.test.js | 551 +++++++++++++++++++--------------------------- 1 file changed, 226 insertions(+), 325 deletions(-) diff --git a/test/404s.test.js b/test/404s.test.js index b9bc182af33..02a68762d18 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -2,7 +2,6 @@ const { test } = require('node:test') const fp = require('fastify-plugin') -const sget = require('simple-get').concat const errors = require('http-errors') const split = require('split2') const Fastify = require('..') @@ -21,50 +20,36 @@ test('default 404', async t => { await fastify.listen({ port: 0 }) - await t.test('unsupported method', (t, done) => { + await t.test('unsupported method', async (t) => { t.plan(3) - sget({ - method: 'PUT', - url: getServerUrl(fastify), - body: {}, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') - done() + const result = await fetch(getServerUrl(fastify), { + method: 'PUT' }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 404) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') }) // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion - await t.test('framework-unsupported method', (t, done) => { + await t.test('framework-unsupported method', async (t) => { t.plan(3) - sget({ - method: 'PROPFIND', - url: getServerUrl(fastify), - body: {}, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') - done() + const result = await fetch(getServerUrl(fastify), { + method: 'PROPFIND' }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 404) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') }) - await t.test('unsupported route', (t, done) => { + await t.test('unsupported route', async (t) => { t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported', - body: {}, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') - done() - }) + const response = await fetch(getServerUrl(fastify) + '/notSupported') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8') }) await t.test('using post method and multipart/formdata', async t => { @@ -109,81 +94,65 @@ test('customized 404', async t => { await fastify.listen({ port: 0 }) - await t.test('unsupported method', (t, done) => { + await t.test('unsupported method', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify), { method: 'PUT', - url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found') }) - await t.test('framework-unsupported method', (t, done) => { + await t.test('framework-unsupported method', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify), { method: 'PROPFIND', - url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found') }) - await t.test('unsupported route', (t, done) => { + await t.test('unsupported route', async (t) => { t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found') - done() - }) + const response = await fetch(getServerUrl(fastify) + '/notSupported') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found') }) - await t.test('with error object', (t, done) => { + await t.test('with error object', async (t) => { t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/with-error' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Not Found', - message: 'Not Found', - statusCode: 404 - }) - done() + const response = await fetch(getServerUrl(fastify) + '/with-error') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.deepStrictEqual(await response.json(), { + error: 'Not Found', + message: 'Not Found', + statusCode: 404 }) }) - await t.test('error object with headers property', (t, done) => { + await t.test('error object with headers property', async (t) => { t.plan(4) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/with-error-custom-header' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(response.headers['x-foo'], 'bar') - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Not Found', - message: 'Not Found', - statusCode: 404 - }) - done() + const response = await fetch(getServerUrl(fastify) + '/with-error-custom-header') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(response.headers.get('x-foo'), 'bar') + t.assert.deepStrictEqual(await response.json(), { + error: 'Not Found', + message: 'Not Found', + statusCode: 404 }) }) }) @@ -201,18 +170,14 @@ test('custom header in notFound handler', async t => { await fastify.listen({ port: 0 }) - await t.test('not found with custom header', (t, done) => { + await t.test('not found with custom header', async (t) => { t.plan(4) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(response.headers['x-foo'], 'bar') - t.assert.strictEqual(body.toString(), 'this was not found') - done() - }) + const response = await fetch(getServerUrl(fastify) + '/notSupported') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(response.headers.get('x-foo'), 'bar') + t.assert.strictEqual(await response.text(), 'this was not found') }) }) @@ -390,176 +355,144 @@ test('encapsulated 404', async t => { await fastify.listen({ port: 0 }) - await t.test('root unsupported method', (t, done) => { + await t.test('root unsupported method', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify), { method: 'PUT', - url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found') }) - await t.test('root framework-unsupported method', (t, done) => { + await t.test('root framework-unsupported method', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify), { method: 'PROPFIND', - url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found') }) - await t.test('root unsupported route', (t, done) => { + await t.test('root unsupported route', async (t) => { t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found') - done() - }) + const response = await fetch(getServerUrl(fastify) + '/notSupported') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found') }) - await t.test('unsupported method', (t, done) => { + await t.test('unsupported method', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify) + '/test', { method: 'PUT', - url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 2') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 2') }) - await t.test('framework-unsupported method', (t, done) => { + await t.test('framework-unsupported method', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify) + '/test', { method: 'PROPFIND', - url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 2') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 2') }) - await t.test('unsupported route', (t, done) => { + await t.test('unsupported route', async (t) => { t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/test/notSupported' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 2') - done() - }) + const response = await fetch(getServerUrl(fastify) + '/test/notSupported') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 2') }) - await t.test('unsupported method 2', (t, done) => { + await t.test('unsupported method 2', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify) + '/test2', { method: 'PUT', - url: getServerUrl(fastify) + '/test2', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 3') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 3') }) - await t.test('framework-unsupported method 2', (t, done) => { + await t.test('framework-unsupported method 2', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify) + '/test2', { method: 'PROPFIND', - url: getServerUrl(fastify) + '/test2', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 3') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 3') }) - await t.test('unsupported route 2', (t, done) => { + await t.test('unsupported route 2', async (t) => { t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/test2/notSupported' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 3') - done() - }) + const response = await fetch(getServerUrl(fastify) + '/test2/notSupported') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 3') }) - await t.test('unsupported method 3', (t, done) => { + await t.test('unsupported method 3', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify) + '/test3/', { method: 'PUT', - url: getServerUrl(fastify) + '/test3/', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 4') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 4') }) - await t.test('framework-unsupported method 3', (t, done) => { + await t.test('framework-unsupported method 3', async (t) => { t.plan(3) - sget({ + const response = await fetch(getServerUrl(fastify) + '/test3/', { method: 'PROPFIND', - url: getServerUrl(fastify) + '/test3/', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 4') - done() }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 4') }) - await t.test('unsupported route 3', (t, done) => { + await t.test('unsupported route 3', async (t) => { t.plan(3) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/test3/notSupported' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(body.toString(), 'this was not found 4') - done() - }) + const response = await fetch(getServerUrl(fastify) + '/test3/notSupported') + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) + t.assert.strictEqual(await response.text(), 'this was not found 4') }) }) @@ -682,8 +615,8 @@ test('encapsulated custom 404 without - prefix hook and handler context', (t, do }) }) -test('run hooks on default 404', (t, done) => { - t.plan(7) +test('run hooks on default 404', async t => { + t.plan(6) const fastify = Fastify() @@ -713,20 +646,16 @@ test('run hooks on default 404', (t, done) => { t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + await fastify.listen({ port: 0 }) - sget({ - method: 'PUT', - url: getServerUrl(fastify), - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) + const response = await fetch(getServerUrl(fastify), { + method: 'PUT', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) }) test('run non-encapsulated plugin hooks on default 404', (t, done) => { @@ -822,8 +751,8 @@ test('run non-encapsulated plugin hooks on custom 404', (t, done) => { }) }) -test('run hook with encapsulated 404', (t, done) => { - t.plan(11) +test('run hook with encapsulated 404', async t => { + t.plan(10) const fastify = Fastify() @@ -877,24 +806,20 @@ test('run hook with encapsulated 404', (t, done) => { t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + await fastify.listen({ port: 0 }) - sget({ - method: 'PUT', - url: getServerUrl(fastify) + '/test', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) + const response = await fetch(getServerUrl(fastify) + '/test', { + method: 'PUT', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) }) -test('run hook with encapsulated 404 and framework-unsupported method', (t, done) => { - t.plan(11) +test('run hook with encapsulated 404 and framework-unsupported method', async t => { + t.plan(10) const fastify = Fastify() @@ -948,24 +873,20 @@ test('run hook with encapsulated 404 and framework-unsupported method', (t, done t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + await fastify.listen({ port: 0 }) - sget({ - method: 'PROPFIND', - url: getServerUrl(fastify) + '/test', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) + const response = await fetch(getServerUrl(fastify) + '/test', { + method: 'PROPFIND', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) }) -test('hooks check 404', (t, done) => { - t.plan(13) +test('hooks check 404', async t => { + t.plan(12) const fastify = Fastify() @@ -989,28 +910,21 @@ test('hooks check 404', (t, done) => { t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'PUT', - url: getServerUrl(fastify) + '?foo=asd', - body: JSON.stringify({ hello: 'world' }), - headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - }) + await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/notSupported?foo=asd' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) + const response1 = await fetch(getServerUrl(fastify) + '?foo=asd', { + method: 'PUT', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } }) + + t.assert.ok(!response1.ok) + t.assert.strictEqual(response1.status, 404) + + const response2 = await fetch(getServerUrl(fastify) + '/notSupported?foo=asd') + + t.assert.ok(!response2.ok) + t.assert.strictEqual(response2.status, 404) }) test('setNotFoundHandler should not suppress duplicated routes checking', t => { @@ -1080,8 +994,8 @@ test('log debug for 404', async t => { }) }) -test('Unknown method', (t, done) => { - t.plan(5) +test('Unknown method', async t => { + t.plan(4) const fastify = Fastify() @@ -1091,34 +1005,31 @@ test('Unknown method', (t, done) => { t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + await fastify.listen({ port: 0 }) - const handler = () => {} - // See https://github.com/fastify/light-my-request/pull/20 - t.assert.throws(() => fastify.inject({ - method: 'UNKNOWN_METHOD', - url: '/' - }, handler), Error) - - sget({ - method: 'UNKNOWN_METHOD', - url: getServerUrl(fastify) - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - message: 'Client Error', - statusCode: 400 - }) - done() - }) + const handler = () => {} + // See https://github.com/fastify/light-my-request/pull/20 + t.assert.throws(() => fastify.inject({ + method: 'UNKNOWN_METHOD', + url: '/' + }, handler), Error) + + const response = await fetch(getServerUrl(fastify), { + method: 'UNKNOWN_METHOD' + }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + + t.assert.deepStrictEqual(await response.json(), { + error: 'Bad Request', + message: 'Client Error', + statusCode: 400 }) }) -test('recognizes errors from the http-errors module', (t, done) => { - t.plan(5) +test('recognizes errors from the http-errors module', async t => { + t.plan(4) const fastify = Fastify() @@ -1128,27 +1039,23 @@ test('recognizes errors from the http-errors module', (t, done) => { t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { + await fastify.listen({ port: 0 }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + }) - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.assert.ifError(err) - t.assert.strictEqual(res.statusCode, 404) - - sget(getServerUrl(fastify), (err, response, body) => { - t.assert.ifError(err) - const obj = JSON.parse(body.toString()) - t.assert.deepStrictEqual(obj, { - error: 'Not Found', - message: 'Not Found', - statusCode: 404 - }) - done() - }) - }) + const response = await fetch(getServerUrl(fastify)) + + t.assert.ok(!response.ok) + t.assert.deepStrictEqual(await response.json(), { + error: 'Not Found', + message: 'Not Found', + statusCode: 404 }) }) @@ -1262,8 +1169,8 @@ test('cannot set notFoundHandler after binding', (t, done) => { }) }) -test('404 inside onSend', (t, done) => { - t.plan(3) +test('404 inside onSend', async t => { + t.plan(2) const fastify = Fastify() @@ -1284,18 +1191,12 @@ test('404 inside onSend', (t, done) => { t.after(() => { fastify.close() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: getServerUrl(fastify) - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) - }) + const response = await fetch(getServerUrl(fastify)) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) }) // https://github.com/fastify/fastify/issues/868 From 7acfd430f7535c21eaccda87aa96fdc0769405c8 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 5 Jun 2025 22:11:53 +0200 Subject: [PATCH 1041/1295] chore: convert handle request to simple-get (#6159) --- test/internals/handle-request.test.js | 115 +++++++++++--------------- 1 file changed, 49 insertions(+), 66 deletions(-) diff --git a/test/internals/handle-request.test.js b/test/internals/handle-request.test.js index e9947f2c9df..0c5abd684b4 100644 --- a/test/internals/handle-request.test.js +++ b/test/internals/handle-request.test.js @@ -7,7 +7,6 @@ const Request = require('../../lib/request') const Reply = require('../../lib/reply') const { kRouteContext } = require('../../lib/symbols') const buildSchema = require('../../lib/validation').compileSchemasForValidation -const sget = require('simple-get').concat const Ajv = require('ajv') const ajv = new Ajv({ coerceTypes: true }) @@ -129,8 +128,8 @@ test('handler function - preValidationCallback with finished response', t => { internals.handler({ [kRouteContext]: context }, new Reply(res, { [kRouteContext]: context })) }) -test('request should be defined in onSend Hook on post request with content type application/json', (t, done) => { - t.plan(8) +test('request should be defined in onSend Hook on post request with content type application/json', async t => { + t.plan(6) const fastify = require('../..')() t.after(() => { @@ -149,28 +148,24 @@ test('request should be defined in onSend Hook on post request with content type reply.send(200) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'content-type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - // a 400 error is expected because of no body - t.assert.strictEqual(response.statusCode, 400) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { + 'content-type': 'application/json' + } }) + + t.assert.strictEqual(result.status, 400) }) -test('request should be defined in onSend Hook on post request with content type application/x-www-form-urlencoded', (t, done) => { - t.plan(7) +test('request should be defined in onSend Hook on post request with content type application/x-www-form-urlencoded', async t => { + t.plan(5) const fastify = require('../..')() - t.after(() => { fastify.close() }) + t.after(() => { + fastify.close() + }) fastify.addHook('onSend', (request, reply, payload, done) => { t.assert.ok(request) @@ -183,29 +178,25 @@ test('request should be defined in onSend Hook on post request with content type reply.send(200) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'content-type': 'application/x-www-form-urlencoded' - } - }, (err, response, body) => { - t.assert.ifError(err) - // a 415 error is expected because of missing content type parser - t.assert.strictEqual(response.statusCode, 415) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + } }) + + // a 415 error is expected because of missing content type parser + t.assert.strictEqual(result.status, 415) }) -test('request should be defined in onSend Hook on options request with content type application/x-www-form-urlencoded', (t, done) => { - t.plan(7) +test('request should be defined in onSend Hook on options request with content type application/x-www-form-urlencoded', async t => { + t.plan(5) const fastify = require('../..')() - t.after(() => { fastify.close() }) + t.after(() => { + fastify.close() + }) fastify.addHook('onSend', (request, reply, payload, done) => { t.assert.ok(request) @@ -218,26 +209,20 @@ test('request should be defined in onSend Hook on options request with content t reply.send(200) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'OPTIONS', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'content-type': 'application/x-www-form-urlencoded' - } - }, (err, response, body) => { - t.assert.ifError(err) - // Body parsing skipped, so no body sent - t.assert.strictEqual(response.statusCode, 200) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + const result = await fetch(fastifyServer, { + method: 'OPTIONS', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + } }) + + // Body parsing skipped, so no body sent + t.assert.strictEqual(result.status, 200) }) -test('request should respond with an error if an unserialized payload is sent inside an async handler', (t, done) => { - t.plan(3) +test('request should respond with an error if an unserialized payload is sent inside an async handler', async t => { + t.plan(2) const fastify = require('../..')() @@ -246,18 +231,16 @@ test('request should respond with an error if an unserialized payload is sent in return Promise.resolve(request.headers) }) - fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' - }, (err, res) => { - t.assert.ifError(err) - t.assert.strictEqual(res.statusCode, 500) - t.assert.deepStrictEqual(JSON.parse(res.payload), { - error: 'Internal Server Error', - code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE', - message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.', - statusCode: 500 - }) - done() + }) + + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE', + message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.', + statusCode: 500 }) }) From cab524ab2e527e06d3b38d83630b4e0e418a51d5 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Fri, 6 Jun 2025 11:32:18 +0200 Subject: [PATCH 1042/1295] chore: remove simple-get from url-rewriting (#6163) * chore: remove simple-get from url-rewriting * fix: lint --- test/url-rewriting.test.js | 107 ++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 62 deletions(-) diff --git a/test/url-rewriting.test.js b/test/url-rewriting.test.js index f251a4d4970..f76aefdd762 100644 --- a/test/url-rewriting.test.js +++ b/test/url-rewriting.test.js @@ -2,10 +2,9 @@ const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat -test('Should rewrite url', (t, done) => { - t.plan(5) +test('Should rewrite url', async t => { + t.plan(4) const fastify = Fastify({ rewriteUrl (req) { t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') @@ -22,24 +21,19 @@ test('Should rewrite url', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - t.assert.strictEqual(response.statusCode, 200) - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}/this-would-404-without-url-rewrite`) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) -test('Should not rewrite if the url is the same', (t, done) => { - t.plan(4) +test('Should not rewrite if the url is the same', async t => { + t.plan(3) const fastify = Fastify({ rewriteUrl (req) { t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') @@ -56,22 +50,18 @@ test('Should not rewrite if the url is the same', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}/this-would-404-without-url-rewrite`) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 404) }) -test('Should throw an error', (t, done) => { - t.plan(5) +test('Should throw an error', async t => { + t.plan(2) const fastify = Fastify({ rewriteUrl (req) { t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') @@ -88,23 +78,20 @@ test('Should throw an error', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' - }, (err, response, body) => { - t.assert.strictEqual(err.code, 'ECONNRESET') - t.assert.strictEqual(response, undefined) - t.assert.strictEqual(body, undefined) - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => fastify.close()) + + try { + await fetch(`${fastifyServer}/this-would-404-without-url-rewrite`) + t.assert.fail('Expected fetch to throw an error') + } catch (err) { + t.assert.ok(err instanceof Error) + } }) -test('Should rewrite url but keep originalUrl unchanged', (t, done) => { - t.plan(7) +test('Should rewrite url but keep originalUrl unchanged', async t => { + t.plan(6) const fastify = Fastify({ rewriteUrl (req) { t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') @@ -122,18 +109,14 @@ test('Should rewrite url but keep originalUrl unchanged', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite' - }, (err, response, body) => { - t.assert.ifError(err) - const parsedBody = JSON.parse(body) - t.assert.deepStrictEqual(parsedBody, { hello: 'world', hostname: 'localhost', port: fastify.server.address().port }) - t.assert.strictEqual(response.statusCode, 200) - done() - }) - }) + await fastify.listen({ port: 0 }) + const port = fastify.server.address().port + + t.after(() => fastify.close()) + + const result = await fetch(`http://localhost:${port}/this-would-404-without-url-rewrite`) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world', hostname: 'localhost', port }) }) From 051d3e497847d21f97a4b2527f2dfb802dc344cc Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sat, 7 Jun 2025 17:13:09 +0200 Subject: [PATCH 1043/1295] chore: report remove simple-get (#6157) --- test/http-methods/report.test.js | 113 ++++++++++++------------------- 1 file changed, 44 insertions(+), 69 deletions(-) diff --git a/test/http-methods/report.test.js b/test/http-methods/report.test.js index 78847c4cbab..c8533fb2948 100644 --- a/test/http-methods/report.test.js +++ b/test/http-methods/report.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('REPORT', { hasBody: true }) @@ -80,88 +79,64 @@ test('report test', async t => { fastify.close() }) - await t.test('request - report', (t, done) => { + await t.test('request - report', async (t) => { t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/`, - method: 'REPORT' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + const result = await fetch(`http://localhost:${fastify.server.address().port}/`, { + method: 'REPORT' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) }) - await t.test('request with other path - report', (t, done) => { + await t.test('request with other path - report', async (t) => { t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/test`, - method: 'REPORT' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'REPORT' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - await t.test('request with body - report', (t, done) => { + await t.test('request with body - report', async (t) => { t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/test`, - headers: { 'content-type': 'text/plain' }, - body: bodySample, - method: 'REPORT' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'REPORT', + headers: { 'content-type': 'text/plain' }, + body: bodySample + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) }) - await t.test('request with body and no content type (415 error) - report', (t, done) => { + await t.test('request with body and no content type (415 error) - report', async (t) => { t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/test`, - body: bodySample, - method: 'REPORT' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'REPORT', + body: bodySample, + headers: { 'content-type': '' } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) }) - await t.test('request without body - report', (t, done) => { + await t.test('request without body - report', async (t) => { t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/test`, - method: 'REPORT' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'REPORT' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) }) }) From 454b8d3a718d7ccdafb2e4d14d3ec983b08da1ad Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sat, 7 Jun 2025 21:13:06 +0200 Subject: [PATCH 1044/1295] chore: remove simple-get from custom parser async (#6164) --- test/custom-parser-async.test.js | 39 ++++++++++++++------------------ 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/test/custom-parser-async.test.js b/test/custom-parser-async.test.js index 0eb622530f6..679d99e0f76 100644 --- a/test/custom-parser-async.test.js +++ b/test/custom-parser-async.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('../fastify') process.removeAllListeners('warning') @@ -24,41 +23,37 @@ test('contentTypeParser should add a custom async parser', async t => { }) t.after(() => fastify.close()) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - await t.test('in POST', (t, done) => { + await t.test('in POST', async t => { t.plan(3) - sget({ + const result = await fetch(fastifyServer, { method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - done() + }, + body: '{"hello":"world"}' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) - await t.test('in OPTIONS', (t, done) => { + await t.test('in OPTIONS', async t => { t.plan(3) - sget({ + const result = await fetch(fastifyServer, { method: 'OPTIONS', - url: 'http://localhost:' + fastify.server.address().port, - body: '{"hello":"world"}', headers: { 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - done() + }, + body: '{"hello":"world"}' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) }) From 0e1ffa236e3f009676764b027891f391090d9cfc Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 9 Jun 2025 10:43:44 +0200 Subject: [PATCH 1045/1295] chore: removed simple-get from mkcol tests (#6194) --- test/http-methods/mkcol.test.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/test/http-methods/mkcol.test.js b/test/http-methods/mkcol.test.js index 449671c48b5..3ef662a938f 100644 --- a/test/http-methods/mkcol.test.js +++ b/test/http-methods/mkcol.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../')() fastify.addHttpMethod('MKCOL') @@ -22,18 +21,15 @@ test('can be created - mkcol', t => { }) test('mkcol test', async t => { - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) t.after(() => { fastify.close() }) - await t.test('request - mkcol', (t, done) => { + await t.test('request - mkcol', async t => { t.plan(2) - sget({ - url: `http://localhost:${fastify.server.address().port}/test/`, + const result = await fetch(`${fastifyServer}/test/`, { method: 'MKCOL' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 201) - done() }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 201) }) }) From 3cbea233df40eb68128120f91d89a36379d45fea Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 9 Jun 2025 10:58:08 +0200 Subject: [PATCH 1046/1295] chore: removed simple-get from proto-poisoning test (#6185) * chore: removed simple-get from proto-poisoning test * fix: after --- test/proto-poisoning.test.js | 175 ++++++++++++++++------------------- 1 file changed, 78 insertions(+), 97 deletions(-) diff --git a/test/proto-poisoning.test.js b/test/proto-poisoning.test.js index 5eeb2e0d741..9b557c00aeb 100644 --- a/test/proto-poisoning.test.js +++ b/test/proto-poisoning.test.js @@ -1,11 +1,10 @@ 'use strict' const Fastify = require('..') -const sget = require('simple-get').concat const { test } = require('node:test') -test('proto-poisoning error', (t, done) => { - t.plan(3) +test('proto-poisoning error', async (t) => { + t.plan(2) const fastify = Fastify() @@ -13,52 +12,46 @@ test('proto-poisoning error', (t, done) => { t.assert.fail('handler should not be called') }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: '{ "__proto__": { "a": 42 } }' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - fastify.close() - done() - }) + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "__proto__": { "a": 42 } }' }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) }) -test('proto-poisoning remove', (t, done) => { - t.plan(4) +test('proto-poisoning remove', async (t) => { + t.plan(3) const fastify = Fastify({ onProtoPoisoning: 'remove' }) + t.after(() => fastify.close()) + fastify.post('/', (request, reply) => { t.assert.strictEqual(undefined, Object.assign({}, request.body).a) reply.send({ ok: true }) }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: '{ "__proto__": { "a": 42 }, "b": 42 }' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - fastify.close() - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "__proto__": { "a": 42 }, "b": 42 }' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) -test('proto-poisoning ignore', (t, done) => { - t.plan(4) +test('proto-poisoning ignore', async (t) => { + t.plan(3) const fastify = Fastify({ onProtoPoisoning: 'ignore' }) @@ -67,25 +60,22 @@ test('proto-poisoning ignore', (t, done) => { reply.send({ ok: true }) }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: '{ "__proto__": { "a": 42 }, "b": 42 }' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - fastify.close() - done() - }) + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "__proto__": { "a": 42 }, "b": 42 }' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) -test('constructor-poisoning error (default in v3)', (t, done) => { - t.plan(3) +test('constructor-poisoning error (default in v3)', async (t) => { + t.plan(2) const fastify = Fastify() @@ -93,72 +83,63 @@ test('constructor-poisoning error (default in v3)', (t, done) => { reply.send('ok') }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: '{ "constructor": { "prototype": { "foo": "bar" } } }' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - fastify.close() - done() - }) + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "constructor": { "prototype": { "foo": "bar" } } }' }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) }) -test('constructor-poisoning error', (t, done) => { - t.plan(3) +test('constructor-poisoning error', async (t) => { + t.plan(2) const fastify = Fastify({ onConstructorPoisoning: 'error' }) + t.after(() => fastify.close()) + fastify.post('/', (request, reply) => { t.assert.fail('handler should not be called') }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: '{ "constructor": { "prototype": { "foo": "bar" } } }' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - fastify.close() - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "constructor": { "prototype": { "foo": "bar" } } }' }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) }) -test('constructor-poisoning remove', (t, done) => { - t.plan(4) +test('constructor-poisoning remove', async (t) => { + t.plan(3) const fastify = Fastify({ onConstructorPoisoning: 'remove' }) + t.after(() => fastify.close()) + fastify.post('/', (request, reply) => { t.assert.strictEqual(undefined, Object.assign({}, request.body).foo) reply.send({ ok: true }) }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: '{ "constructor": { "prototype": { "foo": "bar" } } }' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - fastify.close() - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "constructor": { "prototype": { "foo": "bar" } } }' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) From b7cf50a395e90d30a04ea6e64eee725ec1de6f49 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 9 Jun 2025 12:40:01 +0200 Subject: [PATCH 1047/1295] ci: Added Node.js v24 (#6113) * chore: Added Node.js v24 Signed-off-by: Matteo Collina * disable reporter to see what is timing out Signed-off-by: Matteo Collina * ci: add node 24 to test matrices * make the linter happy Signed-off-by: Matteo Collina * fix http/2 closing tests Signed-off-by: Matteo Collina * fixup Signed-off-by: Matteo Collina --------- Signed-off-by: Matteo Collina Co-authored-by: Frazer Smith Co-authored-by: Aras Abbasi --- .github/workflows/benchmark-parser.yml | 10 +++- .github/workflows/benchmark.yml | 9 +++- .github/workflows/ci.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 +- docs/Guides/Delay-Accepting-Requests.md | 6 +-- package.json | 2 +- test/build-certificate.js | 2 +- test/http2/closing.test.js | 58 ++++++++++++++++-------- 9 files changed, 64 insertions(+), 31 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 5cd7eafd465..8e622eac2e7 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -16,11 +16,13 @@ jobs: outputs: PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} PR-BENCH-22: ${{ steps.benchmark-pr.outputs.BENCH_RESULT22 }} + PR-BENCH-24: ${{ steps.benchmark-pr.outputs.BENCH_RESULT24 }} MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} MAIN-BENCH-22: ${{ steps.benchmark-main.outputs.BENCH_RESULT22 }} + MAIN-BENCH-24: ${{ steps.benchmark-main.outputs.BENCH_RESULT24 }} strategy: matrix: - node-version: [20, 22] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v4 with: @@ -86,3 +88,9 @@ jobs: **Node**: 22 **PR**: ${{ needs.benchmark.outputs.PR-BENCH-22 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-22 }} + + --- + + **Node**: 24 + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-24 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-24 }} \ No newline at end of file diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 7b7a8d35ffa..ae44d7f9708 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -16,11 +16,13 @@ jobs: outputs: PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} PR-BENCH-22: ${{ steps.benchmark-pr.outputs.BENCH_RESULT22 }} + PR-BENCH-24: ${{ steps.benchmark-pr.outputs.BENCH_RESULT24 }} MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} MAIN-BENCH-22: ${{ steps.benchmark-main.outputs.BENCH_RESULT22 }} + MAIN-BENCH-24: ${{ steps.benchmark-main.outputs.BENCH_RESULT24 }} strategy: matrix: - node-version: [20, 22] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v4 with: @@ -87,6 +89,11 @@ jobs: **PR**: ${{ needs.benchmark.outputs.PR-BENCH-22 }} **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-22 }} + --- + + **Node**: 24 + **PR**: ${{ needs.benchmark.outputs.PR-BENCH-24 }} + **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-24 }} remove-label: if: ${{ github.event.label.name == 'benchmark' }} needs: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 515c23abbcc..ce9fe00cdd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: strategy: matrix: - node-version: [20, 22] + node-version: [20, 22, 24] os: [macos-latest, ubuntu-latest, windows-latest] steps: diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ad577125416..d5f16f20ca6 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -25,7 +25,7 @@ jobs: strategy: matrix: - node-version: [20, 22] + node-version: [20, 22, 24] os: [ubuntu-latest] pnpm-version: [8] diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 73a50a5205e..d3062d04f77 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [20, 22] + node-version: [20, 22, 24] os: [ubuntu-latest] pnpm-version: [8] @@ -53,7 +53,7 @@ jobs: strategy: matrix: # Maintenance and active LTS - node-version: [20, 22] + node-version: [20, 22, 24] os: [ubuntu-latest] steps: diff --git a/docs/Guides/Delay-Accepting-Requests.md b/docs/Guides/Delay-Accepting-Requests.md index acf36d84f4b..6505775a3f3 100644 --- a/docs/Guides/Delay-Accepting-Requests.md +++ b/docs/Guides/Delay-Accepting-Requests.md @@ -529,9 +529,9 @@ since that was not one of the requests we asked our plugin to filter, it succeeded. That could also be used as a means of informing an interested party whether or not we were ready to serve requests (although `/ping` is more commonly associated with *liveness* checks and that would be the responsibility -of a *readiness* check -- the curious reader can get more info on these terms -[here](https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes)) -with the `ready` field. Below is the response to that request: +of a *readiness* check -- the curious reader can get more info on these +[terms](https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes)) +here with the `ready` field. Below is the response to that request: ```sh HTTP/1.1 200 OK diff --git a/package.json b/package.json index d682d937eb1..870b656ea18 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "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", "test:typescript": "tsc test/types/import.ts --target es2022 --moduleResolution node16 --module node16 --noEmit && tsd", "test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse", - "unit": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage", + "unit": "borp", "unit:report": "c8 --reporter html borp --reporter=@jsumners/line-reporter", "citgm": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage --concurrency=1" }, diff --git a/test/build-certificate.js b/test/build-certificate.js index c4dc54a9171..73a29729be3 100644 --- a/test/build-certificate.js +++ b/test/build-certificate.js @@ -91,7 +91,7 @@ function selfCert (opts) { } } -async function buildCertificate () { +function buildCertificate () { // "global" is used in here because "t.context" is only supported by "t.beforeEach" and "t.afterEach" // For the test case which execute this code which will be using `t.before` and it can reduce the // number of times executing it. diff --git a/test/http2/closing.test.js b/test/http2/closing.test.js index 9a3193d44d7..2d14090d6df 100644 --- a/test/http2/closing.test.js +++ b/test/http2/closing.test.js @@ -11,16 +11,13 @@ const { getServerUrl } = require('../helper') test.before(buildCertificate) -test('http/2 request while fastify closing', (t, done) => { - let fastify - try { - fastify = Fastify({ - http2: true - }) - t.assert.ok('http2 successfully loaded') - } catch (e) { - t.assert.fail('http2 loading failed') - } +const isNode24OrGreater = Number(process.versions.node.split('.')[0]) >= 24 + +test('http/2 request while fastify closing Node <24', { skip: isNode24OrGreater }, (t, done) => { + const fastify = Fastify({ + http2: true + }) + t.assert.ok('http2 successfully loaded') fastify.get('/', () => Promise.resolve({})) @@ -53,17 +50,38 @@ test('http/2 request while fastify closing', (t, done) => { }) }) -test('http/2 request while fastify closing - return503OnClosing: false', (t, done) => { - let fastify - try { - fastify = Fastify({ - http2: true, - return503OnClosing: false +test('http/2 request while fastify closing Node >=24', { skip: !isNode24OrGreater }, (t, done) => { + const fastify = Fastify({ + http2: true + }) + t.assert.ok('http2 successfully loaded') + + fastify.get('/', () => Promise.resolve({})) + + t.after(() => { fastify.close() }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const url = getServerUrl(fastify) + const session = http2.connect(url, function () { + session.on('error', () => { + // Nothing to do here, + // we are not interested in this error that might + // happen or not + }) + session.on('close', () => { + done() + }) + fastify.close() }) - t.assert.ok('http2 successfully loaded') - } catch (e) { - t.assert.fail('http2 loading failed') - } + }) +}) + +test('http/2 request while fastify closing - return503OnClosing: false', { skip: isNode24OrGreater }, (t, done) => { + const fastify = Fastify({ + http2: true, + return503OnClosing: false + }) t.after(() => { fastify.close() }) From dfec714210fd3f641c7ac8afcce828ae24722396 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 9 Jun 2025 12:51:37 +0200 Subject: [PATCH 1048/1295] chore: removed simple-get from nullable validation test (#6191) --- test/nullable-validation.test.js | 79 +++++++++++++------------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/test/nullable-validation.test.js b/test/nullable-validation.test.js index 02ffa0c6859..456e7a72ac9 100644 --- a/test/nullable-validation.test.js +++ b/test/nullable-validation.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('..') test('nullable string', (t, done) => { @@ -52,8 +51,8 @@ test('nullable string', (t, done) => { }) }) -test('object or null body', (t, done) => { - t.plan(5) +test('object or null body', async (t) => { + t.plan(4) const fastify = Fastify() @@ -88,24 +87,20 @@ test('object or null body', (t, done) => { } }) - fastify.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { isUndefinedBody: true }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer, { + method: 'POST' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { isUndefinedBody: true }) }) -test('nullable body', (t, done) => { - t.plan(5) +test('nullable body', async (t) => { + t.plan(4) const fastify = Fastify() @@ -141,24 +136,20 @@ test('nullable body', (t, done) => { } }) - fastify.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { isUndefinedBody: true }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { isUndefinedBody: true }) }) -test('Nullable body with 204', (t, done) => { - t.plan(5) +test('Nullable body with 204', async (t) => { + t.plan(4) const fastify = Fastify() @@ -183,18 +174,14 @@ test('Nullable body with 204', (t, done) => { } }) - fastify.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 204) - t.assert.strictEqual(body.length, 0) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 204) + t.assert.strictEqual((await result.text()).length, 0) }) From a5f9c4cc88ce707c17888a87dff2df37de8e8cf4 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:43:47 +0200 Subject: [PATCH 1049/1295] feat: configure errorhandler override (#6104) --- build/build-validation.js | 3 +- docs/Reference/Errors.md | 4 +-- docs/Reference/Server.md | 38 +++++++++++++++------- fastify.d.ts | 3 +- fastify.js | 14 +++++++- lib/configValidator.js | 2 +- lib/errors.js | 6 ++++ lib/pluginOverride.js | 4 ++- lib/symbols.js | 1 + lib/warnings.js | 8 +++++ test/internals/errors.test.js | 2 +- test/set-error-handler.test.js | 59 +++++++++++++++++++++++++++++++++- test/types/fastify.test-d.ts | 3 ++ 13 files changed, 126 insertions(+), 21 deletions(-) diff --git a/build/build-validation.js b/build/build-validation.js index b82f74d421c..0d880a11f65 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -42,7 +42,8 @@ const defaultInitOptions = { requestIdLogLabel: 'reqId', http2SessionTimeout: 72000, // 72 seconds exposeHeadRoutes: true, - useSemicolonDelimiter: false + useSemicolonDelimiter: false, + allowErrorHandlerOverride: true // TODO: set to false in v6 } const schema = { diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 93c2334425b..2f8fb3a7f18 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -97,6 +97,7 @@ - [FST_ERR_VALIDATION](#fst_err_validation) - [FST_ERR_LISTEN_OPTIONS_INVALID](#fst_err_listen_options_invalid) - [FST_ERR_ERROR_HANDLER_NOT_FN](#fst_err_error_handler_not_fn) + - [FST_ERR_ERROR_HANDLER_ALREADY_SET](#fst_err_error_handler_already_set) ### Error Handling In Node.js @@ -366,5 +367,4 @@ Below is a table with all the error codes used by Fastify. | FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER | The plugin being registered mixes async and callback styles. | - | [#5141](https://github.com/fastify/fastify/pull/5141) | | FST_ERR_VALIDATION | The Request failed the payload validation. | Check the request payload. | [#4824](https://github.com/fastify/fastify/pull/4824) | | FST_ERR_LISTEN_OPTIONS_INVALID | Invalid listen options. | Check the listen options. | [#4886](https://github.com/fastify/fastify/pull/4886) | -| FST_ERR_ERROR_HANDLER_NOT_FN | Error Handler must be a function | Provide a function to `setErrorHandler`. | [#5317](https://github.com/fastify/fastify/pull/5317) | - +| FST_ERR_ERROR_HANDLER_NOT_FN | Error Handler must be a function | Provide a function to `setErrorHandler`. | [#5317](https://github.com/fastify/fastify/pull/5317) | FST_ERR_ERROR_HANDLER_ALREADY_SET | Error Handler already set in this scope. Set `allowErrorHandlerOverride: true` to allow overriding. | By default, `setErrorHandler` can only be called once per encapsulation context. | [#6097](https://github.com/fastify/fastify/pull/6098) | diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 801c1c47879..9ae1b902f6d 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -45,6 +45,7 @@ describes the properties available in that options object. - [`clientErrorHandler`](#clienterrorhandler) - [`rewriteUrl`](#rewriteurl) - [`useSemicolonDelimiter`](#usesemicolondelimiter) + - [`allowErrorHandlerOverride`](#allowerrorhandleroverride) - [Instance](#instance) - [Server Methods](#server-methods) - [server](#server) @@ -866,6 +867,29 @@ fastify.get('/dev', async (request, reply) => { }) ``` +### `allowErrorHandlerOverride` + + +* **Default:** `true` + +> ⚠ **Warning:** This option will be set to `false` by default +> in the next major release. + +When set to `false`, it prevents `setErrorHandler` from being called +multiple times within the same scope, ensuring that the previous error +handler is not unintentionally overridden. + +#### Example of incorrect usage: + +```js +app.setErrorHandler(function freeSomeResources () { + // Never executed, memory leaks +}) + +app.setErrorHandler(function anotherErrorHandler () { + // Overrides the previous handler +}) +``` ## Instance @@ -1577,18 +1601,8 @@ if (statusCode >= 500) { > ⚠ Warning: > Avoid calling setErrorHandler multiple times in the same scope. -> Only the last handler will take effect, and previous ones will be silently overridden. -> -> Incorrect usage: -> ```js -> app.setErrorHandler(function freeSomeResources () { -> // Never executed, memory leaks -> }) -> -> app.setErrorHandler(function anotherErrorHandler () { -> // Overrides the previous handler -> }) -> ``` +> See [`allowErrorHandlerOverride`](#allowerrorhandleroverride). + #### setChildLoggerFactory diff --git a/fastify.d.ts b/fastify.d.ts index 2f8e2111fea..f2e595d6e12 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -157,7 +157,8 @@ declare namespace fastify { * listener to error events emitted by client connections */ clientErrorHandler?: (error: ConnectionError, socket: Socket) => void, - childLoggerFactory?: FastifyChildLoggerFactory + childLoggerFactory?: FastifyChildLoggerFactory, + allowErrorHandlerOverride?: boolean } /** diff --git a/fastify.js b/fastify.js index 793981bb1af..c0789b45af8 100644 --- a/fastify.js +++ b/fastify.js @@ -31,7 +31,8 @@ const { kErrorHandler, kKeepAliveConnections, kChildLoggerFactory, - kGenReqId + kGenReqId, + kErrorHandlerAlreadySet } = require('./lib/symbols.js') const { createServer } = require('./lib/server') @@ -72,10 +73,12 @@ const { FST_ERR_ROUTE_REWRITE_NOT_STR, FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN, FST_ERR_ERROR_HANDLER_NOT_FN, + FST_ERR_ERROR_HANDLER_ALREADY_SET, FST_ERR_ROUTE_METHOD_INVALID } = errorCodes const { buildErrorHandler } = require('./lib/error-handler.js') +const { FSTWRN004 } = require('./lib/warnings.js') const initChannel = diagnostics.channel('fastify.initialization') @@ -149,6 +152,7 @@ function fastify (options) { options.disableRequestLogging = disableRequestLogging options.ajv = ajvOptions options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler + options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride const initialConfig = getSecuredInitialConfig(options) @@ -237,6 +241,7 @@ function fastify (options) { [kSchemaController]: schemaController, [kSchemaErrorFormatter]: null, [kErrorHandler]: buildErrorHandler(), + [kErrorHandlerAlreadySet]: false, [kChildLoggerFactory]: defaultChildLoggerFactory, [kReplySerializerDefault]: null, [kContentTypeParser]: new ContentTypeParser( @@ -858,6 +863,13 @@ function fastify (options) { throw new FST_ERR_ERROR_HANDLER_NOT_FN() } + if (!options.allowErrorHandlerOverride && this[kErrorHandlerAlreadySet]) { + throw new FST_ERR_ERROR_HANDLER_ALREADY_SET() + } else if (this[kErrorHandlerAlreadySet]) { + FSTWRN004("To disable this behavior, set 'allowErrorHandlerOverride' to false or ignore this message. For more information, visit: https://fastify.dev/docs/latest/Reference/Server/#allowerrorhandleroverride") + } + + this[kErrorHandlerAlreadySet] = true this[kErrorHandler] = buildErrorHandler(this[kErrorHandler], func.bind(this)) return this } diff --git a/lib/configValidator.js b/lib/configValidator.js index 2c1e8b1534d..c2d842896dc 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -1100,5 +1100,5 @@ return errors === 0; } -module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false} +module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false,"allowErrorHandlerOverride":true} /* c8 ignore stop */ diff --git a/lib/errors.js b/lib/errors.js index fe6b524565f..52d17a110a4 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -64,6 +64,12 @@ const codes = { 500, TypeError ), + FST_ERR_ERROR_HANDLER_ALREADY_SET: createError( + 'FST_ERR_ERROR_HANDLER_ALREADY_SET', + "Error Handler already set in this scope. Set 'allowErrorHandlerOverride: true' to allow overriding.", + 500, + TypeError + ), /** * ContentTypeParser diff --git a/lib/pluginOverride.js b/lib/pluginOverride.js index 17f8c0893b0..3a98e352c83 100644 --- a/lib/pluginOverride.js +++ b/lib/pluginOverride.js @@ -12,7 +12,8 @@ const { kReply, kRequest, kFourOhFour, - kPluginNameChain + kPluginNameChain, + kErrorHandlerAlreadySet } = require('./symbols.js') const Reply = require('./reply') @@ -57,6 +58,7 @@ module.exports = function override (old, fn, opts) { // Track the plugin chain since the root instance. // When an non-encapsulated plugin is added, the chain will be updated. instance[kPluginNameChain] = [fnName] + instance[kErrorHandlerAlreadySet] = false if (instance[kLogSerializers] || opts.logSerializers) { instance[kLogSerializers] = Object.assign(Object.create(instance[kLogSerializers]), opts.logSerializers) diff --git a/lib/symbols.js b/lib/symbols.js index 53c44f37328..cd7da0fb68f 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -56,6 +56,7 @@ const keys = { // This symbol is only meant to be used for fastify tests and should not be used for any other purpose kTestInternals: Symbol('fastify.testInternals'), kErrorHandler: Symbol('fastify.errorHandler'), + kErrorHandlerAlreadySet: Symbol('fastify.errorHandlerAlreadySet'), kChildLoggerFactory: Symbol('fastify.childLoggerFactory'), kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'), kKeepAliveConnections: Symbol('fastify.keepAliveConnections'), diff --git a/lib/warnings.js b/lib/warnings.js index a9cb27f9874..07a620e3991 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -25,6 +25,13 @@ const FSTWRN003 = createWarning({ unlimited: true }) +const FSTWRN004 = createWarning({ + name: 'FastifyWarning', + code: 'FSTWRN004', + message: 'It seems that you are overriding an errorHandler in the same scope, which can lead to subtle bugs.', + unlimited: true +}) + const FSTSEC001 = createWarning({ name: 'FastifySecurity', code: 'FSTSEC001', @@ -35,5 +42,6 @@ const FSTSEC001 = createWarning({ module.exports = { FSTWRN001, FSTWRN003, + FSTWRN004, FSTSEC001 } diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 2fa3562539a..f5930d6c7d6 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -const expectedErrors = 84 +const expectedErrors = 85 test(`should expose ${expectedErrors} errors`, t => { t.plan(1) diff --git a/test/set-error-handler.test.js b/test/set-error-handler.test.js index 1350a139b38..1d6b5f62e93 100644 --- a/test/set-error-handler.test.js +++ b/test/set-error-handler.test.js @@ -2,7 +2,7 @@ const { test } = require('node:test') const Fastify = require('..') -const { FST_ERR_ERROR_HANDLER_NOT_FN } = require('../lib/errors') +const { FST_ERR_ERROR_HANDLER_NOT_FN, FST_ERR_ERROR_HANDLER_ALREADY_SET } = require('../lib/errors') test('setErrorHandler should throw an error if the handler is not a function', t => { t.plan(1) @@ -10,3 +10,60 @@ test('setErrorHandler should throw an error if the handler is not a function', t t.assert.throws(() => fastify.setErrorHandler('not a function'), new FST_ERR_ERROR_HANDLER_NOT_FN()) }) + +test('setErrorHandler can be set independently in parent and child scopes', async t => { + t.plan(1) + + const fastify = Fastify() + + t.assert.doesNotThrow(() => { + fastify.setErrorHandler(() => {}) + fastify.register(async (child) => { + child.setErrorHandler(() => {}) + }) + }) +}) + +test('setErrorHandler can be overriden if allowErrorHandlerOverride is set to true', async t => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(async (child) => { + child.setErrorHandler(() => {}) + t.assert.doesNotThrow(() => child.setErrorHandler(() => {})) + }) + + fastify.setErrorHandler(() => {}) + t.assert.doesNotThrow(() => fastify.setErrorHandler(() => {})) + + await fastify.ready() +}) + +test('if `allowErrorHandlerOverride` is disabled, setErrorHandler should throw if called more than once in the same scope', t => { + t.plan(1) + + const fastify = Fastify({ + allowErrorHandlerOverride: false + }) + + fastify.setErrorHandler(() => {}) + t.assert.throws(() => fastify.setErrorHandler(() => {}), new FST_ERR_ERROR_HANDLER_ALREADY_SET()) +}) + +test('if `allowErrorHandlerOverride` is disabled, setErrorHandler should throw if called more than once in the same scope 2', async t => { + t.plan(1) + + const fastify = Fastify({ + allowErrorHandlerOverride: false + }) + t.after(() => fastify.close()) + + fastify.register(async (child) => { + child.setErrorHandler(() => {}) + t.assert.throws(() => child.setErrorHandler(() => {}), new FST_ERR_ERROR_HANDLER_ALREADY_SET()) + }) + + await fastify.ready() +}) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index d482acaf5ee..9ebef411a7e 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -273,3 +273,6 @@ expectType(fastify.errorCodes) fastify({ allowUnsafeRegex: true }) fastify({ allowUnsafeRegex: false }) expectError(fastify({ allowUnsafeRegex: 'invalid' })) + +expectAssignable(fastify({ allowErrorHandlerOverride: true })) +expectAssignable(fastify({ allowErrorHandlerOverride: false })) From b157eae4b8ae6796645f76900144bcc3c9104429 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 9 Jun 2025 14:46:20 +0200 Subject: [PATCH 1050/1295] chore: remove simple-get from search test (#6158) --- test/http-methods/search.test.js | 149 ++++++++++++++----------------- 1 file changed, 67 insertions(+), 82 deletions(-) diff --git a/test/http-methods/search.test.js b/test/http-methods/search.test.js index 43fcd442d45..7e0cdcf58b9 100644 --- a/test/http-methods/search.test.js +++ b/test/http-methods/search.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('SEARCH', { hasBody: true }) @@ -130,119 +129,105 @@ test('search test', async t => { t.after(() => { fastify.close() }) const url = `http://localhost:${fastify.server.address().port}` - await t.test('request - search', (t, done) => { + await t.test('request - search', async t => { t.plan(4) - sget({ - method: 'SEARCH', - url - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() + const result = await fetch(url, { + method: 'SEARCH' }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - await t.test('request search params schema', (t, done) => { + await t.test('request search params schema', async t => { t.plan(4) - sget({ - method: 'SEARCH', - url: `${url}/params/world/123` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) - done() + const result = await fetch(`${url}/params/world/123`, { + method: 'SEARCH' }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) }) - await t.test('request search params schema error', (t, done) => { + await t.test('request search params schema error', async t => { t.plan(3) - sget({ - method: 'SEARCH', - url: `${url}/params/world/string` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'params/test must be integer', - statusCode: 400 - }) - done() + const result = await fetch(`${url}/params/world/string`, { + method: 'SEARCH' + }) + const body = await result.text() + t.assert.strictEqual(result.status, 400) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'params/test must be integer', + statusCode: 400 }) }) - await t.test('request search querystring schema', (t, done) => { + await t.test('request search querystring schema', async t => { t.plan(4) - sget({ - method: 'SEARCH', - url: `${url}/query?hello=123` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) - done() + const result = await fetch(`${url}/query?hello=123`, { + method: 'SEARCH' }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) }) - await t.test('request search querystring schema error', (t, done) => { + await t.test('request search querystring schema error', async t => { t.plan(3) - sget({ - method: 'SEARCH', - url: `${url}/query?hello=world` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'querystring/hello must be integer', - statusCode: 400 - }) - done() + const result = await fetch(`${url}/query?hello=world`, { + method: 'SEARCH' + }) + const body = await result.text() + t.assert.strictEqual(result.status, 400) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'querystring/hello must be integer', + statusCode: 400 }) }) - await t.test('request search body schema', (t, done) => { + await t.test('request search body schema', async t => { t.plan(4) const replyBody = { foo: 'bar', test: 5 } - sget({ + const result = await fetch(`${url}/body`, { method: 'SEARCH', - url: `${url}/body`, body: JSON.stringify(replyBody), headers: { 'content-type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), replyBody) - done() }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), replyBody) }) - await t.test('request search body schema error', (t, done) => { + await t.test('request search body schema error', async t => { t.plan(4) - sget({ + const result = await fetch(`${url}/body`, { method: 'SEARCH', - url: `${url}/body`, body: JSON.stringify({ foo: 'bar', test: 'test' }), headers: { 'content-type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'body/test must be integer', - statusCode: 400 - }) - done() + }) + const body = await result.text() + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'body/test must be integer', + statusCode: 400 }) }) }) From fc4c38c593cb702de598496cc20323befdb60475 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 9 Jun 2025 14:47:29 +0200 Subject: [PATCH 1051/1295] chore: remove simple get from secure with fallback test (#6162) --- test/http2/secure-with-fallback.test.js | 55 +++++++++++++------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/test/http2/secure-with-fallback.test.js b/test/http2/secure-with-fallback.test.js index d1e8511f05b..651f3b28da9 100644 --- a/test/http2/secure-with-fallback.test.js +++ b/test/http2/secure-with-fallback.test.js @@ -3,10 +3,10 @@ const { test } = require('node:test') const Fastify = require('../..') const h2url = require('h2url') -const sget = require('simple-get').concat const msg = { hello: 'world' } const { buildCertificate } = require('../build-certificate') +const { Agent } = require('undici') test.before(buildCertificate) test('secure with fallback', async (t) => { @@ -41,12 +41,12 @@ test('secure with fallback', async (t) => { t.after(() => { fastify.close() }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) await t.test('https get error', async (t) => { t.plan(1) - const url = `https://localhost:${fastify.server.address().port}/error` + const url = `${fastifyServer}/error` const res = await h2url.concat({ url }) t.assert.strictEqual(res.headers[':status'], 500) @@ -55,9 +55,8 @@ test('secure with fallback', async (t) => { await t.test('https post', async (t) => { t.plan(2) - const url = `https://localhost:${fastify.server.address().port}` const res = await h2url.concat({ - url, + url: fastifyServer, method: 'POST', body: JSON.stringify({ hello: 'http2' }), headers: { @@ -72,39 +71,41 @@ test('secure with fallback', async (t) => { await t.test('https get request', async (t) => { t.plan(3) - const url = `https://localhost:${fastify.server.address().port}` - const res = await h2url.concat({ url }) + const res = await h2url.concat({ url: fastifyServer }) t.assert.strictEqual(res.headers[':status'], 200) t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(msg).length) t.assert.deepStrictEqual(JSON.parse(res.body), msg) }) - await t.test('http1 get request', (t, done) => { + await t.test('http1 get request', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() + const result = await fetch(fastifyServer, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) }) + + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), msg) }) - await t.test('http1 get error', (t, done) => { + await t.test('http1 get error', async t => { t.plan(2) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port + '/error', - rejectUnauthorized: false - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - done() + const result = await fetch(`${fastifyServer}/error`, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) }) }) From 74bd660478f36b3510e36b0fcbbc6d3ee8612ff4 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 9 Jun 2025 15:41:43 +0200 Subject: [PATCH 1052/1295] chore: removed simple-get from als test (#6187) --- test/als.test.js | 68 ++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/test/als.test.js b/test/als.test.js index 91a78e8cff4..861a8d5ce1f 100644 --- a/test/als.test.js +++ b/test/als.test.js @@ -3,10 +3,9 @@ const { AsyncLocalStorage } = require('node:async_hooks') const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat -test('Async Local Storage test', (t, done) => { - t.plan(13) +test('Async Local Storage test', async (t) => { + t.plan(12) if (!AsyncLocalStorage) { t.skip('AsyncLocalStorage not available, skipping test') process.exit(0) @@ -15,6 +14,8 @@ test('Async Local Storage test', (t, done) => { const storage = new AsyncLocalStorage() const app = Fastify({ logger: false }) + t.after(() => app.close()) + let counter = 0 app.addHook('onRequest', (req, reply, next) => { const id = counter++ @@ -33,45 +34,32 @@ test('Async Local Storage test', (t, done) => { reply.send({ id }) }) - app.listen({ port: 0 }, function (err, address) { - t.assert.ifError(err) + const fastifyServer = await app.listen({ port: 0 }) - sget({ - method: 'POST', - url: 'http://localhost:' + app.server.address().port, - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { id: 0 }) + // First POST request + const result1 = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ hello: 'world' }) + }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.deepStrictEqual(await result1.json(), { id: 0 }) - sget({ - method: 'POST', - url: 'http://localhost:' + app.server.address().port, - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { id: 1 }) + const result2 = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ hello: 'world' }) + }) + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.deepStrictEqual(await result2.json(), { id: 1 }) - sget({ - method: 'GET', - url: 'http://localhost:' + app.server.address().port, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { id: 2 }) - app.close() - done() - }) - }) - }) + // GET request + const result3 = await fetch(fastifyServer, { + method: 'GET' }) + t.assert.ok(result3.ok) + t.assert.strictEqual(result3.status, 200) + t.assert.deepStrictEqual(await result3.json(), { id: 2 }) }) From eb428803e9c0b3b482fcfd094fa55c7c81ff9c30 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 9 Jun 2025 19:32:36 +0200 Subject: [PATCH 1053/1295] chore: remove simple-get from listen 4 (#6173) --- test/listen.4.test.js | 74 ++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/test/listen.4.test.js b/test/listen.4.test.js index e1214b7e274..c73c1b2e19f 100644 --- a/test/listen.4.test.js +++ b/test/listen.4.test.js @@ -3,10 +3,8 @@ const { test, before } = require('node:test') const dns = require('node:dns').promises const dnsCb = require('node:dns') -const sget = require('simple-get').concat const Fastify = require('../fastify') const helper = require('./helper') -const { waitForCb } = require('./toolkit') let localhostForURL @@ -90,7 +88,7 @@ test('listen logs the port as info', async t => { test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => { const localAddresses = await dns.lookup('localhost', { all: true }) - t.plan(2 * localAddresses.length) + t.plan(3 * localAddresses.length) const app = Fastify() app.get('/', async () => 'hello localhost') @@ -98,52 +96,42 @@ test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => { await app.listen({ port: 0, host: 'localhost' }) for (const lookup of localAddresses) { - await new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: getUrl(app, lookup) - }, (err, response, body) => { - if (err) { return reject(err) } - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), 'hello localhost') - resolve() - }) + const result = await fetch(getUrl(app, lookup), { + method: 'GET' }) + + t.assert.ok(result.ok) + t.assert.deepEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), 'hello localhost') } }) -test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', (t, done) => { - dnsCb.lookup('localhost', { all: true }, (err, lookups) => { - t.plan(2 + (3 * lookups.length)) - t.assert.ifError(err) - - const app = Fastify() - app.get('/', async () => 'hello localhost') - app.listen({ port: 0, host: 'localhost' }, (err) => { - t.assert.ifError(err) - t.after(() => app.close()) - - const { stepIn, patience } = waitForCb({ steps: lookups.length }) - - // Loop over each lookup and perform the assertions - if (lookups.length > 0) { - for (const lookup of lookups) { - sget({ - method: 'GET', - url: getUrl(app, lookup) - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), 'hello localhost') - // Call stepIn to report that a request has been completed - stepIn() - }) - } - // When all requests have been completed, call done - patience.then(() => done()) - } +test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', async (t) => { + const lookups = await new Promise((resolve, reject) => { + dnsCb.lookup('localhost', { all: true }, (err, lookups) => { + if (err) return reject(err) + resolve(lookups) }) }) + + t.plan(3 * lookups.length) + + const app = Fastify() + app.get('/', async () => 'hello localhost') + t.after(() => app.close()) + + await app.listen({ port: 0, host: 'localhost' }) + + // Loop over each lookup and perform the assertions + for (const lookup of lookups) { + const result = await fetch(getUrl(app, lookup), { + method: 'GET' + }) + + t.assert.ok(result.ok) + t.assert.deepEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), 'hello localhost') + } }) test('addresses getter', async t => { From 16c2c3db5cf0fedba34860ae68399ae7f82a0787 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 10 Jun 2025 16:55:01 +0200 Subject: [PATCH 1054/1295] fix: do not freeze request.routeOptions (#6141) Signed-off-by: Matteo Collina Co-authored-by: Manuel Spigolon --- lib/request.js | 13 +++-------- test/request-error.test.js | 46 -------------------------------------- 2 files changed, 3 insertions(+), 56 deletions(-) diff --git a/lib/request.js b/lib/request.js index a0bf5dc8325..66704d42745 100644 --- a/lib/request.js +++ b/lib/request.js @@ -188,19 +188,12 @@ Object.defineProperties(Request.prototype, { exposeHeadRoute: context.exposeHeadRoute, prefixTrailingSlash: context.prefixTrailingSlash, handler: context.handler, + config: context.config, + schema: context.schema, version } - Object.defineProperties(options, { - config: { - get: () => context.config - }, - schema: { - get: () => context.schema - } - }) - - return Object.freeze(options) + return options } }, is404: { diff --git a/test/request-error.test.js b/test/request-error.test.js index cddb4267849..58f35d9dcd3 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -323,52 +323,6 @@ test('default clientError replies with bad request on reused keep-alive connecti }) }) -test('request.routeOptions should be immutable', (t, done) => { - t.plan(14) - const fastify = Fastify() - const handler = function (req, res) { - t.assert.strictEqual('POST', req.routeOptions.method) - t.assert.strictEqual('/', req.routeOptions.url) - t.assert.throws(() => { req.routeOptions = null }, new TypeError('Cannot set property routeOptions of # which has only a getter')) - t.assert.throws(() => { req.routeOptions.method = 'INVALID' }, new TypeError('Cannot assign to read only property \'method\' of object \'#\'')) - t.assert.throws(() => { req.routeOptions.url = '//' }, new TypeError('Cannot assign to read only property \'url\' of object \'#\'')) - t.assert.throws(() => { req.routeOptions.bodyLimit = 0xDEADBEEF }, new TypeError('Cannot assign to read only property \'bodyLimit\' of object \'#\'')) - t.assert.throws(() => { req.routeOptions.attachValidation = true }, new TypeError('Cannot assign to read only property \'attachValidation\' of object \'#\'')) - t.assert.throws(() => { req.routeOptions.logLevel = 'invalid' }, new TypeError('Cannot assign to read only property \'logLevel\' of object \'#\'')) - t.assert.throws(() => { req.routeOptions.version = '95.0.1' }, new TypeError('Cannot assign to read only property \'version\' of object \'#\'')) - t.assert.throws(() => { req.routeOptions.prefixTrailingSlash = true }, new TypeError('Cannot assign to read only property \'prefixTrailingSlash\' of object \'#\'')) - t.assert.throws(() => { req.routeOptions.newAttribute = {} }, new TypeError('Cannot add property newAttribute, object is not extensible')) - - for (const key of Object.keys(req.routeOptions)) { - if (typeof req.routeOptions[key] === 'object' && req.routeOptions[key] !== null) { - t.fail('Object.freeze must run recursively on nested structures to ensure that routeOptions is immutable.') - } - } - - res.send({}) - } - fastify.post('/', { - bodyLimit: 1000, - handler - }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) - - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) - }) -}) - test('request.routeOptions.method is an uppercase string /1', (t, done) => { t.plan(4) const fastify = Fastify() From 3f2b984673f7f93c144260eebf73fefb28185b09 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Wed, 11 Jun 2025 11:51:44 +0200 Subject: [PATCH 1055/1295] chore: removed simple-get from sync-delay-request tests (#6212) --- .../sync-delay-request.test.js | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/test/diagnostics-channel/sync-delay-request.test.js b/test/diagnostics-channel/sync-delay-request.test.js index f9b9f6f5c7e..f83bffec1ee 100644 --- a/test/diagnostics-channel/sync-delay-request.test.js +++ b/test/diagnostics-channel/sync-delay-request.test.js @@ -2,13 +2,11 @@ const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const sget = require('simple-get').concat const Fastify = require('../..') -const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel sync events fire in expected order', (t, done) => { +test('diagnostics channel sync events fire in expected order', async t => { t.plan(10) let callOrder = 0 let firstEncounteredMessage @@ -40,19 +38,12 @@ test('diagnostics channel sync events fire in expected order', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const result = await fetch(fastifyServer + '/') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) From 3bf4f10c660a222bb34e3951a3315ea47652747f Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 12 Jun 2025 15:18:22 +0200 Subject: [PATCH 1056/1295] chore: removed simple-get from output-validation tests (#6213) Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- test/output-validation.test.js | 50 ++++++++++++++++------------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/test/output-validation.test.js b/test/output-validation.test.js index ee4e60937f5..91ad938de73 100644 --- a/test/output-validation.test.js +++ b/test/output-validation.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('..')() const opts = { @@ -90,37 +89,33 @@ test('unlisted response code', t => { }) test('start server and run tests', async (t) => { - await fastify.listen({ port: 0 }) - const baseUrl = 'http://localhost:' + fastify.server.address().port + const fastifyServer = await fastify.listen({ port: 0 }) t.after(() => fastify.close()) - function sgetAsync (opts) { - return new Promise((resolve, reject) => { - sget(opts, (err, res, body) => { - if (err) return reject(err) - resolve({ res, body }) - }) - }) - } - await test('shorthand - string get ok', async (t) => { - const { res, body } = await sgetAsync({ method: 'GET', url: `${baseUrl}/string` }) - t.assert.strictEqual(res.statusCode, 200) - t.assert.strictEqual(res.headers['content-length'], '' + body.length) + const result = await fetch(fastifyServer + '/string') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) await test('shorthand - number get ok', async (t) => { - const { res, body } = await sgetAsync({ method: 'GET', url: `${baseUrl}/number` }) - t.assert.strictEqual(res.statusCode, 201) - t.assert.strictEqual(res.headers['content-length'], '' + body.length) + const result = await fetch(fastifyServer + '/number') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 201) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 55 }) }) await test('shorthand - wrong-object-for-schema', async (t) => { - const { res, body } = await sgetAsync({ method: 'GET', url: `${baseUrl}/wrong-object-for-schema` }) - t.assert.strictEqual(res.statusCode, 500) - t.assert.strictEqual(res.headers['content-length'], '' + body.length) + const result = await fetch(fastifyServer + '/wrong-object-for-schema') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { statusCode: 500, error: 'Internal Server Error', @@ -129,14 +124,17 @@ test('start server and run tests', async (t) => { }) await test('shorthand - empty', async (t) => { - const { res } = await sgetAsync({ method: 'GET', url: `${baseUrl}/empty` }) - t.assert.strictEqual(res.statusCode, 204) + const result = await fetch(fastifyServer + '/empty') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 204) }) await test('shorthand - 400', async (t) => { - const { res, body } = await sgetAsync({ method: 'GET', url: `${baseUrl}/400` }) - t.assert.strictEqual(res.statusCode, 400) - t.assert.strictEqual(res.headers['content-length'], '' + body.length) + const result = await fetch(fastifyServer + '/400') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) t.assert.deepStrictEqual(JSON.parse(body), { hello: 'DOOM' }) }) }) From 1b46ddf206a920f15cabeaaa3c3be863c00bd7bd Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 12 Jun 2025 15:19:18 +0200 Subject: [PATCH 1057/1295] chore: removed simple-get from async-delay-request tests (#6211) Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- .../async-delay-request.test.js | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/test/diagnostics-channel/async-delay-request.test.js b/test/diagnostics-channel/async-delay-request.test.js index 6b3303bc1ad..d86ae124fe5 100644 --- a/test/diagnostics-channel/async-delay-request.test.js +++ b/test/diagnostics-channel/async-delay-request.test.js @@ -2,13 +2,11 @@ const diagnostics = require('node:diagnostics_channel') const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('../..') -const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel async events fire in expected order', (t, done) => { +test('diagnostics channel async events fire in expected order', async t => { t.plan(19) let callOrder = 0 let firstEncounteredMessage @@ -56,19 +54,12 @@ test('diagnostics channel async events fire in expected order', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) + t.after(() => { fastify.close() }) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const result = await fetch(fastifyServer + '/') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) From f1bc0c3c1cb136a7610595b2329f5a112e9f1222 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 12 Jun 2025 15:19:56 +0200 Subject: [PATCH 1058/1295] chore: removed simple-get from body-limit tests (#6209) --- test/body-limit.test.js | 106 ++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 65 deletions(-) diff --git a/test/body-limit.test.js b/test/body-limit.test.js index 108c6bc4411..1eece839251 100644 --- a/test/body-limit.test.js +++ b/test/body-limit.test.js @@ -1,12 +1,11 @@ 'use strict' const Fastify = require('../fastify') -const sget = require('simple-get').concat const zlib = require('node:zlib') const { test } = require('node:test') -test('bodyLimit', (t, done) => { - t.plan(5) +test('bodyLimit', async t => { + t.plan(4) try { Fastify({ bodyLimit: 1.3 }) @@ -28,22 +27,17 @@ test('bodyLimit', (t, done) => { reply.send({ error: 'handler should not be called' }) }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 413) - done() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 413) }) test('bodyLimit is applied to decoded content', async (t) => { @@ -114,8 +108,8 @@ test('bodyLimit is applied to decoded content', async (t) => { }) }) -test('default request.routeOptions.bodyLimit should be 1048576', (t, done) => { - t.plan(4) +test('default request.routeOptions.bodyLimit should be 1048576', async t => { + t.plan(3) const fastify = Fastify() fastify.post('/default-bodylimit', { handler (request, reply) { @@ -123,26 +117,20 @@ test('default request.routeOptions.bodyLimit should be 1048576', (t, done) => { reply.send({ }) } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/default-bodylimit', - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) + const result = await fetch(fastifyServer + '/default-bodylimit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) -test('request.routeOptions.bodyLimit should be equal to route limit', (t, done) => { - t.plan(4) +test('request.routeOptions.bodyLimit should be equal to route limit', async t => { + t.plan(3) const fastify = Fastify({ bodyLimit: 1 }) fastify.post('/route-limit', { bodyLimit: 1000, @@ -151,26 +139,20 @@ test('request.routeOptions.bodyLimit should be equal to route limit', (t, done) reply.send({}) } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/route-limit', - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) + const result = await fetch(fastifyServer + '/route-limit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) -test('request.routeOptions.bodyLimit should be equal to server limit', (t, done) => { - t.plan(4) +test('request.routeOptions.bodyLimit should be equal to server limit', async t => { + t.plan(3) const fastify = Fastify({ bodyLimit: 100 }) fastify.post('/server-limit', { handler (request, reply) { @@ -178,20 +160,14 @@ test('request.routeOptions.bodyLimit should be equal to server limit', (t, done) reply.send({}) } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/server-limit', - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) + const result = await fetch(fastifyServer + '/server-limit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) From b853818e57dec01a1f18c89c942a192b3f8abe31 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 12 Jun 2025 15:22:22 +0200 Subject: [PATCH 1059/1295] chore: removed simple-get from trust-proxy tests (#6205) --- test/trust-proxy.test.js | 90 ++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/test/trust-proxy.test.js b/test/trust-proxy.test.js index d1b698ba14d..0153e631605 100644 --- a/test/trust-proxy.test.js +++ b/test/trust-proxy.test.js @@ -1,14 +1,10 @@ 'use strict' const { test, before } = require('node:test') -const sget = require('simple-get').concat const fastify = require('..') const helper = require('./helper') -const { waitForCb } = require('./toolkit') -const noop = () => {} - -const sgetForwardedRequest = (app, forHeader, path, protoHeader, testCaseDone) => { +const fetchForwardedRequest = async (fastifyServer, forHeader, path, protoHeader) => { const headers = { 'X-Forwarded-For': forHeader, 'X-Forwarded-Host': 'example.com' @@ -16,11 +12,10 @@ const sgetForwardedRequest = (app, forHeader, path, protoHeader, testCaseDone) = if (protoHeader) { headers['X-Forwarded-Proto'] = protoHeader } - sget({ - method: 'GET', - headers, - url: 'http://localhost:' + app.server.address().port + path - }, testCaseDone || noop) + + return fetch(fastifyServer + path, { + headers + }) } const testRequestValues = (t, req, options) => { @@ -52,8 +47,8 @@ before(async function () { [localhost] = await helper.getLoopbackHost() }) -test('trust proxy, not add properties to node req', (t, done) => { - t.plan(14) +test('trust proxy, not add properties to node req', async t => { + t.plan(13) const app = fastify({ trustProxy: true }) @@ -69,20 +64,14 @@ test('trust proxy, not add properties to node req', (t, done) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - app.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - - const completion = waitForCb({ steps: 2 }) - - sgetForwardedRequest(app, '1.1.1.1', '/trustproxy', undefined, completion.stepIn) - sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined, completion.stepIn) + const fastifyServer = await app.listen({ port: 0 }) - completion.patience.then(done) - }) + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxy', undefined) + await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined) }) -test('trust proxy chain', (t, done) => { - t.plan(9) +test('trust proxy chain', async t => { + t.plan(8) const app = fastify({ trustProxy: [localhost, '192.168.1.1'] }) @@ -93,14 +82,12 @@ test('trust proxy chain', (t, done) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - app.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - sgetForwardedRequest(app, '192.168.1.1, 1.1.1.1', '/trustproxychain', undefined, done) - }) + const fastifyServer = await app.listen({ port: 0 }) + await fetchForwardedRequest(fastifyServer, '192.168.1.1, 1.1.1.1', '/trustproxychain', undefined) }) -test('trust proxy function', (t, done) => { - t.plan(9) +test('trust proxy function', async t => { + t.plan(8) const app = fastify({ trustProxy: (address) => address === localhost }) @@ -111,14 +98,12 @@ test('trust proxy function', (t, done) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - app.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - sgetForwardedRequest(app, '1.1.1.1', '/trustproxyfunc', undefined, done) - }) + const fastifyServer = await app.listen({ port: 0 }) + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyfunc', undefined) }) -test('trust proxy number', (t, done) => { - t.plan(10) +test('trust proxy number', async t => { + t.plan(9) const app = fastify({ trustProxy: 1 }) @@ -129,14 +114,12 @@ test('trust proxy number', (t, done) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - app.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined, done) - }) + const fastifyServer = await app.listen({ port: 0 }) + await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined) }) -test('trust proxy IP addresses', (t, done) => { - t.plan(10) +test('trust proxy IP addresses', async t => { + t.plan(9) const app = fastify({ trustProxy: `${localhost}, 2.2.2.2` }) @@ -147,14 +130,12 @@ test('trust proxy IP addresses', (t, done) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - app.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - sgetForwardedRequest(app, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs', undefined, done) - }) + const fastifyServer = await app.listen({ port: 0 }) + await fetchForwardedRequest(fastifyServer, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs', undefined) }) -test('trust proxy protocol', (t, done) => { - t.plan(31) +test('trust proxy protocol', async t => { + t.plan(30) const app = fastify({ trustProxy: true }) @@ -173,16 +154,9 @@ test('trust proxy protocol', (t, done) => { reply.code(200).send({ ip: req.ip, host: req.host }) }) - app.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - - const completion = waitForCb({ steps: 3 }) - sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocol', 'lorem', completion.stepIn) - sgetForwardedRequest(app, '1.1.1.1', '/trustproxynoprotocol', undefined, completion.stepIn) + const fastifyServer = await app.listen({ port: 0 }) - // Allow for sgetForwardedRequest requests above to finish - setTimeout(() => sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor', completion.stepIn)) - - completion.patience.then(done) - }) + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyprotocol', 'lorem') + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxynoprotocol', undefined) + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor') }) From 1fc4689a30222e267d0047ab7dc12ec4c6f6a0bc Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 12 Jun 2025 15:22:47 +0200 Subject: [PATCH 1060/1295] chore: removed simple-get from proppatch tests (#6200) Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- test/http-methods/proppatch.test.js | 52 +++++++++++++---------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/test/http-methods/proppatch.test.js b/test/http-methods/proppatch.test.js index 0f4417b2535..d7ea0a612eb 100644 --- a/test/http-methods/proppatch.test.js +++ b/test/http-methods/proppatch.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../')() fastify.addHttpMethod('PROPPATCH', { hasBody: true }) @@ -62,50 +61,45 @@ test('shorthand - proppatch', t => { }) test('proppatch test', async t => { - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) t.after(() => { fastify.close() }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - await t.test('request with body - proppatch', (t, done) => { + await t.test('request with body - proppatch', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'PROPPATCH', headers: { 'content-type': 'text/plain' }, - body: bodySample, - method: 'PROPPATCH' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() + body: bodySample }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request with body and no content type (415 error) - proppatch', (t, done) => { + await t.test('request with body and no content type (415 error) - proppatch', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'PROPPATCH', body: bodySample, - method: 'PROPPATCH' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() + headers: { 'content-type': undefined } }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request without body - proppatch', (t, done) => { + await t.test('request without body - proppatch', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + const result = await fetch(`${fastifyServer}/test/a.txt`, { method: 'PROPPATCH' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) }) From 8226d14b7de7077ec263e428d5abeb33be0298de Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 12 Jun 2025 15:23:09 +0200 Subject: [PATCH 1061/1295] chore(ci): cleanup citgm.yml (#6195) Signed-off-by: Manuel Spigolon Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- .github/workflows/citgm.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml index 05a63ad7612..7b97c59b8f7 100644 --- a/.github/workflows/citgm.yml +++ b/.github/workflows/citgm.yml @@ -19,7 +19,6 @@ jobs: package: - '@fastify/accepts' - '@fastify/accepts-serializer' - - '@fastify/any-schema' - '@fastify/auth' - '@fastify/autoload' - '@fastify/awilix' @@ -33,7 +32,6 @@ jobs: - '@fastify/cors' - '@fastify/csrf-protection' - '@fastify/diagnostics-channel' - - '@fastify/early-hints' # - '@fastify/elasticsearch' - '@fastify/env' - '@fastify/etag' @@ -66,7 +64,6 @@ jobs: - '@fastify/secure-session' - '@fastify/sensible' - '@fastify/session' - - '@fastify/soap-client' - '@fastify/static' - '@fastify/swagger' - '@fastify/swagger-ui' From 0a0b9691269a72ec843218aeb08c87ff43652aee Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 12 Jun 2025 15:23:34 +0200 Subject: [PATCH 1062/1295] chore: removed simple-get from https tests (#6197) --- test/https/https.test.js | 109 ++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/test/https/https.test.js b/test/https/https.test.js index fe4f28ecbb3..1e42c1fd6f6 100644 --- a/test/https/https.test.js +++ b/test/https/https.test.js @@ -1,10 +1,11 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat +const { request } = require('undici') const Fastify = require('../..') const { buildCertificate } = require('../build-certificate') +const { Agent } = require('undici') test.before(buildCertificate) test('https', async (t) => { @@ -35,43 +36,45 @@ test('https', async (t) => { t.after(() => { fastify.close() }) - await t.test('https get request', (t, done) => { + await t.test('https get request', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() + const result = await fetch('https://localhost:' + fastify.server.address().port, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - await t.test('https get request without trust proxy - protocol', (t, done) => { - t.plan(4) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port + '/proto', - rejectUnauthorized: false - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body), { proto: 'https' }) + await t.test('https get request without trust proxy - protocol', async t => { + t.plan(3) + const result1 = await fetch(`${'https://localhost:' + fastify.server.address().port}/proto`, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) }) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port + '/proto', - rejectUnauthorized: false, + t.assert.ok(result1.ok) + t.assert.deepStrictEqual(await result1.json(), { proto: 'https' }) + + const result2 = await fetch(`${'https://localhost:' + fastify.server.address().port}/proto`, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }), headers: { 'x-forwarded-proto': 'lorem' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body), { proto: 'https' }) - done() }) + t.assert.deepStrictEqual(await result2.json(), { proto: 'https' }) }) }) @@ -98,36 +101,36 @@ test('https - headers', async (t) => { await fastify.listen({ port: 0 }) - await t.test('https get request', (t, done) => { - t.plan(4) - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - const parsedBody = JSON.parse(body) - t.assert.strictEqual(parsedBody.hostname, 'localhost') - t.assert.strictEqual(parsedBody.port, fastify.server.address().port) - done() + await t.test('https get request', async t => { + t.plan(3) + const result = await fetch('https://localhost:' + fastify.server.address().port, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hostname: 'localhost', port: fastify.server.address().port, hello: 'world' }) }) - await t.test('https get request - test port fall back', (t, done) => { - t.plan(3) - sget({ + + await t.test('https get request - test port fall back', async t => { + t.plan(2) + + const result = await request('https://localhost:' + fastify.server.address().port, { method: 'GET', headers: { host: 'example.com' }, - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - const parsedBody = JSON.parse(body) - t.assert.strictEqual(parsedBody.port, null) - done() + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) }) + + t.assert.strictEqual(result.statusCode, 200) + t.assert.deepStrictEqual(await result.body.json(), { hello: 'world', hostname: 'example.com', port: null }) }) }) From a6b357cc8958357b6c15ae55dcf5dbb38d4b3e9c Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 12 Jun 2025 15:26:32 +0200 Subject: [PATCH 1063/1295] chore: removed simple-get from lock test (#6186) --- test/http-methods/lock.test.js | 62 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/test/http-methods/lock.test.js b/test/http-methods/lock.test.js index dad5c486670..b0a00b668bd 100644 --- a/test/http-methods/lock.test.js +++ b/test/http-methods/lock.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('LOCK', { hasBody: true }) @@ -57,52 +56,53 @@ test('can be created - lock', t => { }) test('lock test', async t => { - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) t.after(() => { fastify.close() }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - await t.test('request with body - lock', (t, done) => { + await t.test('request with body - lock', async (t) => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'LOCK', headers: { 'content-type': 'text/plain' }, - body: bodySample, - method: 'LOCK' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() + body: bodySample }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request with body and no content type (415 error) - lock', (t, done) => { + await t.test('request with body and no content type (415 error) - lock', async (t) => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'LOCK', body: bodySample, - method: 'LOCK' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() + headers: { 'content-type': undefined } }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request without body - lock', (t, done) => { + await t.test('request without body - lock', async (t) => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test/a.txt`, - headers: { 'content-type': 'text/plain' }, - method: 'LOCK' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() + + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'LOCK', + headers: { 'content-type': 'text/plain' } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) }) From 0111d0a30052b060c79b534295e309513d74ebd6 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 12 Jun 2025 15:27:47 +0200 Subject: [PATCH 1064/1295] Bumped v5.4.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index c0789b45af8..aa0b36551f7 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.3.3' +const VERSION = '5.4.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 870b656ea18..164a9f5bfa2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.3.3", + "version": "5.4.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 1846108637443de03938a1009c430076885d8712 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 13 Jun 2025 09:27:45 +0200 Subject: [PATCH 1065/1295] docs: fix markdown linting issue (#6175) Signed-off-by: Aras Abbasi --- docs/Guides/Delay-Accepting-Requests.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/Guides/Delay-Accepting-Requests.md b/docs/Guides/Delay-Accepting-Requests.md index 6505775a3f3..acb2fdaa952 100644 --- a/docs/Guides/Delay-Accepting-Requests.md +++ b/docs/Guides/Delay-Accepting-Requests.md @@ -527,11 +527,14 @@ Retry-After: 5000 Then we attempted a new request (`req-2`), which was a `GET /ping`. As expected, since that was not one of the requests we asked our plugin to filter, it succeeded. That could also be used as a means of informing an interested party -whether or not we were ready to serve requests (although `/ping` is more -commonly associated with *liveness* checks and that would be the responsibility -of a *readiness* check -- the curious reader can get more info on these -[terms](https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes)) -here with the `ready` field. Below is the response to that request: +whether or not we were ready to serve requests with the `ready` field. Although +`/ping` is more commonly associated with *liveness* checks and that would be +the responsibility of a *readiness* check. The curious reader can get more info +on these terms in the article +["Kubernetes best practices: Setting up health checks with readiness and liveness probes"]( +https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes). + +Below is the response to that request: ```sh HTTP/1.1 200 OK From a747da88a302b8cf1935ba2d37780c4a0485e930 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Fri, 13 Jun 2025 11:23:54 +0200 Subject: [PATCH 1066/1295] chore: removed simple-get from mkcalendar tests (#6199) --- test/http-methods/mkcalendar.test.js | 117 +++++++++++---------------- 1 file changed, 45 insertions(+), 72 deletions(-) diff --git a/test/http-methods/mkcalendar.test.js b/test/http-methods/mkcalendar.test.js index 91780a707fa..d2221884432 100644 --- a/test/http-methods/mkcalendar.test.js +++ b/test/http-methods/mkcalendar.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('MKCALENDAR', { hasBody: true }) @@ -78,93 +77,67 @@ test('can be created - mkcalendar', (t) => { }) test('mkcalendar test', async t => { - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) t.after(() => { fastify.close() }) - await t.test('request - mkcalendar', (t, done) => { - t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/`, - method: 'MKCALENDAR' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + await t.test('request - mkcalendar', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/`, { + method: 'MKCALENDAR' + }) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request with other path - mkcalendar', (t, done) => { - t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/test`, - method: 'MKCALENDAR' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + await t.test('request with other path - mkcalendar', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/test`, { + method: 'MKCALENDAR' + }) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - await t.test('request with body - mkcalendar', (t, done) => { + await t.test('request with body - mkcalendar', async t => { t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/test`, - headers: { 'content-type': 'text/plain' }, - body: bodySample, - method: 'MKCALENDAR' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + const result = await fetch(`${fastifyServer}/test`, { + method: 'MKCALENDAR', + headers: { 'content-type': 'text/plain' }, + body: bodySample + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request with body and no content type (415 error) - mkcalendar', (t, done) => { + await t.test('request with body and no content type (415 error) - mkcalendar', async t => { t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/test`, - body: bodySample, - method: 'MKCALENDAR' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + const result = await fetch(`${fastifyServer}/test`, { + method: 'MKCALENDAR', + body: bodySample, + headers: { 'content-type': undefined } + }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request without body - mkcalendar', (t, done) => { + await t.test('request without body - mkcalendar', async t => { t.plan(3) - sget( - { - url: `http://localhost:${fastify.server.address().port}/test`, - method: 'MKCALENDAR' - }, - (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() - } - ) + const result = await fetch(`${fastifyServer}/test`, { + method: 'MKCALENDAR' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) }) From 126ca382169ec86dbb2e5de79921022595a13e80 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Fri, 13 Jun 2025 11:25:12 +0200 Subject: [PATCH 1067/1295] chore: removed simple-get from versioned-routes tests (#6202) --- test/versioned-routes.test.js | 95 ++++++++++++++--------------------- 1 file changed, 39 insertions(+), 56 deletions(-) diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js index e843770f4be..0ec03b1d9f3 100644 --- a/test/versioned-routes.test.js +++ b/test/versioned-routes.test.js @@ -3,7 +3,6 @@ const { test, before } = require('node:test') const helper = require('./helper') const Fastify = require('..') -const sget = require('simple-get').concat const http = require('node:http') const split = require('split2') const append = require('vary').append @@ -233,8 +232,8 @@ test('Versioned route but not version header should return a 404', (t, done) => }) }) -test('Should register a versioned route (server)', (t, done) => { - t.plan(6) +test('Should register a versioned route (server)', async t => { + t.plan(5) const fastify = Fastify() fastify.route({ @@ -246,34 +245,27 @@ test('Should register a versioned route (server)', (t, done) => { } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'Accept-Version': '1.x' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'Accept-Version': '2.x' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) - }) + const result1 = await fetch(fastifyServer, { + headers: { + 'Accept-Version': '1.x' + } + }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.json() + t.assert.deepStrictEqual(body1, { hello: 'world' }) + + const result2 = await fetch(fastifyServer, { + headers: { + 'Accept-Version': '2.x' + } }) + + t.assert.ok(!result2.ok) + t.assert.strictEqual(result2.status, 404) }) test('Shorthand route declaration', (t, done) => { @@ -402,8 +394,8 @@ test('Bad accept version (inject)', (t, done) => { }) }) -test('Bad accept version (server)', (t, done) => { - t.plan(5) +test('Bad accept version (server)', async t => { + t.plan(4) const fastify = Fastify() fastify.route({ @@ -415,33 +407,24 @@ test('Bad accept version (server)', (t, done) => { } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'Accept-Version': 'a.b.c' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'Accept-Version': 12 - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) - }) + const result1 = await fetch(fastifyServer, { + headers: { + 'Accept-Version': 'a.b.c' + } + }) + t.assert.ok(!result1.ok) + t.assert.strictEqual(result1.status, 404) + + const result2 = await fetch(fastifyServer, { + headers: { + 'Accept-Version': '12' + } }) + t.assert.ok(!result2.ok) + t.assert.strictEqual(result2.status, 404) }) test('test log stream', (t, done) => { From 31ef7a4bee18374f9ef9f19618d803ed0534564b Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Fri, 13 Jun 2025 11:25:43 +0200 Subject: [PATCH 1068/1295] chore: removed simple-get from hooks tests (#6210) * chore: removed simple-get from hooks tests * chore: removed simple-get from hooks tests * chore: removed simple-get from hooks tests * chore: removed simple-get from hooks tests * chore: removed simple-get from hooks tests * chore: removed simple-get from hooks tests --------- Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- test/hooks.test.js | 708 +++++++++++++++++---------------------------- 1 file changed, 261 insertions(+), 447 deletions(-) diff --git a/test/hooks.test.js b/test/hooks.test.js index 100adf54c82..0a4e2a7c150 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const stream = require('node:stream') const Fastify = require('..') const fp = require('fastify-plugin') @@ -11,13 +10,13 @@ const symbols = require('../lib/symbols.js') const payload = { hello: 'world' } const proxyquire = require('proxyquire') const { connect } = require('node:net') -const { sleep, getServerUrl } = require('./helper') +const { sleep } = require('./helper') const { waitForCb } = require('./toolkit.js') process.removeAllListeners('warning') -test('hooks', (t, testDone) => { - t.plan(49) +test('hooks', async t => { + t.plan(48) const fastify = Fastify({ exposeHeadRoutes: false }) try { @@ -148,39 +147,23 @@ test('hooks', (t, testDone) => { reply.code(200).send(payload) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - const completion = waitForCb({ steps: 3 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - sget({ - method: 'HEAD', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - completion.stepIn() - }) - sget({ - method: 'DELETE', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const getResult = await fetch(fastifyServer) + t.assert.ok(getResult.ok) + t.assert.strictEqual(getResult.status, 200) + const getBody = await getResult.text() + t.assert.strictEqual(getResult.headers.get('content-length'), '' + getBody.length) + t.assert.deepStrictEqual(JSON.parse(getBody), { hello: 'world' }) + + const headResult = await fetch(fastifyServer, { method: 'HEAD' }) + t.assert.ok(!headResult.ok) + t.assert.strictEqual(headResult.status, 500) + + const deleteResult = await fetch(fastifyServer, { method: 'DELETE' }) + t.assert.ok(!deleteResult.ok) + t.assert.strictEqual(deleteResult.status, 500) }) test('onRequest hook should support encapsulation / 1', (t, testDone) => { @@ -237,8 +220,8 @@ test('onRequest hook should support encapsulation / 2', (t, testDone) => { }) }) -test('onRequest hook should support encapsulation / 3', (t, testDone) => { - t.plan(20) +test('onRequest hook should support encapsulation / 3', async t => { + t.plan(19) const fastify = Fastify() fastify.decorate('hello', 'world') @@ -276,37 +259,26 @@ test('onRequest hook should support encapsulation / 3', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const firstResult = await fetch(fastifyServer + '/first', { method: 'GET' }) + t.assert.ok(firstResult.ok) + t.assert.strictEqual(firstResult.status, 200) + const firstBody = await firstResult.text() + t.assert.strictEqual(firstResult.headers.get('content-length'), '' + firstBody.length) + t.assert.deepStrictEqual(JSON.parse(firstBody), { hello: 'world' }) + + const secondResult = await fetch(fastifyServer + '/second', { method: 'GET' }) + t.assert.ok(secondResult.ok) + t.assert.strictEqual(secondResult.status, 200) + const secondBody = await secondResult.text() + t.assert.strictEqual(secondResult.headers.get('content-length'), '' + secondBody.length) + t.assert.deepStrictEqual(JSON.parse(secondBody), { hello: 'world' }) }) -test('preHandler hook should support encapsulation / 5', (t, testDone) => { - t.plan(17) +test('preHandler hook should support encapsulation / 5', async t => { + t.plan(16) const fastify = Fastify() t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') @@ -341,32 +313,21 @@ test('preHandler hook should support encapsulation / 5', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const firstResult = await fetch(fastifyServer + '/first') + t.assert.ok(firstResult.ok) + t.assert.strictEqual(firstResult.status, 200) + const firstBody = await firstResult.text() + t.assert.strictEqual(firstResult.headers.get('content-length'), '' + firstBody.length) + t.assert.deepStrictEqual(JSON.parse(firstBody), { hello: 'world' }) + + const secondResult = await fetch(fastifyServer + '/second') + t.assert.ok(secondResult.ok) + t.assert.strictEqual(secondResult.status, 200) + const secondBody = await secondResult.text() + t.assert.strictEqual(secondResult.headers.get('content-length'), '' + secondBody.length) + t.assert.deepStrictEqual(JSON.parse(secondBody), { hello: 'world' }) }) test('onRoute hook should be called / 1', (t, testDone) => { @@ -750,8 +711,8 @@ test('onRoute hook should be called once when prefixTrailingSlash', (t, testDone }) }) -test('onRoute hook should able to change the route url', (t, testDone) => { - t.plan(5) +test('onRoute hook should able to change the route url', async t => { + t.plan(4) const fastify = Fastify({ exposeHeadRoutes: false }) t.after(() => { fastify.close() }) @@ -769,19 +730,12 @@ test('onRoute hook should able to change the route url', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + encodeURI('/foo') - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'here /foo') - testDone() - }) - }) + const result = await fetch(fastifyServer + encodeURI('/foo')) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'here /foo') }) test('onRoute hook that throws should be caught', (t, testDone) => { @@ -951,8 +905,8 @@ test('onResponse hook should support encapsulation / 2', (t, testDone) => { }) }) -test('onResponse hook should support encapsulation / 3', (t, testDone) => { - t.plan(16) +test('onResponse hook should support encapsulation / 3', async t => { + t.plan(15) const fastify = Fastify() t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') @@ -983,32 +937,21 @@ test('onResponse hook should support encapsulation / 3', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const firstResult = await fetch(fastifyServer + '/first', { method: 'GET' }) + t.assert.ok(firstResult.ok) + t.assert.strictEqual(firstResult.status, 200) + const firstBody = await firstResult.text() + t.assert.strictEqual(firstResult.headers.get('content-length'), '' + firstBody.length) + t.assert.deepStrictEqual(JSON.parse(firstBody), { hello: 'world' }) + + const secondResult = await fetch(fastifyServer + '/second') + t.assert.ok(secondResult.ok) + t.assert.strictEqual(secondResult.status, 200) + const secondBody = await secondResult.text() + t.assert.strictEqual(secondResult.headers.get('content-length'), '' + secondBody.length) + t.assert.deepStrictEqual(JSON.parse(secondBody), { hello: 'world' }) }) test('onSend hook should support encapsulation / 1', (t, testDone) => { @@ -1032,8 +975,8 @@ test('onSend hook should support encapsulation / 1', (t, testDone) => { }) }) -test('onSend hook should support encapsulation / 2', (t, testDone) => { - t.plan(16) +test('onSend hook should support encapsulation / 2', async t => { + t.plan(15) const fastify = Fastify() t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') @@ -1064,33 +1007,21 @@ test('onSend hook should support encapsulation / 2', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) + const firstResult = await fetch(fastifyServer + '/first') + t.assert.ok(firstResult.ok) + t.assert.strictEqual(firstResult.status, 200) + const firstBody = await firstResult.text() + t.assert.strictEqual(firstResult.headers.get('content-length'), '' + firstBody.length) + t.assert.deepStrictEqual(JSON.parse(firstBody), { hello: 'world' }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const secondResult = await fetch(fastifyServer + '/second') + t.assert.ok(secondResult.ok) + t.assert.strictEqual(secondResult.status, 200) + const secondBody = await secondResult.text() + t.assert.strictEqual(secondResult.headers.get('content-length'), '' + secondBody.length) + t.assert.deepStrictEqual(JSON.parse(secondBody), { hello: 'world' }) }) test('onSend hook is called after payload is serialized and headers are set', (t, testDone) => { @@ -1312,8 +1243,8 @@ test('clear payload', (t, testDone) => { }) }) -test('onSend hook throws', (t, testDone) => { - t.plan(11) +test('onSend hook throws', async t => { + t.plan(10) const Fastify = proxyquire('..', { './lib/schemas.js': { getSchemaSerializer: (param1, param2, param3) => { @@ -1372,46 +1303,26 @@ test('onSend hook throws', (t, testDone) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 4 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - sget({ - method: 'POST', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - completion.stepIn() - }) - sget({ - method: 'DELETE', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - completion.stepIn() - }) - sget({ - method: 'PUT', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const getResult = await fetch(fastifyServer) + t.assert.ok(getResult.ok) + t.assert.strictEqual(getResult.status, 200) + const getBody = await getResult.text() + t.assert.strictEqual(getResult.headers.get('content-length'), '' + getBody.length) + t.assert.deepStrictEqual(JSON.parse(getBody), { hello: 'world' }) + + const postResult = await fetch(fastifyServer, { method: 'POST' }) + t.assert.ok(!postResult.ok) + t.assert.strictEqual(postResult.status, 500) + + const deleteResult = await fetch(fastifyServer, { method: 'DELETE' }) + t.assert.ok(!deleteResult.ok) + t.assert.strictEqual(deleteResult.status, 500) + + const putResult = await fetch(fastifyServer, { method: 'PUT' }) + t.assert.ok(!putResult.ok) + t.assert.strictEqual(putResult.status, 500) }) test('onSend hook should receive valid request and reply objects if onRequest hook fails', (t, testDone) => { @@ -2501,8 +2412,8 @@ test('preValidation hook should support encapsulation / 2', (t, testDone) => { }) }) -test('preValidation hook should support encapsulation / 3', (t, testDone) => { - t.plan(20) +test('preValidation hook should support encapsulation / 3', async t => { + t.plan(19) const fastify = Fastify() t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') @@ -2541,32 +2452,21 @@ test('preValidation hook should support encapsulation / 3', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const result1 = await fetch(fastifyServer + '/first') + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world' }) + + const result2 = await fetch(fastifyServer + '/second') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { hello: 'world' }) }) test('onError hook', (t, testDone) => { @@ -2669,8 +2569,8 @@ test('onError hook with setErrorHandler', (t, testDone) => { }) }) -test('preParsing hook should run before parsing and be able to modify the payload', (t, testDone) => { - t.plan(5) +test('preParsing hook should run before parsing and be able to modify the payload', async t => { + t.plan(4) const fastify = Fastify() t.after(() => { fastify.close() }) @@ -2690,26 +2590,22 @@ test('preParsing hook should run before parsing and be able to modify the payloa } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'POST', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first', - body: { hello: 'world' }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + JSON.stringify(body).length) - t.assert.deepStrictEqual(body, { hello: 'another world' }) - testDone() - }) + const result = await fetch(fastifyServer + '/first', { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'another world' }) }) -test('preParsing hooks should run in the order in which they are defined', (t, testDone) => { - t.plan(5) +test('preParsing hooks should run in the order in which they are defined', async t => { + t.plan(4) const fastify = Fastify() t.after(() => { fastify.close() }) @@ -2734,26 +2630,22 @@ test('preParsing hooks should run in the order in which they are defined', (t, t } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'POST', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first', - body: { hello: 'world' }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + JSON.stringify(body).length) - t.assert.deepStrictEqual(body, { hello: 'another world' }) - testDone() - }) + const result = await fetch(fastifyServer + '/first', { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'another world' }) }) -test('preParsing hooks should support encapsulation', (t, testDone) => { - t.plan(9) +test('preParsing hooks should support encapsulation', async t => { + t.plan(8) const fastify = Fastify() t.after(() => { fastify.close() }) @@ -2785,36 +2677,29 @@ test('preParsing hooks should support encapsulation', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'POST', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first', - body: { hello: 'world' }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + JSON.stringify(body).length) - t.assert.deepStrictEqual(body, { hello: 'another world' }) - completion.stepIn() - }) - sget({ - method: 'POST', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/second', - body: { hello: 'world' }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + JSON.stringify(body).length) - t.assert.deepStrictEqual(body, { hello: 'encapsulated world' }) - completion.stepIn() - }) - completion.patience.then(testDone) + const result1 = await fetch(fastifyServer + '/first', { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'another world' }) + + const result2 = await fetch(fastifyServer + '/second', { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }) + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { hello: 'encapsulated world' }) }) test('preParsing hook should support encapsulation / 1', (t, testDone) => { @@ -2870,8 +2755,8 @@ test('preParsing hook should support encapsulation / 2', (t, testDone) => { }) }) -test('preParsing hook should support encapsulation / 3', (t, testDone) => { - t.plan(20) +test('preParsing hook should support encapsulation / 3', async t => { + t.plan(19) const fastify = Fastify() t.after(() => { fastify.close() }) fastify.decorate('hello', 'world') @@ -2910,36 +2795,25 @@ test('preParsing hook should support encapsulation / 3', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const result1 = await fetch(fastifyServer + '/first') + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world' }) + + const result2 = await fetch(fastifyServer + '/second') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { hello: 'world' }) }) -test('preSerialization hook should run before serialization and be able to modify the payload', (t, testDone) => { - t.plan(5) +test('preSerialization hook should run before serialization and be able to modify the payload', async t => { + t.plan(4) const fastify = Fastify() t.after(() => { fastify.close() }) @@ -2975,23 +2849,18 @@ test('preSerialization hook should run before serialization and be able to modif } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world1', world: 'ok' }) - testDone() - }) - }) + const result = await fetch(fastifyServer + '/first') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world1', world: 'ok' }) }) -test('preSerialization hook should be able to throw errors which are validated against schema response', (t, testDone) => { +test('preSerialization hook should be able to throw errors which are validated against schema response', async t => { + t.plan(5) const fastify = Fastify() t.after(() => { fastify.close() }) @@ -3027,24 +2896,18 @@ test('preSerialization hook should be able to throw errors which are validated a } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { world: 'error' }) - testDone() - }) - }) + const result = await fetch(fastifyServer + '/first') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { world: 'error' }) }) -test('preSerialization hook which returned error should still run onError hooks', (t, testDone) => { - t.plan(4) +test('preSerialization hook which returned error should still run onError hooks', async t => { + t.plan(3) const fastify = Fastify() t.after(() => { fastify.close() }) @@ -3061,22 +2924,15 @@ test('preSerialization hook which returned error should still run onError hooks' reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - testDone() - }) - }) + const result = await fetch(fastifyServer + '/first') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) }) -test('preSerialization hooks should run in the order in which they are defined', (t, testDone) => { - t.plan(5) +test('preSerialization hooks should run in the order in which they are defined', async t => { + t.plan(4) const fastify = Fastify() t.after(() => { fastify.close() }) @@ -3093,27 +2949,21 @@ test('preSerialization hooks should run in the order in which they are defined', }) fastify.get('/first', (req, reply) => { - reply.send(payload) + reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world21' }) - testDone() - }) - }) + const result = await fetch(fastifyServer + '/first') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world21' }) }) -test('preSerialization hooks should support encapsulation', (t, testDone) => { - t.plan(9) +test('preSerialization hooks should support encapsulation', async t => { + t.plan(8) const fastify = Fastify() t.after(() => { fastify.close() }) @@ -3141,32 +2991,21 @@ test('preSerialization hooks should support encapsulation', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world1' }) - completion.stepIn() - }) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world12' }) - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const result1 = await fetch(fastifyServer + '/first') + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world1' }) + + const result2 = await fetch(fastifyServer + '/second') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { hello: 'world12' }) }) test('onRegister hook should be called / 1', (t, testDone) => { @@ -3378,8 +3217,8 @@ test('reply.send should throw if undefined error is thrown at onSend hook', (t, }) }) -test('onTimeout should be triggered', (t, testDone) => { - t.plan(6) +test('onTimeout should be triggered', async t => { + t.plan(4) const fastify = Fastify({ connectionTimeout: 500 }) t.after(() => { fastify.close() }) @@ -3396,32 +3235,17 @@ test('onTimeout should be triggered', (t, testDone) => { return reply }) - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: address - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completion.stepIn() - }) - sget({ - method: 'GET', - url: `${address}/timeout` - }, (err, response, body) => { - t.assert.ok(err, Error) - t.assert.strictEqual(err.message, 'socket hang up') - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const result1 = await fetch(fastifyServer) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + + await t.assert.rejects(() => fetch(fastifyServer + '/timeout')) }) -test('onTimeout should be triggered and socket _meta is set', (t, testDone) => { - t.plan(6) +test('onTimeout should be triggered and socket _meta is set', async t => { + t.plan(4) const fastify = Fastify({ connectionTimeout: 500 }) t.after(() => { fastify.close() }) @@ -3439,28 +3263,18 @@ test('onTimeout should be triggered and socket _meta is set', (t, testDone) => { return reply }) - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ steps: 2 }) - sget({ - method: 'GET', - url: address - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completion.stepIn() - }) - sget({ - method: 'GET', - url: `${address}/timeout` - }, (err, response, body) => { - t.assert.ok(err, Error) - t.assert.strictEqual(err.message, 'socket hang up') - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const result1 = await fetch(fastifyServer) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + + try { + await fetch(fastifyServer + '/timeout') + t.fail('Should have thrown an error') + } catch (err) { + t.assert.ok(err instanceof Error) + } }) test('registering invalid hooks should throw an error', async t => { From 55c4841507bd796030a0aa754ed51f5865f732a4 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sat, 14 Jun 2025 15:00:47 +0200 Subject: [PATCH 1069/1295] fix: close pipelining flaky test (#6204) --- test/close-pipelining.test.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index 34dd5053d5f..f16c8a08912 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -21,14 +21,22 @@ test('Should return 503 while closing - pipelining', async t => { pipelining: 2 }) - const codes = [200, 200, 503] - const responses = await Promise.all([ + const [firstRequest, secondRequest, thirdRequest] = await Promise.allSettled([ instance.request({ path: '/', method: 'GET' }), instance.request({ path: '/', method: 'GET' }), instance.request({ path: '/', method: 'GET' }) ]) - const actual = responses.map(r => r.statusCode) - t.assert.deepStrictEqual(actual, codes) + t.assert.strictEqual(firstRequest.status, 'fulfilled') + t.assert.strictEqual(secondRequest.status, 'fulfilled') + + t.assert.strictEqual(firstRequest.value.statusCode, 200) + t.assert.strictEqual(secondRequest.value.statusCode, 200) + + if (thirdRequest.status === 'fulfilled') { + t.assert.strictEqual(thirdRequest.value.statusCode, 503) + } else { + t.assert.strictEqual(thirdRequest.reason.code, 'ECONNREFUSED') + } await instance.close() }) From 219931b63faba498117d9415d4ca0b76dd8eddd5 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sat, 14 Jun 2025 20:33:14 +0200 Subject: [PATCH 1070/1295] chore: removed simple-get from unlock test (#6178) --- test/http-methods/unlock.test.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/test/http-methods/unlock.test.js b/test/http-methods/unlock.test.js index 0da02a838ea..7260ba4f07a 100644 --- a/test/http-methods/unlock.test.js +++ b/test/http-methods/unlock.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('UNLOCK') @@ -22,21 +21,18 @@ test('can be created - unlock', t => { }) test('unlock test', async t => { - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) t.after(() => { fastify.close() }) - await t.test('request - unlock', (t, done) => { + await t.test('request - unlock', async t => { t.plan(2) - sget({ - url: `http://localhost:${fastify.server.address().port}/test/a.txt`, + const result = await fetch(`${fastifyServer}/test/a.txt`, { method: 'UNLOCK', headers: { 'Lock-Token': 'urn:uuid:a515cfa4-5da4-22e1-f5b5-00a0451e6bf7' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 204) - done() }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 204) }) }) From b6954fc3a8527d67815b6d5a4be50614bcadb206 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sat, 14 Jun 2025 22:26:17 +0200 Subject: [PATCH 1071/1295] chore: removed simple-get from use-semicolon-delimiter tests (#6203) --- test/use-semicolon-delimiter.test.js | 100 ++++++++++----------------- 1 file changed, 37 insertions(+), 63 deletions(-) diff --git a/test/use-semicolon-delimiter.test.js b/test/use-semicolon-delimiter.test.js index d3e37fb3c75..efefc619e1c 100644 --- a/test/use-semicolon-delimiter.test.js +++ b/test/use-semicolon-delimiter.test.js @@ -2,10 +2,9 @@ const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat -test('use semicolon delimiter default false', (t, done) => { - t.plan(4) +test('use semicolon delimiter default false', async (t) => { + t.plan(2) const fastify = Fastify() @@ -13,23 +12,19 @@ test('use semicolon delimiter default false', (t, done) => { reply.send(req.query) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), {}) - fastify.close() - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar', { + method: 'GET' }) + t.assert.strictEqual(result.status, 200) + const body = await result.json() + t.assert.deepStrictEqual(body, {}) }) -test('use semicolon delimiter set to true', (t, done) => { - t.plan(4) +test('use semicolon delimiter set to true', async (t) => { + t.plan(3) const fastify = Fastify({ useSemicolonDelimiter: true }) @@ -38,26 +33,19 @@ test('use semicolon delimiter set to true', (t, done) => { reply.send(req.query) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { - foo: 'bar' - }) - fastify.close() - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { + foo: 'bar' }) }) -test('use semicolon delimiter set to false', (t, done) => { - t.plan(4) +test('use semicolon delimiter set to false', async (t) => { + t.plan(3) const fastify = Fastify({ useSemicolonDelimiter: false @@ -67,24 +55,17 @@ test('use semicolon delimiter set to false', (t, done) => { reply.send(req.query) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), {}) - fastify.close() - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), {}) }) -test('use semicolon delimiter set to false return 404', (t, done) => { - t.plan(3) +test('use semicolon delimiter set to false return 404', async (t) => { + t.plan(2) const fastify = Fastify({ useSemicolonDelimiter: false @@ -94,17 +75,10 @@ test('use semicolon delimiter set to false return 404', (t, done) => { reply.send(req.query) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/1234;foo=bar' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - fastify.close() - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 404) }) From 96c35c4e27e1c4e2ec907fea01ebf417c0c92626 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sat, 14 Jun 2025 22:26:46 +0200 Subject: [PATCH 1072/1295] chore: removed simple-get from custom-https-server tests (#6201) --- test/https/custom-https-server.test.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/test/https/custom-https-server.test.js b/test/https/custom-https-server.test.js index d1aee450063..ba7a7b65fc3 100644 --- a/test/https/custom-https-server.test.js +++ b/test/https/custom-https-server.test.js @@ -4,8 +4,8 @@ const { test } = require('node:test') const Fastify = require('../..') const https = require('node:https') const dns = require('node:dns').promises -const sget = require('simple-get').concat const { buildCertificate } = require('../build-certificate') +const { Agent } = require('undici') async function setup () { await buildCertificate() @@ -13,7 +13,7 @@ async function setup () { const localAddresses = await dns.lookup('localhost', { all: true }) test('Should support a custom https server', { skip: localAddresses.length < 1 }, async t => { - t.plan(4) + t.plan(5) const fastify = Fastify({ serverFactory: (handler, opts) => { @@ -42,20 +42,16 @@ async function setup () { await fastify.listen({ port: 0 }) - await new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'https://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - if (err) { - return reject(err) + const result = await fetch('https://localhost:' + fastify.server.address().port, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false } - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - resolve() }) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) } From 4178b79f204832dcad86ee482e50ce8e97307e49 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sat, 14 Jun 2025 22:29:28 +0200 Subject: [PATCH 1073/1295] chore: remove simple-get from custom parser 5 (#6172) --- test/custom-parser.5.test.js | 131 +++++++++++++++-------------------- 1 file changed, 56 insertions(+), 75 deletions(-) diff --git a/test/custom-parser.5.test.js b/test/custom-parser.5.test.js index 78f5ae4105f..f630e851e43 100644 --- a/test/custom-parser.5.test.js +++ b/test/custom-parser.5.test.js @@ -1,42 +1,35 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('../fastify') const jsonParser = require('fast-json-body') -const { getServerUrl, plainTextParser } = require('./helper') +const { plainTextParser } = require('./helper') process.removeAllListeners('warning') -test('cannot remove all content type parsers after binding', (t, done) => { - t.plan(2) +test('cannot remove all content type parsers after binding', async (t) => { + t.plan(1) const fastify = Fastify() - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.assert.throws(() => fastify.removeAllContentTypeParsers()) - fastify.close() - done() - }) + t.after(() => fastify.close()) + + await fastify.listen({ port: 0 }) + t.assert.throws(() => fastify.removeAllContentTypeParsers()) }) -test('cannot remove content type parsers after binding', (t, done) => { - t.plan(2) +test('cannot remove content type parsers after binding', async (t) => { + t.plan(1) const fastify = Fastify() - t.after(() => fastify.close()) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.assert.throws(() => fastify.removeContentTypeParser('application/json')) - done() - }) + await fastify.listen({ port: 0 }) + t.assert.throws(() => fastify.removeContentTypeParser('application/json')) }) -test('should be able to override the default json parser after removeAllContentTypeParsers', (t, done) => { - t.plan(5) +test('should be able to override the default json parser after removeAllContentTypeParsers', async (t) => { + t.plan(4) const fastify = Fastify() @@ -53,28 +46,24 @@ test('should be able to override the default json parser after removeAllContentT }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - fastify.close() - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ hello: 'world' })) + await fastify.close() }) -test('should be able to override the default plain text parser after removeAllContentTypeParsers', (t, done) => { - t.plan(5) +test('should be able to override the default plain text parser after removeAllContentTypeParsers', async (t) => { + t.plan(4) const fastify = Fastify() @@ -91,28 +80,24 @@ test('should be able to override the default plain text parser after removeAllCo }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello world') - fastify.close() - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'hello world') + await fastify.close() }) -test('should be able to add a custom content type parser after removeAllContentTypeParsers', (t, done) => { - t.plan(5) +test('should be able to add a custom content type parser after removeAllContentTypeParsers', async (t) => { + t.plan(4) const fastify = Fastify() @@ -128,22 +113,18 @@ test('should be able to add a custom content type parser after removeAllContentT }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - fastify.close() - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/jsoff' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ hello: 'world' })) + await fastify.close() }) From a746e2c81fcc98ea4aacf9ea20268a0bb2e95abb Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:25:26 +0200 Subject: [PATCH 1074/1295] chore: removed simple-get from head tests (#6196) Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- test/http-methods/head.test.js | 137 +++++++++++++-------------------- 1 file changed, 53 insertions(+), 84 deletions(-) diff --git a/test/http-methods/head.test.js b/test/http-methods/head.test.js index 0b4d169a3dc..fe68694ef37 100644 --- a/test/http-methods/head.test.js +++ b/test/http-methods/head.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../fastify')() const schema = { @@ -164,131 +163,101 @@ test('missing schema - head', t => { test('head test', async t => { t.after(() => { fastify.close() }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - await t.test('shorthand - request head', (t, done) => { + await t.test('shorthand - request head', async t => { t.plan(2) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() + const result = await fetch(fastifyServer, { + method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) - await t.test('shorthand - request head params schema', (t, done) => { + await t.test('shorthand - request head params schema', async t => { t.plan(2) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/params/world/123' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() + const result = await fetch(`${fastifyServer}/params/world/123`, { + method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) - await t.test('shorthand - request head params schema error', (t, done) => { + await t.test('shorthand - request head params schema error', async t => { t.plan(2) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/params/world/string' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - done() + const result = await fetch(`${fastifyServer}/params/world/string`, { + method: 'HEAD' }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) }) - await t.test('shorthand - request head querystring schema', (t, done) => { + await t.test('shorthand - request head querystring schema', async t => { t.plan(2) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/query?hello=123' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() + const result = await fetch(`${fastifyServer}/query?hello=123`, { + method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) - await t.test('shorthand - request head querystring schema error', (t, done) => { + await t.test('shorthand - request head querystring schema error', async t => { t.plan(2) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/query?hello=world' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - done() + const result = await fetch(`${fastifyServer}/query?hello=world`, { + method: 'HEAD' }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) }) - await t.test('shorthand - request head missing schema', (t, done) => { + await t.test('shorthand - request head missing schema', async t => { t.plan(2) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/missing' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() + const result = await fetch(`${fastifyServer}/missing`, { + method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) - await t.test('shorthand - request head custom head', (t, done) => { + await t.test('shorthand - request head custom head', async t => { t.plan(3) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/proxy/test' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['x-foo'], 'bar') - t.assert.strictEqual(response.statusCode, 200) - done() + const result = await fetch(`${fastifyServer}/proxy/test`, { + method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('x-foo'), 'bar') + t.assert.strictEqual(result.status, 200) }) - await t.test('shorthand - request head custom head with constraints', (t, done) => { + await t.test('shorthand - request head custom head with constraints', async t => { t.plan(3) - sget({ + const result = await fetch(`${fastifyServer}/proxy/test`, { method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/proxy/test', headers: { version: '1.0.0' } - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['x-foo'], 'bar') - t.assert.strictEqual(response.statusCode, 200) - done() }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('x-foo'), 'bar') + t.assert.strictEqual(result.status, 200) }) - await t.test('shorthand - should not reset a head route', (t, done) => { + await t.test('shorthand - should not reset a head route', async t => { t.plan(2) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/query1' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() + const result = await fetch(`${fastifyServer}/query1`, { + method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) - await t.test('shorthand - should set get and head route in the same api call', (t, done) => { + await t.test('shorthand - should set get and head route in the same api call', async t => { t.plan(3) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port + '/query4' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['x-foo'], 'bar') - t.assert.strictEqual(response.statusCode, 200) - done() + const result = await fetch(`${fastifyServer}/query4`, { + method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('x-foo'), 'bar') + t.assert.strictEqual(result.status, 200) }) }) From 5f7231d25f44b285b7fdd6d5fa74deb58c93b25a Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:25:44 +0200 Subject: [PATCH 1075/1295] chore: removed simple-get from request id tests (#6193) Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- test/request-id.test.js | 79 ++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/test/request-id.test.js b/test/request-id.test.js index 19b0edb279f..e0b935d7659 100644 --- a/test/request-id.test.js +++ b/test/request-id.test.js @@ -2,7 +2,6 @@ const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat test('The request id header key can be customized', async (t) => { t.plan(2) @@ -40,8 +39,8 @@ test('The request id header key can be customized', async (t) => { t.assert.strictEqual(body.id, REQUEST_ID) }) -test('The request id header key can be customized', (t, done) => { - t.plan(4) +test('The request id header key can be customized', async (t) => { + t.plan(3) const REQUEST_ID = '42' const fastify = Fastify({ @@ -55,25 +54,19 @@ test('The request id header key can be customized', (t, done) => { t.after(() => fastify.close()) - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: address, - headers: { - 'my-custom-request-id': REQUEST_ID - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(body.toString(), `{"id":"${REQUEST_ID}"}`) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + headers: { + 'my-custom-request-id': REQUEST_ID + } }) + t.assert.ok(result.ok) + t.assert.deepStrictEqual(await result.json(), { id: REQUEST_ID }) }) -test('The request id header key can be customized', (t, done) => { - t.plan(4) +test('The request id header key can be customized', async (t) => { + t.plan(3) const REQUEST_ID = '42' const fastify = Fastify({ @@ -87,25 +80,19 @@ test('The request id header key can be customized', (t, done) => { t.after(() => fastify.close()) - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: address, - headers: { - 'MY-CUSTOM-REQUEST-ID': REQUEST_ID - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(body.toString(), `{"id":"${REQUEST_ID}"}`) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + headers: { + 'MY-CUSTOM-REQUEST-ID': REQUEST_ID + } }) + t.assert.ok(result.ok) + t.assert.deepStrictEqual(await result.json(), { id: REQUEST_ID }) }) -test('The request id header key can be customized', (t, done) => { - t.plan(4) +test('The request id header key can be customized', async (t) => { + t.plan(3) const REQUEST_ID = '42' const fastify = Fastify({ @@ -119,19 +106,13 @@ test('The request id header key can be customized', (t, done) => { t.after(() => fastify.close()) - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: address, - headers: { - 'MY-CUSTOM-REQUEST-ID': REQUEST_ID - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(body.toString(), `{"id":"${REQUEST_ID}"}`) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + headers: { + 'MY-CUSTOM-REQUEST-ID': REQUEST_ID + } }) + t.assert.ok(result.ok) + t.assert.deepStrictEqual(await result.json(), { id: REQUEST_ID }) }) From 9f86dae9e2bb968e90f8138877bfb6a1fdb0c2c9 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:27:52 +0200 Subject: [PATCH 1076/1295] chore: remove simple get from custom parser 1 (#6167) --- test/custom-parser.1.test.js | 332 +++++++++++++++-------------------- 1 file changed, 141 insertions(+), 191 deletions(-) diff --git a/test/custom-parser.1.test.js b/test/custom-parser.1.test.js index 71ef9fdaaca..c0ac1adad5c 100644 --- a/test/custom-parser.1.test.js +++ b/test/custom-parser.1.test.js @@ -1,72 +1,61 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('../fastify') const jsonParser = require('fast-json-body') -const { getServerUrl } = require('./helper') -const { waitForCb } = require('./toolkit') process.removeAllListeners('warning') -test('Should have typeof body object with no custom parser defined, null body and content type = \'text/plain\'', (t, testDone) => { - t.plan(4) +test('Should have typeof body object with no custom parser defined, null body and content type = \'text/plain\'', async (t) => { + t.plan(3) const fastify = Fastify() fastify.post('/', (req, reply) => { reply.send(req.body) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: null, - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(typeof body, 'object') - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: null, + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '') }) -test('Should have typeof body object with no custom parser defined, undefined body and content type = \'text/plain\'', (t, testDone) => { - t.plan(4) +test('Should have typeof body object with no custom parser defined, undefined body and content type = \'text/plain\'', async (t) => { + t.plan(3) const fastify = Fastify() fastify.post('/', (req, reply) => { reply.send(req.body) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: undefined, - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(typeof body, 'object') - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: undefined, + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '') }) -test('Should get the body as string /1', (t, testDone) => { - t.plan(6) +test('Should get the body as string /1', async (t) => { + t.plan(4) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -85,28 +74,23 @@ test('Should get the body as string /1', (t, testDone) => { } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello world') - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'hello world') }) -test('Should get the body as string /2', (t, testDone) => { - t.plan(6) +test('Should get the body as string /2', async (t) => { + t.plan(4) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -125,28 +109,23 @@ test('Should get the body as string /2', (t, testDone) => { } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': ' text/plain/test ' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello world') - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': ' text/plain/test ' + } }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'hello world') }) -test('Should get the body as buffer', (t, testDone) => { - t.plan(6) +test('Should get the body as buffer', async (t) => { + t.plan(4) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -165,28 +144,23 @@ test('Should get the body as buffer', (t, testDone) => { } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), '{"hello":"world"}') - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '{"hello":"world"}') }) -test('Should get the body as buffer', (t, testDone) => { - t.plan(6) +test('Should get the body as buffer', async (t) => { + t.plan(4) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -205,28 +179,23 @@ test('Should get the body as buffer', (t, testDone) => { } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello world') - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'hello world') }) -test('Should parse empty bodies as a string', (t) => { - t.plan(9) +test('Should parse empty bodies as a string', async (t) => { + t.plan(8) const fastify = Fastify() fastify.addContentTypeParser('text/plain', { parseAs: 'string' }, (req, body, done) => { @@ -242,47 +211,37 @@ test('Should parse empty bodies as a string', (t) => { } }) - const completion = waitForCb({ steps: 2 }) - - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), '') - completion.stepIn() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'DELETE', - url: getServerUrl(fastify), - body: '', - headers: { - 'Content-Type': 'text/plain', - 'Content-Length': '0' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), '') - completion.stepIn() - }) + const postResult = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'text/plain' + } }) - return completion.patience + t.assert.ok(postResult.ok) + t.assert.strictEqual(postResult.status, 200) + t.assert.strictEqual(await postResult.text(), '') + + const deleteResult = await fetch(fastifyServer, { + method: 'DELETE', + body: '', + headers: { + 'Content-Type': 'text/plain', + 'Content-Length': '0' + } + }) + + t.assert.ok(deleteResult.ok) + t.assert.strictEqual(deleteResult.status, 200) + t.assert.strictEqual(await deleteResult.text(), '') }) -test('Should parse empty bodies as a buffer', (t, testDone) => { - t.plan(6) +test('Should parse empty bodies as a buffer', async (t) => { + t.plan(4) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -295,28 +254,23 @@ test('Should parse empty bodies as a buffer', (t, testDone) => { done(null, body) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.length, 0) - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual((await result.arrayBuffer()).byteLength, 0) }) -test('The charset should not interfere with the content type handling', (t, testDone) => { - t.plan(5) +test('The charset should not interfere with the content type handling', async (t) => { + t.plan(4) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -330,22 +284,18 @@ test('The charset should not interfere with the content type handling', (t, test }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json; charset=utf-8' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), '{"hello":"world"}') - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '{"hello":"world"}') }) From 2944c237dc93272a23d66b64fe0f95efa7adead6 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:28:57 +0200 Subject: [PATCH 1077/1295] chore: removed simple-get from custom-parser-0 (#6168) --- test/custom-parser.0.test.js | 615 +++++++++++++++-------------------- 1 file changed, 267 insertions(+), 348 deletions(-) diff --git a/test/custom-parser.0.test.js b/test/custom-parser.0.test.js index 16aea6b73c7..28831e3a6b2 100644 --- a/test/custom-parser.0.test.js +++ b/test/custom-parser.0.test.js @@ -2,11 +2,9 @@ const fs = require('node:fs') const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('../fastify') const jsonParser = require('fast-json-body') -const { getServerUrl, plainTextParser } = require('./helper') -const { waitForCb } = require('./toolkit') +const { plainTextParser } = require('./helper') process.removeAllListeners('warning') @@ -16,8 +14,8 @@ test('contentTypeParser method should exist', t => { t.assert.ok(fastify.addContentTypeParser) }) -test('contentTypeParser should add a custom parser', (t, mainTestDone) => { - t.plan(3) +test('contentTypeParser should add a custom parser', async (t) => { + t.plan(2) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -34,56 +32,44 @@ test('contentTypeParser should add a custom parser', (t, mainTestDone) => { }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - const completion = waitForCb({ steps: 2 }) - - t.after(() => fastify.close()) - - t.test('in POST', (t, testDone) => { - t.plan(3) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - testDone() - completion.stepIn() - }) + await t.test('in POST', async (t) => { + t.plan(3) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } }) - t.test('in OPTIONS', (t, testDone) => { - t.plan(3) - - sget({ - method: 'OPTIONS', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - testDone() - completion.stepIn() - }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) + }) + + await t.test('in OPTIONS', async (t) => { + t.plan(2) + + const result = await fetch(fastifyServer, { + method: 'OPTIONS', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } }) - completion.patience.then(mainTestDone) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, JSON.stringify({ hello: 'world' })) }) }) -test('contentTypeParser should handle multiple custom parsers', (t, testDone) => { - t.plan(7) +test('contentTypeParser should handle multiple custom parsers', async (t) => { + t.plan(6) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -103,45 +89,37 @@ test('contentTypeParser should handle multiple custom parsers', (t, testDone) => fastify.addContentTypeParser('application/jsoff', customParser) fastify.addContentTypeParser('application/ffosj', customParser) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - const completion = waitForCb({ steps: 2 }) + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - completion.stepIn() - }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.deepStrictEqual(await result1.json(), { hello: 'world' }) - sget({ - method: 'POST', - url: getServerUrl(fastify) + '/hello', - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/ffosj' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - completion.stepIn() - }) - completion.patience.then(testDone) + const result2 = await fetch(fastifyServer + '/hello', { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/ffosj' + } }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + + t.assert.deepStrictEqual(await result2.json(), { hello: 'world' }) }) -test('contentTypeParser should handle an array of custom contentTypes', (t, testDone) => { - t.plan(7) +test('contentTypeParser should handle an array of custom contentTypes', async (t) => { + t.plan(6) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -160,45 +138,36 @@ test('contentTypeParser should handle an array of custom contentTypes', (t, test fastify.addContentTypeParser(['application/jsoff', 'application/ffosj'], customParser) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - const completion = waitForCb({ steps: 2 }) + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - completion.stepIn() - }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.deepStrictEqual(await result1.json(), { hello: 'world' }) - sget({ - method: 'POST', - url: getServerUrl(fastify) + '/hello', - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/ffosj' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - completion.stepIn() - }) - completion.patience.then(testDone) + const result2 = await fetch(fastifyServer + '/hello', { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/ffosj' + } }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.deepStrictEqual(await result2.json(), { hello: 'world' }) }) -test('contentTypeParser should handle errors', (t, testDone) => { - t.plan(3) +test('contentTypeParser should handle errors', async (t) => { + t.plan(1) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -209,23 +178,18 @@ test('contentTypeParser should handle errors', (t, testDone) => { done(new Error('kaboom!'), {}) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - fastify.close() - testDone() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } }) + + t.assert.strictEqual(result.status, 500) }) test('contentTypeParser should support encapsulation', (t, testDone) => { @@ -254,8 +218,8 @@ test('contentTypeParser should support encapsulation', (t, testDone) => { }) }) -test('contentTypeParser should support encapsulation, second try', (t, testDone) => { - t.plan(4) +test('contentTypeParser should support encapsulation, second try', async (t) => { + t.plan(2) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -272,28 +236,24 @@ test('contentTypeParser should support encapsulation, second try', (t, testDone) done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - fastify.close() - testDone() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, JSON.stringify({ hello: 'world' })) }) -test('contentTypeParser shouldn\'t support request with undefined "Content-Type"', (t, testDone) => { - t.plan(3) +test('contentTypeParser shouldn\'t support request with undefined "Content-Type"', async (t) => { + t.plan(1) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -306,23 +266,18 @@ test('contentTypeParser shouldn\'t support request with undefined "Content-Type" }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'unknown content type!', - headers: { - // 'Content-Type': undefined - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - fastify.close() - testDone() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'unknown content type!', + headers: { + 'Content-Type': undefined + } }) + + t.assert.strictEqual(result.status, 415) }) test('the content type should be a string or RegExp', t => { @@ -364,8 +319,8 @@ test('the content type handler should be a function', t => { } }) -test('catch all content type parser', (t, testDone) => { - t.plan(7) +test('catch all content type parser', async (t) => { + t.plan(6) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -380,41 +335,36 @@ test('catch all content type parser', (t, testDone) => { }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello') - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello', - headers: { - 'Content-Type': 'very-weird-content-type' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello') - fastify.close() - testDone() - }) - }) + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: 'hello', + headers: { + 'Content-Type': 'application/jsoff' + } }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.strictEqual(await result1.text(), 'hello') + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: 'hello', + headers: { + 'Content-Type': 'very-weird-content-type' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.strictEqual(await result2.text(), 'hello') }) -test('catch all content type parser should not interfere with other conte type parsers', (t, testDone) => { - t.plan(7) +test('catch all content type parser should not interfere with other conte type parsers', async (t) => { + t.plan(6) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -435,42 +385,37 @@ test('catch all content type parser should not interfere with other conte type p }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/jsoff' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello', - headers: { - 'Content-Type': 'very-weird-content-type' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello') - fastify.close() - testDone() - }) - }) + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.deepStrictEqual(await result1.json(), { hello: 'world' }) + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: 'hello', + headers: { + 'Content-Type': 'very-weird-content-type' + } }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.strictEqual(await result2.text(), 'hello') }) // Issue 492 https://github.com/fastify/fastify/issues/492 -test('\'*\' catch undefined Content-Type requests', (t, testDone) => { - t.plan(4) +test('\'*\' catch undefined Content-Type requests', async (t) => { + t.plan(3) const fastify = Fastify() @@ -489,22 +434,19 @@ test('\'*\' catch undefined Content-Type requests', (t, testDone) => { res.type('text/plain').send(req.body) }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const fileStream = fs.createReadStream(__filename) + const fileStream = fs.createReadStream(__filename) - sget({ - method: 'POST', - url: getServerUrl(fastify) + '/', - body: fileStream - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body + '', fs.readFileSync(__filename).toString()) - testDone() - }) + const result = await fetch(fastifyServer + '/', { + method: 'POST', + body: fileStream, + duplex: 'half' }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), fs.readFileSync(__filename).toString()) }) test('cannot add custom parser after binding', (t, testDone) => { @@ -531,8 +473,8 @@ test('cannot add custom parser after binding', (t, testDone) => { }) }) -test('Can override the default json parser', (t, testDone) => { - t.plan(5) +test('Can override the default json parser', async (t) => { + t.plan(3) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -546,28 +488,24 @@ test('Can override the default json parser', (t, testDone) => { }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), '{"hello":"world"}') - fastify.close() - testDone() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, '{"hello":"world"}') }) -test('Can override the default plain text parser', (t, testDone) => { - t.plan(5) +test('Can override the default plain text parser', async (t) => { + t.plan(3) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -581,28 +519,24 @@ test('Can override the default plain text parser', (t, testDone) => { }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello world') - fastify.close() - testDone() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, 'hello world') }) -test('Can override the default json parser in a plugin', (t, testDone) => { - t.plan(5) +test('Can override the default json parser in a plugin', async (t) => { + t.plan(3) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -620,24 +554,20 @@ test('Can override the default json parser in a plugin', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), '{"hello":"world"}') - fastify.close() - testDone() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, '{"hello":"world"}') }) test('Can\'t override the json parser multiple times', t => { @@ -686,8 +616,8 @@ test('Can\'t override the plain text parser multiple times', t => { } }) -test('Should get the body as string', (t, testDone) => { - t.plan(6) +test('Should get the body as string', async (t) => { + t.plan(4) const fastify = Fastify() fastify.post('/', (req, reply) => { @@ -706,77 +636,66 @@ test('Should get the body as string', (t, testDone) => { } }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), '{"hello":"world"}') - fastify.close() - testDone() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, '{"hello":"world"}') }) -test('Should return defined body with no custom parser defined and content type = \'text/plain\'', (t, testDone) => { - t.plan(4) +test('Should return defined body with no custom parser defined and content type = \'text/plain\'', async (t) => { + t.plan(2) const fastify = Fastify() fastify.post('/', (req, reply) => { reply.send(req.body) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'hello world', - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'hello world') - fastify.close() - testDone() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, 'hello world') }) -test('Should have typeof body object with no custom parser defined, no body defined and content type = \'text/plain\'', (t, testDone) => { - t.plan(4) +test('Should have typeof body object with no custom parser defined, no body defined and content type = \'text/plain\'', async (t) => { + t.plan(3) const fastify = Fastify() fastify.post('/', (req, reply) => { reply.send(req.body) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: getServerUrl(fastify), - headers: { - 'Content-Type': 'text/plain' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(typeof body, 'object') - fastify.close() - testDone() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '') }) From b475a0737036d6c1ae5ed429e77ba896cd93eafb Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:29:24 +0200 Subject: [PATCH 1078/1295] chore: remove simple-get from custom parser 2 (#6169) --- test/custom-parser.2.test.js | 78 ++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/test/custom-parser.2.test.js b/test/custom-parser.2.test.js index 30ff87b884f..88d596ca6c4 100644 --- a/test/custom-parser.2.test.js +++ b/test/custom-parser.2.test.js @@ -1,9 +1,7 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('..') -const { getServerUrl } = require('./helper') process.removeAllListeners('warning') @@ -20,8 +18,8 @@ test('Wrong parseAs parameter', t => { } }) -test('Should allow defining the bodyLimit per parser', (t, done) => { - t.plan(3) +test('Should allow defining the bodyLimit per parser', async (t) => { + t.plan(2) const fastify = Fastify() t.after(() => fastify.close()) @@ -38,31 +36,27 @@ test('Should allow defining the bodyLimit per parser', (t, done) => { } ) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '1234567890', - headers: { - 'Content-Type': 'x/foo' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body.toString()), { - statusCode: 413, - code: 'FST_ERR_CTP_BODY_TOO_LARGE', - error: 'Payload Too Large', - message: 'Request body is too large' - }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '1234567890', + headers: { + 'Content-Type': 'x/foo' + } + }) + + t.assert.ok(!result.ok) + t.assert.deepStrictEqual(await result.json(), { + statusCode: 413, + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + error: 'Payload Too Large', + message: 'Request body is too large' }) }) -test('route bodyLimit should take precedence over a custom parser bodyLimit', (t, done) => { - t.plan(3) +test('route bodyLimit should take precedence over a custom parser bodyLimit', async (t) => { + t.plan(2) const fastify = Fastify() t.after(() => fastify.close()) @@ -79,23 +73,19 @@ test('route bodyLimit should take precedence over a custom parser bodyLimit', (t } ) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '1234567890', - headers: { 'Content-Type': 'x/foo' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body.toString()), { - statusCode: 413, - code: 'FST_ERR_CTP_BODY_TOO_LARGE', - error: 'Payload Too Large', - message: 'Request body is too large' - }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '1234567890', + headers: { 'Content-Type': 'x/foo' } + }) + + t.assert.ok(!result.ok) + t.assert.deepStrictEqual(await result.json(), { + statusCode: 413, + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + error: 'Payload Too Large', + message: 'Request body is too large' }) }) From 9e4cb241274d482b66b844ae1ea67b10e37ad1eb Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:32:43 +0200 Subject: [PATCH 1079/1295] chore: remove simple-get from custom parser 4 (#6171) --- test/custom-parser.4.test.js | 250 +++++++++++++++-------------------- 1 file changed, 106 insertions(+), 144 deletions(-) diff --git a/test/custom-parser.4.test.js b/test/custom-parser.4.test.js index 67100fd8fbd..6481a48f8bf 100644 --- a/test/custom-parser.4.test.js +++ b/test/custom-parser.4.test.js @@ -1,16 +1,13 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('../fastify') const jsonParser = require('fast-json-body') -const { getServerUrl } = require('./helper') -const { waitForCb } = require('./toolkit') process.removeAllListeners('warning') -test('should prefer string content types over RegExp ones', (t, testDone) => { - t.plan(7) +test('should prefer string content types over RegExp ones', async (t) => { + t.plan(6) const fastify = Fastify() t.after(() => { fastify.close() }) fastify.post('/', (req, reply) => { @@ -31,44 +28,35 @@ test('should prefer string content types over RegExp ones', (t, testDone) => { }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - const completion = waitForCb({ steps: 2 }) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"k1":"myValue", "k2": "myValue"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.equal(body.toString(), JSON.stringify({ k1: 'myValue', k2: 'myValue' })) - completion.stepIn() - }) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'javascript', - headers: { - 'Content-Type': 'application/javascript' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.equal(body.toString(), 'javascript') - completion.stepIn() - }) + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '{"k1":"myValue", "k2": "myValue"}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.equal(await result1.text(), JSON.stringify({ k1: 'myValue', k2: 'myValue' })) - completion.patience.then(testDone) + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: 'javascript', + headers: { + 'Content-Type': 'application/javascript' + } }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.equal(await result2.text(), 'javascript') }) -test('removeContentTypeParser should support arrays of content types to remove', (t, testDone) => { - t.plan(8) +test('removeContentTypeParser should support arrays of content types to remove', async (t) => { + t.plan(7) const fastify = Fastify() t.after(() => fastify.close()) @@ -93,55 +81,45 @@ test('removeContentTypeParser should support arrays of content types to remove', reply.send(req.body) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - const completion = waitForCb({ steps: 3 }) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '', - headers: { - 'Content-Type': 'application/xml' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.equal(body.toString(), 'xml') - completion.stepIn() - }) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '', - headers: { - 'Content-Type': 'image/png' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - completion.stepIn() - }) + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'application/xml' + } + }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{test: "test"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - completion.stepIn() - }) - completion.patience.then(testDone) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.equal(await result1.text(), 'xml') + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'image/png' + } + }) + + t.assert.ok(!result2.ok) + t.assert.strictEqual(result2.status, 415) + + const result3 = await fetch(fastifyServer, { + method: 'POST', + body: '{test: "test"}', + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(!result3.ok) + t.assert.strictEqual(result3.status, 415) }) -test('removeContentTypeParser should support encapsulation', (t, testDone) => { - t.plan(6) +test('removeContentTypeParser should support encapsulation', async (t) => { + t.plan(5) const fastify = Fastify() t.after(() => fastify.close()) @@ -167,42 +145,34 @@ test('removeContentTypeParser should support encapsulation', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - const completion = waitForCb({ steps: 2 }) - - sget({ - method: 'POST', - url: getServerUrl(fastify) + '/encapsulated', - body: '', - headers: { - 'Content-Type': 'application/xml' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - completion.stepIn() - }) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '', - headers: { - 'Content-Type': 'application/xml' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.equal(body.toString(), 'xml') - completion.stepIn() - }) - completion.patience.then(testDone) + const result1 = await fetch(fastifyServer + '/encapsulated', { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'application/xml' + } }) + + t.assert.ok(!result1.ok) + t.assert.strictEqual(result1.status, 415) + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'application/xml' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.equal(await result2.text(), 'xml') }) -test('removeAllContentTypeParsers should support encapsulation', (t, testDone) => { - t.plan(6) +test('removeAllContentTypeParsers should support encapsulation', async (t) => { + t.plan(5) const fastify = Fastify() t.after(() => fastify.close()) @@ -221,36 +191,28 @@ test('removeAllContentTypeParsers should support encapsulation', (t, testDone) = done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - const completion = waitForCb({ steps: 2 }) - - sget({ - method: 'POST', - url: getServerUrl(fastify) + '/encapsulated', - body: '{}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - completion.stepIn() - }) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"test":1}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.equal(JSON.parse(body.toString()).test, 1) - completion.stepIn() - }) - completion.patience.then(testDone) + const result1 = await fetch(fastifyServer + '/encapsulated', { + method: 'POST', + body: '{}', + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(!result1.ok) + t.assert.strictEqual(result1.status, 415) + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: '{"test":1}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.equal(JSON.parse(await result2.text()).test, 1) }) From 09b3585e5d76542f1dda78286ed8f06a71ef3b15 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:34:33 +0200 Subject: [PATCH 1080/1295] chore: removed simple-get from promises test (#6183) --- test/promises.test.js | 105 +++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 63 deletions(-) diff --git a/test/promises.test.js b/test/promises.test.js index c3ee8162d8e..f1f34b1ec81 100644 --- a/test/promises.test.js +++ b/test/promises.test.js @@ -2,7 +2,6 @@ const { test } = require('node:test') const assert = require('node:assert') -const sget = require('simple-get').concat const fastify = require('..')() test.after(() => fastify.close()) @@ -62,85 +61,65 @@ fastify.get('/return-reply', opts, function (req, reply) { return reply.send({ hello: 'world' }) }) -fastify.listen({ port: 0 }, err => { +fastify.listen({ port: 0 }, (err, fastifyServer) => { assert.ifError(err) - test('shorthand - sget return promise es6 get', (t, done) => { + test('shorthand - sget return promise es6 get', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/return' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) + + const result = await fetch(`${fastifyServer}/return`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - test('shorthand - sget promise es6 get return error', (t, done) => { + test('shorthand - sget promise es6 get return error', async t => { t.plan(2) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/return-error' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - done() - }) + + const result = await fetch(`${fastifyServer}/return-error`) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) }) - test('sget promise double send', (t, done) => { + test('sget promise double send', async t => { t.plan(3) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/double' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: '42' }) - done() - }) + + const result = await fetch(`${fastifyServer}/double`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: '42' }) }) - test('thenable', (t, done) => { + test('thenable', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/thenable' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) + + const result = await fetch(`${fastifyServer}/thenable`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - test('thenable (error)', (t, done) => { + test('thenable (error)', async t => { t.plan(2) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/thenable-error' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - done() - }) + + const result = await fetch(`${fastifyServer}/thenable-error`) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) }) - test('return-reply', (t, done) => { + test('return-reply', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/return-reply' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) + + const result = await fetch(`${fastifyServer}/return-reply`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) }) From 13bc8b2072cad7fd35eb40d819e155e8dffa2156 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:35:10 +0200 Subject: [PATCH 1081/1295] chore: removed simple-get from move test (#6179) --- test/http-methods/move.test.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/test/http-methods/move.test.js b/test/http-methods/move.test.js index 4f9bb561a40..b1a566b49d7 100644 --- a/test/http-methods/move.test.js +++ b/test/http-methods/move.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../')() fastify.addHttpMethod('MOVE') @@ -24,23 +23,20 @@ test('shorthand - move', t => { } }) test('move test', async t => { - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) t.after(() => { fastify.close() }) - await t.test('request - move', (t, done) => { + await t.test('request - move', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test.txt`, + const result = await fetch(`${fastifyServer}/test.txt`, { method: 'MOVE', headers: { Destination: '/test2.txt' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 201) - t.assert.strictEqual(response.headers.location, '/test2.txt') - done() }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 201) + t.assert.strictEqual(result.headers.get('location'), '/test2.txt') }) }) From fe4582bb85550d48cb09558e9b2c9f911af2c43c Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:37:23 +0200 Subject: [PATCH 1082/1295] chore: remove simple-get from custom querystring parser (#6166) --- test/custom-querystring-parser.test.js | 110 ++++++++----------------- 1 file changed, 33 insertions(+), 77 deletions(-) diff --git a/test/custom-querystring-parser.test.js b/test/custom-querystring-parser.test.js index 724d052dab6..2f24a0d27db 100644 --- a/test/custom-querystring-parser.test.js +++ b/test/custom-querystring-parser.test.js @@ -2,12 +2,10 @@ const { test } = require('node:test') const querystring = require('node:querystring') -const sget = require('simple-get').concat const Fastify = require('..') -const { waitForCb } = require('./toolkit') -test('Custom querystring parser', t => { - t.plan(9) +test('Custom querystring parser', async t => { + t.plan(7) const fastify = Fastify({ querystringParser: function (str) { @@ -24,36 +22,22 @@ test('Custom querystring parser', t => { reply.send({ hello: 'world' }) }) - const completion = waitForCb({ steps: 2 }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const result = await fetch(`${fastifyServer}?foo=bar&baz=faz`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) - sget({ - method: 'GET', - url: `${address}?foo=bar&baz=faz` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completion.stepIn() - }) - - fastify.inject({ - method: 'GET', - url: `${address}?foo=bar&baz=faz` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completion.stepIn() - }) + const injectResponse = await fastify.inject({ + method: 'GET', + url: `${fastifyServer}?foo=bar&baz=faz` }) - - return completion.patience + t.assert.strictEqual(injectResponse.statusCode, 200) }) -test('Custom querystring parser should be called also if there is nothing to parse', t => { - t.plan(9) +test('Custom querystring parser should be called also if there is nothing to parse', async t => { + t.plan(7) const fastify = Fastify({ querystringParser: function (str) { @@ -67,36 +51,22 @@ test('Custom querystring parser should be called also if there is nothing to par reply.send({ hello: 'world' }) }) - const completion = waitForCb({ steps: 2 }) - - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: address - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completion.stepIn() - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) - fastify.inject({ - method: 'GET', - url: address - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completion.stepIn() - }) + const injectResponse = await fastify.inject({ + method: 'GET', + url: fastifyServer }) - - return completion.patience + t.assert.strictEqual(injectResponse.statusCode, 200) }) -test('Querystring without value', t => { - t.plan(9) +test('Querystring without value', async t => { + t.plan(7) const fastify = Fastify({ querystringParser: function (str) { @@ -110,32 +80,18 @@ test('Querystring without value', t => { reply.send({ hello: 'world' }) }) - const completion = waitForCb({ steps: 2 }) - - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: `${address}?foo` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completion.stepIn() - }) + const result = await fetch(`${fastifyServer}?foo`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) - fastify.inject({ - method: 'GET', - url: `${address}?foo` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completion.stepIn() - }) + const injectResponse = await fastify.inject({ + method: 'GET', + url: `${fastifyServer}?foo` }) - - return completion.patience + t.assert.strictEqual(injectResponse.statusCode, 200) }) test('Custom querystring parser should be a function', t => { From 2fcca420d42656956a178acc685d25f53e4e0d96 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:38:10 +0200 Subject: [PATCH 1083/1295] chore: remove simple-get from propfind (#6174) --- test/http-methods/propfind.test.js | 78 +++++++++++++----------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/test/http-methods/propfind.test.js b/test/http-methods/propfind.test.js index 43ff6e7c9ab..385f5ec573d 100644 --- a/test/http-methods/propfind.test.js +++ b/test/http-methods/propfind.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../')() fastify.addHttpMethod('PROPFIND', { hasBody: true }) @@ -74,73 +73,64 @@ test('propfind test', async t => { fastify.close() }) - await t.test('request - propfind', (t, done) => { + await t.test('request - propfind', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/`, + const result = await fetch(`http://localhost:${fastify.server.address().port}/`, { method: 'PROPFIND' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request with other path - propfind', (t, done) => { + await t.test('request with other path - propfind', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test`, + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { method: 'PROPFIND' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) // the body test uses a text/plain content type instead of application/xml because it requires // a specific content type parser - await t.test('request with body - propfind', (t, done) => { + await t.test('request with body - propfind', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test`, + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'PROPFIND', headers: { 'content-type': 'text/plain' }, - body: bodySample, - method: 'PROPFIND' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() + body: bodySample }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request with body and no content type (415 error) - propfind', (t, done) => { + await t.test('request with body and no content type (415 error) - propfind', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test`, + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'PROPFIND', body: bodySample, - method: 'PROPFIND' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() + headers: { 'content-type': '' } }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) - await t.test('request without body - propfind', (t, done) => { + await t.test('request without body - propfind', async t => { t.plan(3) - sget({ - url: `http://localhost:${fastify.server.address().port}/test`, + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { method: 'PROPFIND' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 207) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - done() }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) }) }) From eca5f9345ff808ca8e0c1059ae0cf3ebec61f022 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:39:46 +0200 Subject: [PATCH 1084/1295] chore: removed simple-get from case insensitive test (#6188) --- test/case-insensitive.test.js | 109 ++++++++++++++-------------------- 1 file changed, 44 insertions(+), 65 deletions(-) diff --git a/test/case-insensitive.test.js b/test/case-insensitive.test.js index b71c34db359..a9bce3b4f9a 100644 --- a/test/case-insensitive.test.js +++ b/test/case-insensitive.test.js @@ -2,10 +2,9 @@ const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat -test('case insensitive', (t, done) => { - t.plan(4) +test('case insensitive', async (t) => { + t.plan(3) const fastify = Fastify({ caseSensitive: false @@ -16,25 +15,19 @@ test('case insensitive', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/FOO' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { - hello: 'world' - }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(`${fastifyServer}/FOO`) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { + hello: 'world' }) }) -test('case insensitive inject', (t, done) => { - t.plan(4) +test('case insensitive inject', async t => { + t.plan(2) const fastify = Fastify({ caseSensitive: false @@ -45,25 +38,21 @@ test('case insensitive inject', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - fastify.inject({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/FOO' - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(response.payload), { - hello: 'world' - }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fastify.inject({ + method: 'GET', + url: fastifyServer + '/FOO' + }) + + t.assert.strictEqual(result.statusCode, 200) + t.assert.deepStrictEqual(result.json(), { + hello: 'world' }) }) -test('case insensitive (parametric)', (t, done) => { - t.plan(5) +test('case insensitive (parametric)', async (t) => { + t.plan(4) const fastify = Fastify({ caseSensitive: false @@ -75,25 +64,21 @@ test('case insensitive (parametric)', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/FoO/bAr' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { - hello: 'world' - }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(`${fastifyServer}/FoO/bAr`, { + method: 'GET' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { + hello: 'world' }) }) -test('case insensitive (wildcard)', (t, done) => { - t.plan(5) +test('case insensitive (wildcard)', async (t) => { + t.plan(4) const fastify = Fastify({ caseSensitive: false @@ -105,19 +90,13 @@ test('case insensitive (wildcard)', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/FoO/bAr/baZ' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { - hello: 'world' - }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(`${fastifyServer}/FoO/bAr/baZ`) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { + hello: 'world' }) }) From 775e339205bb5acd4b8481e913f1d478f3caa17d Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:40:47 +0200 Subject: [PATCH 1085/1295] chore: remove simple get from async-await tests (#6165) --- test/async-await.test.js | 215 +++++++++++++++------------------------ 1 file changed, 81 insertions(+), 134 deletions(-) diff --git a/test/async-await.test.js b/test/async-await.test.js index 0f0a0cbe6ef..b7f62053f2d 100644 --- a/test/async-await.test.js +++ b/test/async-await.test.js @@ -1,12 +1,10 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('..') const split = require('split2') const pino = require('pino') const { sleep } = require('./helper') -const { waitForCb } = require('./toolkit') const statusCodes = require('node:http').STATUS_CODES const opts = { @@ -44,8 +42,8 @@ const optsWithHostnameAndPort = { } } } -test('async await', (t, testDone) => { - t.plan(16) +test('async await', async t => { + t.plan(15) const fastify = Fastify() try { fastify.get('/', opts, async function awaitMyFunc (req, reply) { @@ -76,54 +74,37 @@ test('async await', (t, testDone) => { t.assert.fail() } - fastify.listen({ port: 0 }, async err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ - steps: 3 - }) + t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) + const result = await fetch(fastifyServer) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/no-await' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/await/hostname_port' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - const parsedBody = JSON.parse(body) - t.assert.strictEqual(parsedBody.hostname, 'localhost') - t.assert.strictEqual(parseInt(parsedBody.port), fastify.server.address().port) - completion.stepIn() - }) + const result1 = await fetch(`${fastifyServer}/no-await`) - completion.patience.then(testDone) - }) + const body1 = await result1.text() + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world' }) + + const result2 = await fetch(`http://localhost:${fastify.server.address().port}/await/hostname_port`) + + const parsedBody = await result2.json() + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.strictEqual(parsedBody.hostname, 'localhost') + t.assert.strictEqual(parseInt(parsedBody.port), fastify.server.address().port) }) -test('ignore the result of the promise if reply.send is called beforehand (undefined)', (t, done) => { - t.plan(4) +test('ignore the result of the promise if reply.send is called beforehand (undefined)', async (t) => { + t.plan(3) const server = Fastify() const payload = { hello: 'world' } @@ -134,22 +115,17 @@ test('ignore the result of the promise if reply.send is called beforehand (undef t.after(() => { server.close() }) - server.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - sget({ - method: 'GET', - url: 'http://localhost:' + server.server.address().port + '/' - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(payload, JSON.parse(body)) - t.assert.strictEqual(res.statusCode, 200) - done() - }) - }) + const fastifyServer = await server.listen({ port: 0 }) + + const result = await fetch(fastifyServer) + + t.assert.ok(result.ok) + t.assert.deepStrictEqual(payload, await result.json()) + t.assert.strictEqual(result.status, 200) }) -test('ignore the result of the promise if reply.send is called beforehand (object)', (t, done) => { - t.plan(4) +test('ignore the result of the promise if reply.send is called beforehand (object)', async (t) => { + t.plan(3) const server = Fastify() const payload = { hello: 'world2' } @@ -161,18 +137,13 @@ test('ignore the result of the promise if reply.send is called beforehand (objec t.after(() => { server.close() }) - server.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - sget({ - method: 'GET', - url: 'http://localhost:' + server.server.address().port + '/' - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(payload, JSON.parse(body)) - t.assert.strictEqual(res.statusCode, 200) - done() - }) - }) + const fastifyServer = await server.listen({ port: 0 }) + + const result = await fetch(fastifyServer) + + t.assert.ok(result.ok) + t.assert.deepStrictEqual(payload, await result.json()) + t.assert.strictEqual(result.status, 200) }) test('server logs an error if reply.send is called and a value is returned via async/await', (t, done) => { @@ -206,8 +177,8 @@ test('server logs an error if reply.send is called and a value is returned via a }) }) -test('ignore the result of the promise if reply.send is called beforehand (undefined)', (t, done) => { - t.plan(4) +test('ignore the result of the promise if reply.send is called beforehand (undefined)', async (t) => { + t.plan(3) const server = Fastify() const payload = { hello: 'world' } @@ -218,22 +189,17 @@ test('ignore the result of the promise if reply.send is called beforehand (undef t.after(() => { server.close() }) - server.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - sget({ - method: 'GET', - url: 'http://localhost:' + server.server.address().port + '/' - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(payload, JSON.parse(body)) - t.assert.strictEqual(res.statusCode, 200) - done() - }) - }) + const fastifyServer = await server.listen({ port: 0 }) + + const result = await fetch(fastifyServer) + + t.assert.ok(result.ok) + t.assert.deepStrictEqual(payload, await result.json()) + t.assert.strictEqual(result.status, 200) }) -test('ignore the result of the promise if reply.send is called beforehand (object)', (t, done) => { - t.plan(4) +test('ignore the result of the promise if reply.send is called beforehand (object)', async (t) => { + t.plan(3) const server = Fastify() const payload = { hello: 'world2' } @@ -245,18 +211,13 @@ test('ignore the result of the promise if reply.send is called beforehand (objec t.after(() => { server.close() }) - server.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - sget({ - method: 'GET', - url: 'http://localhost:' + server.server.address().port + '/' - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(payload, JSON.parse(body)) - t.assert.strictEqual(res.statusCode, 200) - done() - }) - }) + const fastifyServer = await server.listen({ port: 0 }) + + const result = await fetch(fastifyServer) + + t.assert.ok(result.ok) + t.assert.deepStrictEqual(payload, await result.json()) + t.assert.strictEqual(result.status, 200) }) test('await reply if we will be calling reply.send in the future', (t, done) => { @@ -442,8 +403,8 @@ test('does not call reply.send() twice if 204 response equal already sent', (t, }) }) -test('promise was fulfilled with undefined', (t, done) => { - t.plan(4) +test('promise was fulfilled with undefined', async (t) => { + t.plan(3) let fastify = null const stream = split(JSON.parse) @@ -467,20 +428,13 @@ test('promise was fulfilled with undefined', (t, done) => { t.assert.fail('should not log an error') }) - fastify.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/' - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.strictEqual(res.body, undefined) - t.assert.strictEqual(res.statusCode, 200) - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer) + + t.assert.ok(result.ok) + t.assert.strictEqual(await result.text(), '') + t.assert.strictEqual(result.status, 200) }) test('promise was fulfilled with undefined using inject', async (t) => { @@ -505,8 +459,8 @@ test('promise was fulfilled with undefined using inject', async (t) => { t.assert.strictEqual(res.statusCode, 200) }) -test('error is not logged because promise was fulfilled with undefined but response was sent before promise resolution', (t, done) => { - t.plan(4) +test('error is not logged because promise was fulfilled with undefined but response was sent before promise resolution', async (t) => { + t.plan(3) let fastify = null const stream = split(JSON.parse) @@ -532,23 +486,16 @@ test('error is not logged because promise was fulfilled with undefined but respo t.assert.fail('should not log an error') }) - fastify.listen({ port: 0 }, (err) => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/' - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.strictEqual(res.statusCode, 200) - t.assert.deepStrictEqual( - payload, - JSON.parse(body) - ) - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual( + payload, + await result.json() + ) }) test('Thrown Error instance sets HTTP status code', (t, done) => { From d64e12cf261e92311ca66019d6a2bde3de03191a Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:55:44 +0200 Subject: [PATCH 1086/1295] chore: removed simple-get from header overflow test (#6190) --- test/header-overflow.test.js | 47 ++++++++++++++---------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/test/header-overflow.test.js b/test/header-overflow.test.js index a00a068b652..cfc32b88f2d 100644 --- a/test/header-overflow.test.js +++ b/test/header-overflow.test.js @@ -2,12 +2,11 @@ const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat const maxHeaderSize = 1024 -test('Should return 431 if request header fields are too large', (t, done) => { - t.plan(3) +test('Should return 431 if request header fields are too large', async (t) => { + t.plan(2) const fastify = Fastify({ http: { maxHeaderSize } }) fastify.route({ @@ -18,27 +17,23 @@ test('Should return 431 if request header fields are too large', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'Large-Header': 'a'.repeat(maxHeaderSize) - } - }, (err, res) => { - t.assert.ifError(err) - t.assert.strictEqual(res.statusCode, 431) - done() - }) + const result = await fetch(fastifyServer, { + method: 'GET', + headers: { + 'Large-Header': 'a'.repeat(maxHeaderSize) + } }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 431) + t.after(() => fastify.close()) }) -test('Should return 431 if URI is too long', (t, done) => { - t.plan(3) +test('Should return 431 if URI is too long', async (t) => { + t.plan(2) const fastify = Fastify({ http: { maxHeaderSize } }) fastify.route({ @@ -49,18 +44,12 @@ test('Should return 431 if URI is too long', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + `/${'a'.repeat(maxHeaderSize)}` - }, (err, res) => { - t.assert.ifError(err) - t.assert.strictEqual(res.statusCode, 431) - done() - }) - }) + const result = await fetch(`${fastifyServer}/${'a'.repeat(maxHeaderSize)}`) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 431) t.after(() => fastify.close()) }) From 98bf26f645ddedcb5644f8cbaf72ec25ceeda5bc Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:56:17 +0200 Subject: [PATCH 1087/1295] chore: removed simple-get from check test (#6176) --- test/check.test.js | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/test/check.test.js b/test/check.test.js index e932b4a05f7..941152f24f7 100644 --- a/test/check.test.js +++ b/test/check.test.js @@ -3,7 +3,6 @@ const { test } = require('node:test') const { S } = require('fluent-json-schema') const Fastify = require('..') -const sget = require('simple-get').concat const BadRequestSchema = S.object() .prop('statusCode', S.number()) @@ -105,32 +104,29 @@ const handler = (request, reply) => { }) } -test('serialize the response for a Bad Request error, as defined on the schema', (t, done) => { +test('serialize the response for a Bad Request error, as defined on the schema', async t => { const fastify = Fastify({}) t.after(() => fastify.close()) fastify.post('/', options, handler) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - const url = `http://localhost:${fastify.server.address().port}/` - - sget({ - method: 'POST', - url, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(body, { - statusCode: 400, - error: 'Bad Request', - message: 'body must be object' - }) - done() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: '12' + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + statusCode: 400, + error: 'Bad Request', + message: 'body must be object' }) }) From a45d57af25fa2cb1c0a8039d102ba3b64df61e0e Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 08:58:03 +0200 Subject: [PATCH 1088/1295] chore: removed simple-get from casync hooks test (#6189) --- test/async_hooks.test.js | 55 +++++++++++++--------------------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/test/async_hooks.test.js b/test/async_hooks.test.js index 72fdb8f9259..b99dda953c9 100644 --- a/test/async_hooks.test.js +++ b/test/async_hooks.test.js @@ -3,7 +3,6 @@ const { createHook } = require('node:async_hooks') const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat const remainingIds = new Set() @@ -20,7 +19,7 @@ createHook({ const app = Fastify({ logger: false }) -test('test async hooks', (t, done) => { +test('test async hooks', async (t) => { app.get('/', function (request, reply) { reply.send({ id: 42 }) }) @@ -29,43 +28,25 @@ test('test async hooks', (t, done) => { reply.send({ id: 42 }) }) - app.listen({ port: 0 }, function (err, address) { - t.assert.ifError(err) + t.after(() => app.close()) - sget({ - method: 'POST', - url: 'http://localhost:' + app.server.address().port, - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) + const fastifyServer = await app.listen({ port: 0 }) - sget({ - method: 'POST', - url: 'http://localhost:' + app.server.address().port, - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) + const result1 = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ hello: 'world' }) + }) + t.assert.strictEqual(result1.status, 200) - sget({ - method: 'GET', - url: 'http://localhost:' + app.server.address().port, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - app.close() - t.assert.strictEqual(remainingIds.size, 0) - done() - }) - }) - }) + const result2 = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ hello: 'world' }) }) + t.assert.strictEqual(result2.status, 200) + + const result3 = await fetch(fastifyServer) + t.assert.strictEqual(result3.status, 200) + t.assert.strictEqual(remainingIds.size, 0) }) From 508f3dc2fa94e060aad77bd7c992024557d02604 Mon Sep 17 00:00:00 2001 From: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com> Date: Sun, 15 Jun 2025 08:58:51 +0200 Subject: [PATCH 1089/1295] feat(types): export more schema related types (#6207) Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- fastify.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.d.ts b/fastify.d.ts index f2e595d6e12..84cb64d7384 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -28,7 +28,7 @@ import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './type import { FastifyReply } from './types/reply' import { FastifyRequest, RequestGenericInterface } from './types/request' import { RouteGenericInterface, RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route' -import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema' +import { FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema' import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' import { FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike } from './types/type-provider' import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './types/utils' @@ -179,7 +179,7 @@ declare namespace fastify { FastifyRegister, FastifyRegisterOptions, RegisterOptions, // './types/register' FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction, // './types/content-type-parser' FastifyError, // '@fastify/error' - FastifySchema, FastifySchemaCompiler, // './types/schema' + FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, // './types/schema' HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault, // './types/utils' DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, // './types/hooks' FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory' From 5c375d4501b14646334ef2543a06120c591d8d67 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 15 Jun 2025 14:17:57 +0200 Subject: [PATCH 1090/1295] chore: removed simple-get from copy tests (#6198) --- test/http-methods/copy.test.js | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/test/http-methods/copy.test.js b/test/http-methods/copy.test.js index 3dbb1b41afd..15a4fc807ef 100644 --- a/test/http-methods/copy.test.js +++ b/test/http-methods/copy.test.js @@ -1,29 +1,13 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('../../fastify')() fastify.addHttpMethod('COPY') -test('can be created - copy', (t, done) => { - t.plan(4) +test('can be created - copy', async t => { + t.plan(3) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - url: `http://localhost:${fastify.server.address().port}/test.txt`, - method: 'COPY', - headers: { - Destination: '/test2.txt' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 204) - done() - }) - }) + t.after(() => fastify.close()) try { fastify.route({ @@ -37,4 +21,15 @@ test('can be created - copy', (t, done) => { } catch (e) { t.assert.fail() } + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(`${fastifyServer}/test.txt`, { + method: 'COPY', + headers: { + Destination: '/test2.txt' + } + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 204) }) From 718214a2ee9062b8a63728f0fb32b3a172376370 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 16 Jun 2025 08:59:43 +0200 Subject: [PATCH 1091/1295] chore: removed simple-get from request-error test (#6184) --- test/request-error.test.js | 157 ++++++++++++++----------------------- 1 file changed, 57 insertions(+), 100 deletions(-) diff --git a/test/request-error.test.js b/test/request-error.test.js index 58f35d9dcd3..2dde9b1ca99 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -1,7 +1,6 @@ 'use strict' const { connect } = require('node:net') -const sget = require('simple-get').concat const { test } = require('node:test') const Fastify = require('..') const { kRequest } = require('../lib/symbols.js') @@ -323,8 +322,8 @@ test('default clientError replies with bad request on reused keep-alive connecti }) }) -test('request.routeOptions.method is an uppercase string /1', (t, done) => { - t.plan(4) +test('request.routeOptions.method is an uppercase string /1', async t => { + t.plan(3) const fastify = Fastify() const handler = function (req, res) { t.assert.strictEqual('POST', req.routeOptions.method) @@ -335,26 +334,20 @@ test('request.routeOptions.method is an uppercase string /1', (t, done) => { bodyLimit: 1000, handler }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) -test('request.routeOptions.method is an uppercase string /2', (t, done) => { - t.plan(4) +test('request.routeOptions.method is an uppercase string /2', async t => { + t.plan(3) const fastify = Fastify() const handler = function (req, res) { t.assert.strictEqual('POST', req.routeOptions.method) @@ -367,26 +360,20 @@ test('request.routeOptions.method is an uppercase string /2', (t, done) => { bodyLimit: 1000, handler }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) -test('request.routeOptions.method is an uppercase string /3', (t, done) => { - t.plan(4) +test('request.routeOptions.method is an uppercase string /3', async t => { + t.plan(3) const fastify = Fastify() const handler = function (req, res) { t.assert.strictEqual('POST', req.routeOptions.method) @@ -399,26 +386,20 @@ test('request.routeOptions.method is an uppercase string /3', (t, done) => { bodyLimit: 1000, handler }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) -test('request.routeOptions.method is an array with uppercase string', (t, done) => { - t.plan(4) +test('request.routeOptions.method is an array with uppercase string', async t => { + t.plan(3) const fastify = Fastify() const handler = function (req, res) { t.assert.deepStrictEqual(['POST'], req.routeOptions.method) @@ -431,26 +412,20 @@ test('request.routeOptions.method is an array with uppercase string', (t, done) bodyLimit: 1000, handler }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port, - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) }) -test('test request.routeOptions.version', (t, done) => { - t.plan(7) +test('test request.routeOptions.version', async t => { + t.plan(6) const fastify = Fastify() fastify.route({ @@ -471,40 +446,22 @@ test('test request.routeOptions.version', (t, done) => { reply.send({}) } }) - fastify.listen({ port: 0 }, function (err) { - t.assert.ifError(err) - t.after(() => fastify.close()) - - let pending = 2 - - function completed () { - if (--pending === 0) { - done() - } - } + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/version', - headers: { 'Content-Type': 'application/json', 'Accept-Version': '1.2.0' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completed() - }) + const result1 = await fetch(fastifyServer + '/version', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Accept-Version': '1.2.0' }, + body: JSON.stringify([]) + }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) - sget({ - method: 'POST', - url: 'http://localhost:' + fastify.server.address().port + '/version-undefined', - headers: { 'Content-Type': 'application/json' }, - body: [], - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - completed() - }) + const result2 = await fetch(fastifyServer + '/version-undefined', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) }) + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) }) From 1a698e0e2676a2f25ad76dafc799f4d23a80cde9 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 16 Jun 2025 18:16:05 +0200 Subject: [PATCH 1092/1295] chore: removed simple-get from decorator tests (#6192) --- test/decorator.test.js | 335 ++++++++++------------------------------- 1 file changed, 76 insertions(+), 259 deletions(-) diff --git a/test/decorator.test.js b/test/decorator.test.js index ad8fa51b061..972658f9c58 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -3,7 +3,6 @@ const { test, describe } = require('node:test') const Fastify = require('..') const fp = require('fastify-plugin') -const sget = require('simple-get').concat const symbols = require('../lib/symbols.js') test('server methods should exist', t => { @@ -118,8 +117,24 @@ test('should pass error for missing request decorator', (t, done) => { }) }) -test('decorateReply inside register', (t, done) => { - t.plan(11) +const runTests = async (t, fastifyServer) => { + const endpoints = [ + { path: '/yes', expectedBody: { hello: 'world' } }, + { path: '/no', expectedBody: { hello: 'world' } } + ] + + for (const { path, expectedBody } of endpoints) { + const result = await fetch(`${fastifyServer}${path}`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), expectedBody) + } +} + +test('decorateReply inside register', async (t) => { + t.plan(10) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -138,44 +153,14 @@ test('decorateReply inside register', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - let pending = 2 - - function completed () { - if (--pending === 0) { - done() - } - } - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/no' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) - }) + await runTests(t, fastifyServer) }) -test('decorateReply as plugin (inside .after)', (t, done) => { - t.plan(11) +test('decorateReply as plugin (inside .after)', async t => { + t.plan(10) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -196,44 +181,14 @@ test('decorateReply as plugin (inside .after)', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - let pending = 2 - - function completed () { - if (--pending === 0) { - done() - } - } - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/no' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) - }) + await runTests(t, fastifyServer) }) -test('decorateReply as plugin (outside .after)', (t, done) => { - t.plan(11) +test('decorateReply as plugin (outside .after)', async t => { + t.plan(10) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -254,44 +209,14 @@ test('decorateReply as plugin (outside .after)', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - let pending = 2 - - function completed () { - if (--pending === 0) { - done() - } - } - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/no' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) - }) + await runTests(t, fastifyServer) }) -test('decorateRequest inside register', (t, done) => { - t.plan(11) +test('decorateRequest inside register', async t => { + t.plan(10) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -310,44 +235,14 @@ test('decorateRequest inside register', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - let pending = 2 - - function completed () { - if (--pending === 0) { - done() - } - } - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/no' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) - }) + await runTests(t, fastifyServer) }) -test('decorateRequest as plugin (inside .after)', (t, done) => { - t.plan(11) +test('decorateRequest as plugin (inside .after)', async t => { + t.plan(10) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -368,44 +263,14 @@ test('decorateRequest as plugin (inside .after)', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - let pending = 2 - - function completed () { - if (--pending === 0) { - done() - } - } - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/no' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) - }) + await runTests(t, fastifyServer) }) -test('decorateRequest as plugin (outside .after)', (t, done) => { - t.plan(11) +test('decorateRequest as plugin (outside .after)', async (t) => { + t.plan(10) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -426,40 +291,10 @@ test('decorateRequest as plugin (outside .after)', (t, done) => { reply.send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - let pending = 2 - - function completed () { - if (--pending === 0) { - done() - } - } - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/no' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completed() - }) - }) + await runTests(t, fastifyServer) }) test('decorators should be instance separated', (t, done) => { @@ -1064,8 +899,8 @@ test('plugin required decorators', async t => { await app.ready() }) -test('decorateRequest/decorateReply empty string', (t, done) => { - t.plan(7) +test('decorateRequest/decorateReply empty string', async t => { + t.plan(6) const fastify = Fastify() fastify.decorateRequest('test', '') @@ -1077,25 +912,19 @@ test('decorateRequest/decorateReply empty string', (t, done) => { }) t.after(() => fastify.close()) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const result = await fetch(`${fastifyServer}/yes`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) -test('decorateRequest/decorateReply is undefined', (t, done) => { - t.plan(7) +test('decorateRequest/decorateReply is undefined', async t => { + t.plan(6) const fastify = Fastify() fastify.decorateRequest('test', undefined) @@ -1107,25 +936,19 @@ test('decorateRequest/decorateReply is undefined', (t, done) => { }) t.after(() => fastify.close()) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const result = await fetch(`${fastifyServer}/yes`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) -test('decorateRequest/decorateReply is not set to a value', (t, done) => { - t.plan(7) +test('decorateRequest/decorateReply is not set to a value', async t => { + t.plan(6) const fastify = Fastify() fastify.decorateRequest('test') @@ -1137,21 +960,15 @@ test('decorateRequest/decorateReply is not set to a value', (t, done) => { }) t.after(() => fastify.close()) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/yes' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const result = await fetch(`${fastifyServer}/yes`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) test('decorateRequest with dependencies', (t, done) => { From b01e67d46628fa8c3995c9dce79f06ccf51d845b Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 16 Jun 2025 19:16:47 +0100 Subject: [PATCH 1093/1295] ci: pin third-party actions to commit-hash (#6218) * ci: pin third-party actions to commit-hash * ci(ci): pin actions/dependency-review-action to commit hash not immutable yet --- .github/workflows/backport.yml | 2 +- .github/workflows/benchmark-parser.yml | 2 +- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci-alternative-runtime.yml | 6 +++--- .github/workflows/ci.yml | 2 +- .github/workflows/integration-alternative-runtimes.yml | 4 ++-- .github/workflows/integration.yml | 2 +- .github/workflows/links-check.yml | 2 +- .github/workflows/lock-threads.yml | 2 +- .github/workflows/md-lint.yml | 2 +- .github/workflows/package-manager-ci.yml | 2 +- .github/workflows/test-compare.yml | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 675555fd3a2..52b66aab703 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -26,6 +26,6 @@ jobs: name: Backport steps: - name: Backport - uses: tibdex/backport@v2 + uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2.0.4 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 8e622eac2e7..80f79bc10f7 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -75,7 +75,7 @@ jobs: pull-requests: write steps: - name: Comment PR - uses: thollander/actions-comment-pull-request@v3 + uses: thollander/actions-comment-pull-request@65f9e5c9a1f2cd378bd74b2e057c9736982a8e74 # v3.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} message: | diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index ae44d7f9708..0d00025be6f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -75,7 +75,7 @@ jobs: pull-requests: write steps: - name: Comment PR - uses: thollander/actions-comment-pull-request@v3 + uses: thollander/actions-comment-pull-request@65f9e5c9a1f2cd378bd74b2e057c9736982a8e74 # v3.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} message: | diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index 75020a91c15..d8955abfe74 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -41,7 +41,7 @@ jobs: with: persist-credentials: false - - uses: nodesource/setup-nsolid@v1 + - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1 if: ${{ matrix.runtime == 'nsolid' }} with: node-version: ${{ matrix.node-version }} @@ -65,7 +65,7 @@ jobs: with: persist-credentials: false - - uses: nodesource/setup-nsolid@v1 + - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1 with: node-version: 20 nsolid-version: 5 @@ -91,7 +91,7 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - - uses: nodesource/setup-nsolid@v1 + - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1 with: nsolid-version: 5 - name: install fastify diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce9fe00cdd4..98255ce5ed7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Dependency review - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 check-licenses: name: Check licenses diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index 06724b93684..e58c66fb83c 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -38,14 +38,14 @@ jobs: with: persist-credentials: false - - uses: nodesource/setup-nsolid@v1 + - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1 if: ${{ matrix.runtime == 'nsolid' }} with: node-version: ${{ matrix.node-version }} nsolid-version: ${{ matrix.nsolid-version }} - name: Install Pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 with: version: ${{ matrix.pnpm-version }} diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index d5f16f20ca6..2af82678e90 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -41,7 +41,7 @@ jobs: check-latest: true - name: Install Pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 with: version: ${{ matrix.pnpm-version }} diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 4ef9aa2fb47..16bcfd07334 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -24,7 +24,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 + uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1 with: fail: true # As external links behavior is not predictable, we check only internal links diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml index e7e8026fad4..883286d8476 100644 --- a/.github/workflows/lock-threads.yml +++ b/.github/workflows/lock-threads.yml @@ -18,7 +18,7 @@ jobs: issues: write pull-requests: write steps: - - uses: jsumners/lock-threads@b27edac0ac998d42b2815e122b6c24b32b568321 + - uses: jsumners/lock-threads@e460dfeb36e731f3aeb214be6b0c9a9d9a67eda6 # v3.0.0 with: issue-inactive-days: '90' exclude-any-issue-labels: 'discussion,good first issue,help wanted' diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index 9ae9dfd8716..a3493edad5e 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -38,7 +38,7 @@ jobs: run: npm install --ignore-scripts - name: Add Matcher - uses: xt0rted/markdownlint-problem-matcher@1a5fabfb577370cfdf5af944d418e4be3ea06f27 + uses: xt0rted/markdownlint-problem-matcher@1a5fabfb577370cfdf5af944d418e4be3ea06f27 # v3.0.0 - name: Run Linter run: ./node_modules/.bin/markdownlint-cli2 diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index d3062d04f77..6356a5aefeb 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -33,7 +33,7 @@ jobs: check-latest: true - name: Install with pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 with: version: ${{ matrix.pnpm-version }} diff --git a/.github/workflows/test-compare.yml b/.github/workflows/test-compare.yml index 16b494d62a3..c8689e237b6 100644 --- a/.github/workflows/test-compare.yml +++ b/.github/workflows/test-compare.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write steps: - name: Test compare - uses: nearform-actions/github-action-test-compare@v1 + uses: nearform-actions/github-action-test-compare@d50bc37a05e736bb40db0eebc8fdad3e33ece136 # v1.0.26 with: label: test-compare testCommand: 'npm run test:ci' From 8435cb10f93e149c8956f815db9e5dd59789a1d9 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Tue, 17 Jun 2025 09:13:38 +0200 Subject: [PATCH 1094/1295] fix: close fastify instance (#6177) * chore: removed simple-get from secure with fallback test * fix: after --------- Co-authored-by: Manuel Spigolon Co-authored-by: Carlos Fuentes --- test/http2/secure-with-fallback.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/http2/secure-with-fallback.test.js b/test/http2/secure-with-fallback.test.js index 651f3b28da9..25566389226 100644 --- a/test/http2/secure-with-fallback.test.js +++ b/test/http2/secure-with-fallback.test.js @@ -39,7 +39,7 @@ test('secure with fallback', async (t) => { throw new Error('kaboom') }) - t.after(() => { fastify.close() }) + t.after(() => fastify.close()) const fastifyServer = await fastify.listen({ port: 0 }) @@ -80,6 +80,7 @@ test('secure with fallback', async (t) => { await t.test('http1 get request', async t => { t.plan(4) + const result = await fetch(fastifyServer, { dispatcher: new Agent({ connect: { @@ -97,6 +98,7 @@ test('secure with fallback', async (t) => { await t.test('http1 get error', async t => { t.plan(2) + const result = await fetch(`${fastifyServer}/error`, { dispatcher: new Agent({ connect: { From 93d51eaa326245b6b58724f14b315c70cb2e0c36 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 17 Jun 2025 12:13:24 +0100 Subject: [PATCH 1095/1295] chore(license): remove date range (#6219) Signed-off-by: Frazer Smith --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index deef1e20e65..e1a6f37b42c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2025 The Fastify Team +Copyright (c) 2016-present The Fastify Team The Fastify team members are listed at https://github.com/fastify/fastify#team and in the README file. From 02c28ba09fac4241dae783c2367340bdd18156c9 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Tue, 17 Jun 2025 19:53:52 +0200 Subject: [PATCH 1096/1295] chore: removed simple-get from plugin test (#6180) --- test/plugin.1.test.js | 108 ++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 68 deletions(-) diff --git a/test/plugin.1.test.js b/test/plugin.1.test.js index 0b1dccd74bc..34027aa3d03 100644 --- a/test/plugin.1.test.js +++ b/test/plugin.1.test.js @@ -3,9 +3,7 @@ const t = require('node:test') const test = t.test const Fastify = require('../fastify') -const sget = require('simple-get').concat const fp = require('fastify-plugin') -const { waitForCb } = require('./toolkit') test('require a plugin', (t, testDone) => { t.plan(1) @@ -59,8 +57,8 @@ test('plugin metadata - naming plugins', async t => { await fastify.ready() }) -test('fastify.register with fastify-plugin should not encapsulate his code', (t, testDone) => { - t.plan(10) +test('fastify.register with fastify-plugin should not encapsulate his code', async t => { + t.plan(9) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -89,25 +87,19 @@ test('fastify.register with fastify-plugin should not encapsulate his code', (t, t.assert.ok(!fastify.test) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - testDone() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) -test('fastify.register with fastify-plugin should provide access to external fastify instance if opts argument is a function', (t, testDone) => { - t.plan(22) +test('fastify.register with fastify-plugin should provide access to external fastify instance if opts argument is a function', async t => { + t.plan(21) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -169,25 +161,19 @@ test('fastify.register with fastify-plugin should provide access to external fas t.assert.ok(!fastify.global) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - testDone() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) -test('fastify.register with fastify-plugin registers fastify level plugins', t => { - t.plan(15) +test('fastify.register with fastify-plugin registers fastify level plugins', async t => { + t.plan(14) const fastify = Fastify() function fastifyPlugin (instance, opts, done) { @@ -225,34 +211,20 @@ test('fastify.register with fastify-plugin registers fastify level plugins', t = reply.send({ test: fastify.test }) }) - const { stepIn, patience } = waitForCb({ steps: 2 }) - - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { test: 'first' }) - stepIn() - }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/test2' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { test2: 'second' }) - stepIn() - }) - }) - - return patience + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result1 = await fetch(fastifyServer) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { test: 'first' }) + + const result2 = await fetch(fastifyServer + '/test2') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { test2: 'second' }) }) From c1edd611646a2d7ce1ffdf585a2a94160724721d Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Tue, 17 Jun 2025 19:54:23 +0200 Subject: [PATCH 1097/1295] chore: removed simple-get from plugin-2 test (#6181) --- test/plugin.2.test.js | 110 +++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 70 deletions(-) diff --git a/test/plugin.2.test.js b/test/plugin.2.test.js index bdee9585731..a073995d843 100644 --- a/test/plugin.2.test.js +++ b/test/plugin.2.test.js @@ -2,12 +2,10 @@ const { test } = require('node:test') const Fastify = require('../fastify') -const sget = require('simple-get').concat const fp = require('fastify-plugin') -const { waitForCb } = require('./toolkit') -test('check dependencies - should not throw', (t, testDone) => { - t.plan(12) +test('check dependencies - should not throw', async t => { + t.plan(11) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -42,25 +40,19 @@ test('check dependencies - should not throw', (t, testDone) => { t.assert.ok(!fastify.otherTest) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - testDone() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) -test('check dependencies - should throw', (t, testDone) => { - t.plan(12) +test('check dependencies - should throw', async t => { + t.plan(11) const fastify = Fastify() fastify.register((instance, opts, done) => { @@ -95,21 +87,15 @@ test('check dependencies - should throw', (t, testDone) => { t.assert.ok(!fastify.test) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - testDone() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) test('set the plugin name based on the plugin displayName symbol', (t, testDone) => { @@ -274,8 +260,8 @@ test('approximate a plugin name also when fastify-plugin has no meta data', (t, }) }) -test('plugin encapsulation', (t, testDone) => { - t.plan(10) +test('plugin encapsulation', async t => { + t.plan(9) const fastify = Fastify() t.after(() => fastify.close()) @@ -309,36 +295,20 @@ test('plugin encapsulation', (t, testDone) => { t.assert.ok(!fastify.test) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - const completion = waitForCb({ - steps: 2 - }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/first' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { plugin: 'first' }) - completion.stepIn() - }) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/second' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { plugin: 'second' }) - completion.stepIn() - }) - - completion.patience.then(testDone) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result1 = await fetch(fastifyServer + '/first') + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { plugin: 'first' }) + + const result2 = await fetch(fastifyServer + '/second') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { plugin: 'second' }) }) From bc2739f9a4866dca3dd1157b51244c72644d55d7 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Tue, 17 Jun 2025 19:55:05 +0200 Subject: [PATCH 1098/1295] chore: removed simple-get from plugin-3 test (#6182) --- test/plugin.3.test.js | 93 ++++++++++++------------------------------- 1 file changed, 25 insertions(+), 68 deletions(-) diff --git a/test/plugin.3.test.js b/test/plugin.3.test.js index ce518a86f53..423bb74d6a4 100644 --- a/test/plugin.3.test.js +++ b/test/plugin.3.test.js @@ -2,9 +2,7 @@ const { test } = require('node:test') const Fastify = require('../fastify') -const sget = require('simple-get').concat const fp = require('fastify-plugin') -const { waitForCb } = require('./toolkit') test('if a plugin raises an error and there is not a callback to handle it, the server must not start', (t, testDone) => { t.plan(2) @@ -21,8 +19,8 @@ test('if a plugin raises an error and there is not a callback to handle it, the }) }) -test('add hooks after route declaration', (t, testDone) => { - t.plan(3) +test('add hooks after route declaration', async t => { + t.plan(2) const fastify = Fastify() t.after(() => fastify.close()) @@ -59,22 +57,15 @@ test('add hooks after route declaration', (t, testDone) => { done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body), { hook1: true, hook2: true, hook3: true }) - testDone() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.deepStrictEqual(await result.json(), { hook1: true, hook2: true, hook3: true }) }) -test('nested plugins', (t, testDone) => { - t.plan(5) +test('nested plugins', async t => { + t.plan(4) const fastify = Fastify() @@ -98,36 +89,19 @@ test('nested plugins', (t, testDone) => { done() }, { prefix: '/parent' }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - const completion = waitForCb({ - steps: 2 - }) + const result1 = await fetch(fastifyServer + '/parent/child1') + t.assert.ok(result1.ok) + t.assert.deepStrictEqual(await result1.text(), 'I am child 1') - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/parent/child1' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(body.toString(), 'I am child 1') - completion.stepIn() - }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/parent/child2' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(body.toString(), 'I am child 2') - completion.stepIn() - }) - - completion.patience.then(testDone) - }) + const result2 = await fetch(fastifyServer + '/parent/child2') + t.assert.ok(result2.ok) + t.assert.deepStrictEqual(await result2.text(), 'I am child 2') }) -test('nested plugins awaited', (t, testDone) => { - t.plan(5) +test('nested plugins awaited', async t => { + t.plan(4) const fastify = Fastify() @@ -147,32 +121,15 @@ test('nested plugins awaited', (t, testDone) => { }, { prefix: '/child2' }) }, { prefix: '/parent' }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - const completion = waitForCb({ - steps: 2 - }) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/parent/child1' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(body.toString(), 'I am child 1') - completion.stepIn() - }) + const result1 = await fetch(fastifyServer + '/parent/child1') + t.assert.ok(result1.ok) + t.assert.deepStrictEqual(await result1.text(), 'I am child 1') - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/parent/child2' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(body.toString(), 'I am child 2') - completion.stepIn() - }) - completion.patience.then(testDone) - }) + const result2 = await fetch(fastifyServer + '/parent/child2') + t.assert.ok(result2.ok) + t.assert.deepStrictEqual(await result2.text(), 'I am child 2') }) test('plugin metadata - decorators', (t, testDone) => { From 7dc58951e921d362898861eed74ee0c7e7647c56 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Wed, 18 Jun 2025 01:43:27 +0200 Subject: [PATCH 1099/1295] chore: remove simple get from reply test (#6160) * chore: doGet * chore: remove simple-get * chore: reply * chore: fastifyServer --- test/internals/reply.test.js | 773 +++++++++++++---------------------- 1 file changed, 277 insertions(+), 496 deletions(-) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index c849869f867..0eaced9ea55 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const http = require('node:http') const NotFound = require('http-errors').NotFound const Request = require('../../lib/request') @@ -19,18 +18,17 @@ const { const fs = require('node:fs') const path = require('node:path') -const agent = new http.Agent({ keepAlive: false }) - -const doGet = function (url) { - return new Promise((resolve, reject) => { - sget({ method: 'GET', url, followRedirects: false, agent }, (err, response, body) => { - if (err) { - reject(err) - } else { - resolve({ response, body }) - } - }) +const doGet = async function (url) { + const result = await fetch(url, { + method: 'GET', + redirect: 'manual', + keepAlive: false }) + + return { + response: result, + body: await result.json().catch(() => undefined) + } } test('Once called, Reply should return an object with methods', t => { @@ -279,65 +277,44 @@ test('within an instance', async t => { done() }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - await t.test('custom serializer should be used', (t, done) => { + await t.test('custom serializer should be used', async t => { t.plan(3) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/custom-serializer' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body.toString(), 'hello=world!') - done() - }) + const result = await fetch(fastifyServer + '/custom-serializer') + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello=world!') }) - await t.test('status code and content-type should be correct', (t, done) => { - t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) + await t.test('status code and content-type should be correct', async t => { + t.plan(3) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) - await t.test('auto status code should be 200', (t, done) => { + await t.test('auto status code should be 200', async t => { t.plan(3) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/auto-status-code' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) + const result = await fetch(fastifyServer + '/auto-status-code') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) - await t.test('auto type should be text/plain', (t, done) => { + await t.test('auto type should be text/plain', async t => { t.plan(3) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/auto-type' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) + const result = await fetch(fastifyServer + '/auto-type') + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) await t.test('redirect to `/` - 1', (t, done) => { t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect', function (response) { + http.get(fastifyServer + '/redirect', function (response) { t.assert.strictEqual(response.statusCode, 302) done() }) @@ -346,43 +323,33 @@ test('within an instance', async t => { await t.test('redirect to `/` - 2', (t, done) => { t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code', function (response) { + http.get(fastifyServer + '/redirect-code', function (response) { t.assert.strictEqual(response.statusCode, 301) done() }) }) - await t.test('redirect to `/` - 3', (t, done) => { + await t.test('redirect to `/` - 3', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) + const result = await fetch(fastifyServer + '/redirect') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) - await t.test('redirect to `/` - 4', (t, done) => { + await t.test('redirect to `/` - 4', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) + const result = await fetch(fastifyServer + '/redirect-code') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) await t.test('redirect to `/` - 5', (t, done) => { t.plan(3) - const url = 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-onsend' + const url = fastifyServer + '/redirect-onsend' http.get(url, (response) => { t.assert.strictEqual(response.headers['x-onsend'], 'yes') t.assert.strictEqual(response.headers['content-length'], '0') @@ -391,38 +358,28 @@ test('within an instance', async t => { }) }) - await t.test('redirect to `/` - 6', (t, done) => { + await t.test('redirect to `/` - 6', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) + const result = await fetch(fastifyServer + '/redirect-code-before-call') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) - await t.test('redirect to `/` - 7', (t, done) => { + await t.test('redirect to `/` - 7', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call-overwrite' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) + const result = await fetch(fastifyServer + '/redirect-code-before-call-overwrite') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) await t.test('redirect to `/` - 8', (t, done) => { t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call', function (response) { + http.get(fastifyServer + '/redirect-code-before-call', function (response) { t.assert.strictEqual(response.statusCode, 307) done() }) @@ -431,7 +388,7 @@ test('within an instance', async t => { await t.test('redirect to `/` - 9', (t, done) => { t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-code-before-call-overwrite', function (response) { + http.get(fastifyServer + '/redirect-code-before-call-overwrite', function (response) { t.assert.strictEqual(response.statusCode, 302) done() }) @@ -440,15 +397,15 @@ test('within an instance', async t => { await t.test('redirect with async function to `/` - 10', (t, done) => { t.plan(1) - http.get('http://127.0.0.1:' + fastify.server.address().port + '/redirect-async', function (response) { + http.get(fastifyServer + '/redirect-async', function (response) { t.assert.strictEqual(response.statusCode, 302) done() }) }) }) -test('buffer without content type should send a application/octet-stream and raw buffer', (t, done) => { - t.plan(4) +test('buffer without content type should send a application/octet-stream and raw buffer', async t => { + t.plan(3) const fastify = Fastify() @@ -456,20 +413,13 @@ test('buffer without content type should send a application/octet-stream and raw reply.send(Buffer.alloc(1024)) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'application/octet-stream') - t.assert.deepStrictEqual(body, Buffer.alloc(1024)) - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'application/octet-stream') + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), Buffer.alloc(1024)) }) test('Uint8Array without content type should send a application/octet-stream and raw buffer', (t, done) => { @@ -545,8 +495,8 @@ test('TypedArray with content type should not send application/octet-stream', (t }) }) }) -test('buffer with content type should not send application/octet-stream', (t, done) => { - t.plan(4) +test('buffer with content type should not send application/octet-stream', async t => { + t.plan(3) const fastify = Fastify() @@ -555,24 +505,17 @@ test('buffer with content type should not send application/octet-stream', (t, do reply.send(Buffer.alloc(1024)) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body, Buffer.alloc(1024)) - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), Buffer.alloc(1024)) }) -test('stream with content type should not send application/octet-stream', (t, done) => { - t.plan(4) +test('stream with content type should not send application/octet-stream', async t => { + t.plan(3) const fastify = Fastify() @@ -584,23 +527,17 @@ test('stream with content type should not send application/octet-stream', (t, do reply.header('Content-Type', 'text/plain').send(stream) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(body, buf) - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf) }) -test('stream without content type should not send application/octet-stream', (t, done) => { - t.plan(4) +test('stream without content type should not send application/octet-stream', async t => { + t.plan(3) const fastify = Fastify() @@ -611,23 +548,17 @@ test('stream without content type should not send application/octet-stream', (t, reply.send(stream) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], undefined) - t.assert.deepStrictEqual(body, buf) - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), null) + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf) }) -test('stream using reply.raw.writeHead should return customize headers', (t, done) => { - t.plan(6) +test('stream using reply.raw.writeHead should return customize headers', async t => { + t.plan(5) const fastify = Fastify() const fs = require('node:fs') @@ -647,24 +578,18 @@ test('stream using reply.raw.writeHead should return customize headers', (t, don reply.send(stream) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers.location, '/') - t.assert.strictEqual(response.headers['Content-Type'], undefined) - t.assert.deepStrictEqual(body, buf) - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('location'), '/') + t.assert.strictEqual(result.headers.get('content-type'), null) + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf) }) -test('plain string without content type should send a text/plain', (t, done) => { - t.plan(4) +test('plain string without content type should send a text/plain', async t => { + t.plan(3) const fastify = Fastify() @@ -672,24 +597,17 @@ test('plain string without content type should send a text/plain', (t, done) => reply.send('hello world!') }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'text/plain; charset=utf-8') - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) -test('plain string with content type should be sent unmodified', (t, done) => { - t.plan(4) +test('plain string with content type should be sent unmodified', async t => { + t.plan(3) const fastify = Fastify() @@ -697,24 +615,17 @@ test('plain string with content type should be sent unmodified', (t, done) => { reply.type('text/css').send('hello world!') }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'text/css') - t.assert.deepStrictEqual(body.toString(), 'hello world!') - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/css') + t.assert.deepStrictEqual(await result.text(), 'hello world!') }) -test('plain string with content type and custom serializer should be serialized', (t, done) => { - t.plan(4) +test('plain string with content type and custom serializer should be serialized', async t => { + t.plan(3) const fastify = Fastify() @@ -725,24 +636,17 @@ test('plain string with content type and custom serializer should be serialized' .send('hello world!') }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'text/css') - t.assert.deepStrictEqual(body.toString(), 'serialized') - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/css') + t.assert.deepStrictEqual(await result.text(), 'serialized') }) -test('plain string with content type application/json should NOT be serialized as json', (t, done) => { - t.plan(4) +test('plain string with content type application/json should NOT be serialized as json', async t => { + t.plan(3) const fastify = Fastify() @@ -750,24 +654,17 @@ test('plain string with content type application/json should NOT be serialized a reply.type('application/json').send('{"key": "hello world!"}') }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') - t.assert.deepStrictEqual(body.toString(), '{"key": "hello world!"}') - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), '{"key": "hello world!"}') }) test('plain string with custom json content type should NOT be serialized as json', async t => { - t.plan(12) + t.plan(18) const fastify = Fastify() @@ -806,27 +703,18 @@ test('plain string with custom json content type should NOT be serialized as jso }) }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - await Promise.all(Object.keys(customSamples).map(path => { - return new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/' + path - }, (err, response, body) => { - if (err) { - reject(err) - } - t.assert.strictEqual(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8') - t.assert.deepStrictEqual(body.toString(), customSamples[path].sample) - resolve() - }) - }) + await Promise.all(Object.keys(customSamples).map(async path => { + const result = await fetch(fastifyServer + '/' + path) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), customSamples[path].mimeType + '; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), customSamples[path].sample) })) }) -test('non-string with content type application/json SHOULD be serialized as json', (t, done) => { - t.plan(4) +test('non-string with content type application/json SHOULD be serialized as json', async t => { + t.plan(3) const fastify = Fastify() @@ -834,24 +722,17 @@ test('non-string with content type application/json SHOULD be serialized as json reply.type('application/json').send({ key: 'hello world!' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ key: 'hello world!' })) - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ key: 'hello world!' })) }) -test('non-string with custom json\'s content-type SHOULD be serialized as json', (t, done) => { - t.plan(4) +test('non-string with custom json\'s content-type SHOULD be serialized as json', async t => { + t.plan(3) const fastify = Fastify() @@ -859,24 +740,17 @@ test('non-string with custom json\'s content-type SHOULD be serialized as json', reply.type('application/json; version=2; ').send({ key: 'hello world!' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'application/json; version=2; charset=utf-8') - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ key: 'hello world!' })) - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; version=2; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ key: 'hello world!' })) }) test('non-string with custom json content type SHOULD be serialized as json', async t => { - t.plan(10) + t.plan(15) const fastify = Fastify() t.after(() => fastify.close()) @@ -910,22 +784,13 @@ test('non-string with custom json content type SHOULD be serialized as json', as }) }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - await Promise.all(Object.keys(customSamples).map(path => { - return new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/' + path - }, (err, response, body) => { - if (err) { - reject(err) - } - t.assert.strictEqual(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8') - t.assert.deepStrictEqual(body.toString(), JSON.stringify(customSamples[path].sample)) - resolve() - }) - }) + await Promise.all(Object.keys(customSamples).map(async path => { + const result = await fetch(fastifyServer + '/' + path) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), customSamples[path].mimeType + '; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), JSON.stringify(customSamples[path].sample)) })) }) @@ -963,8 +828,8 @@ test('error object with a content type that is not application/json should work' } }) -test('undefined payload should be sent as-is', (t, done) => { - t.plan(6) +test('undefined payload should be sent as-is', async t => { + t.plan(5) const fastify = Fastify() @@ -977,25 +842,19 @@ test('undefined payload should be sent as-is', (t, done) => { reply.code(204).send() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: `http://127.0.0.1:${fastify.server.address().port}` - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], undefined) - t.assert.strictEqual(response.headers['content-length'], undefined) - t.assert.strictEqual(body.length, 0) - done() - }) - }) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), null) + t.assert.strictEqual(result.headers.get('content-length'), null) + const body = await result.text() + t.assert.strictEqual(body.length, 0) }) test('for HEAD method, no body should be sent but content-length should be', async t => { - t.plan(8) + t.plan(10) const fastify = Fastify() t.after(() => fastify.close()) @@ -1024,37 +883,23 @@ test('for HEAD method, no body should be sent but content-length should be', asy reply.code(200).send(null) }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - const promise1 = new Promise((resolve, reject) => { - sget({ - method: 'HEAD', - url: `http://127.0.0.1:${fastify.server.address().port}` - }, (err, response, body) => { - if (err) { - reject(err) - } - t.assert.strictEqual(response.headers['content-type'], contentType) - t.assert.strictEqual(response.headers['content-length'], bodySize.toString()) - t.assert.strictEqual(body.length, 0) - resolve() - }) - }) + const promise1 = (async () => { + const result = await fetch(fastifyServer, { method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), contentType) + t.assert.strictEqual(result.headers.get('content-length'), bodySize.toString()) + t.assert.strictEqual((await result.text()).length, 0) + })() - const promise2 = new Promise((resolve, reject) => { - sget({ - method: 'HEAD', - url: `http://127.0.0.1:${fastify.server.address().port}/with/null` - }, (err, response, body) => { - if (err) { - reject(err) - } - t.assert.strictEqual(response.headers['content-type'], contentType) - t.assert.strictEqual(response.headers['content-length'], bodySize.toString()) - t.assert.strictEqual(body.length, 0) - resolve() - }) - }) + const promise2 = (async () => { + const result = await fetch(fastifyServer + '/with/null', { method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), contentType) + t.assert.strictEqual(result.headers.get('content-length'), bodySize.toString()) + t.assert.strictEqual((await result.text()).length, 0) + })() await Promise.all([promise1, promise2]) }) @@ -1081,51 +926,35 @@ test('reply.send(new NotFound()) should not invoke the 404 handler', async t => done() }, { prefix: '/prefixed' }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - const promise1 = new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/not-found' - }, (err, response, body) => { - if (err) { - reject(err) - } - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') - t.assert.deepStrictEqual(JSON.parse(body.toString()), { - statusCode: 404, - error: 'Not Found', - message: 'Not Found' - }) - resolve() + const promise1 = (async () => { + const result = await fetch(`${fastifyServer}/not-found`) + t.assert.strictEqual(result.status, 404) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(await result.text()), { + statusCode: 404, + error: 'Not Found', + message: 'Not Found' }) - }) + })() - const promise2 = new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/prefixed/not-found' - }, (err, response, body) => { - if (err) { - reject(err) - } - t.assert.strictEqual(response.statusCode, 404) - t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') - t.assert.deepStrictEqual(JSON.parse(body.toString()), { - statusCode: 404, - error: 'Not Found', - message: 'Not Found' - }) - resolve() + const promise2 = (async () => { + const result = await fetch(`${fastifyServer}/prefixed/not-found`) + t.assert.strictEqual(result.status, 404) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(await result.text()), { + statusCode: 404, + error: 'Not Found', + message: 'Not Found' }) - }) + })() await Promise.all([promise1, promise2]) }) -test('reply can set multiple instances of same header', (t, done) => { - t.plan(4) +test('reply can set multiple instances of same header', async t => { + t.plan(3) const fastify = require('../../')() @@ -1136,24 +965,17 @@ test('reply can set multiple instances of same header', (t, done) => { .send({}) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.ok(response.headers['set-cookie']) - t.assert.deepStrictEqual(response.headers['set-cookie'], ['one', 'two']) - done() - }) - }) + const result = await fetch(`${fastifyServer}/headers`) + t.assert.ok(result.ok) + t.assert.ok(result.headers.get('set-cookie')) + t.assert.deepStrictEqual(result.headers.getSetCookie(), ['one', 'two']) }) -test('reply.hasHeader returns correct values', (t, done) => { - t.plan(3) +test('reply.hasHeader returns correct values', async t => { + t.plan(2) const fastify = require('../../')() @@ -1164,20 +986,14 @@ test('reply.hasHeader returns correct values', (t, done) => { reply.send() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => { - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await fetch(`${fastifyServer}/headers`) }) -test('reply.getHeader returns correct values', (t, done) => { - t.plan(5) +test('reply.getHeader returns correct values', async t => { + t.plan(4) const fastify = require('../../')() @@ -1198,16 +1014,10 @@ test('reply.getHeader returns correct values', (t, done) => { reply.send() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => { - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await fetch(`${fastifyServer}/headers`) }) test('reply.getHeader returns raw header if there is not in the reply headers', (t) => { @@ -1254,8 +1064,8 @@ test('reply.getHeaders returns correct values', (t, done) => { }) }) -test('reply.removeHeader can remove the value', (t, done) => { - t.plan(4) +test('reply.removeHeader can remove the value', async t => { + t.plan(3) const fastify = require('../../')() @@ -1271,20 +1081,13 @@ test('reply.removeHeader can remove the value', (t, done) => { reply.send() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => { - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + await fetch(`${fastifyServer}/headers`) }) -test('reply.header can reset the value', (t, done) => { - t.plan(2) +test('reply.header can reset the value', async t => { + t.plan(1) const fastify = require('../../')() @@ -1298,21 +1101,14 @@ test('reply.header can reset the value', (t, done) => { reply.send() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => { - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + + await fetch(`${fastifyServer}/headers`) }) // https://github.com/fastify/fastify/issues/3030 -test('reply.hasHeader computes raw and fastify headers', (t, done) => { - t.plan(3) +test('reply.hasHeader computes raw and fastify headers', async t => { + t.plan(2) const fastify = require('../../')() @@ -1327,16 +1123,10 @@ test('reply.hasHeader computes raw and fastify headers', (t, done) => { reply.send() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, () => { - done() - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await fetch(`${fastifyServer}/headers`) }) test('Reply should handle JSON content type with a charset', async t => { @@ -1485,7 +1275,7 @@ test('.statusCode is getter and setter', (t, done) => { }) test('reply.header setting multiple cookies as multiple Set-Cookie headers', async t => { - t.plan(4) + t.plan(5) const fastify = require('../../')() t.after(() => fastify.close()) @@ -1499,21 +1289,12 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', asy .send({}) }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - await new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'http://127.0.0.1:' + fastify.server.address().port + '/headers' - }, (err, response, body) => { - if (err) { - reject(err) - } - t.assert.ok(response.headers['set-cookie']) - t.assert.deepStrictEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six']) - resolve() - }) - }) + const result = await fetch(`${fastifyServer}/headers`) + t.assert.ok(result.ok) + t.assert.ok(result.headers.get('set-cookie')) + t.assert.deepStrictEqual(result.headers.getSetCookie(), ['one', 'two', 'three', 'four', 'five', 'six']) const response = await fastify.inject('/headers') t.assert.ok(response.headers['set-cookie']) @@ -1937,12 +1718,12 @@ test('redirect to an invalid URL should not crash the server', async t => { } }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) { - const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=1`) - t.assert.strictEqual(response.statusCode, 500) - t.assert.deepStrictEqual(JSON.parse(body), { + const { response, body } = await doGet(`${fastifyServer}/redirect?useCase=1`) + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(body, { statusCode: 500, code: 'ERR_INVALID_CHAR', error: 'Internal Server Error', @@ -1950,15 +1731,15 @@ test('redirect to an invalid URL should not crash the server', async t => { }) } { - const { response } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=2`) - t.assert.strictEqual(response.statusCode, 302) - t.assert.strictEqual(response.headers.location, '/?key=a%E2%80%99b') + const { response } = await doGet(`${fastifyServer}/redirect?useCase=2`) + t.assert.strictEqual(response.status, 302) + t.assert.strictEqual(response.headers.get('location'), '/?key=a%E2%80%99b') } { - const { response } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/redirect?useCase=3`) - t.assert.strictEqual(response.statusCode, 302) - t.assert.strictEqual(response.headers.location, '/?key=ab') + const { response } = await doGet(`${fastifyServer}/redirect?useCase=3`) + t.assert.strictEqual(response.status, 302) + t.assert.strictEqual(response.headers.get('location'), '/?key=ab') } await fastify.close() @@ -1983,11 +1764,11 @@ test('invalid response headers should not crash the server', async t => { } }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) - t.assert.strictEqual(response.statusCode, 500) - t.assert.deepStrictEqual(JSON.parse(body), { + const { response, body } = await doGet(`${fastifyServer}/bad-headers`) + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(body, { statusCode: 500, code: 'ERR_INVALID_CHAR', error: 'Internal Server Error', @@ -2012,11 +1793,11 @@ test('invalid response headers when sending back an error', async t => { } }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) - t.assert.strictEqual(response.statusCode, 500) - t.assert.deepStrictEqual(JSON.parse(body), { + const { response, body } = await doGet(`${fastifyServer}/bad-headers`) + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(body, { statusCode: 500, code: 'ERR_INVALID_CHAR', error: 'Internal Server Error', @@ -2046,11 +1827,11 @@ test('invalid response headers and custom error handler', async t => { reply.status(500).send({ ops: true }) }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) - const { response, body } = await doGet(`http://127.0.0.1:${fastify.server.address().port}/bad-headers`) - t.assert.strictEqual(response.statusCode, 500) - t.assert.deepStrictEqual(JSON.parse(body), { + const { response, body } = await doGet(`${fastifyServer}/bad-headers`) + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(body, { statusCode: 500, code: 'ERR_INVALID_CHAR', error: 'Internal Server Error', From f863b9a655261d9893921b6c6d06368889581124 Mon Sep 17 00:00:00 2001 From: David Wood Date: Wed, 18 Jun 2025 05:09:00 -0500 Subject: [PATCH 1100/1295] feat(types): add missing error types (#6217) * feat(types): add missing error types * add tests to ensure that the errors are typed in errors.d.ts * fix node 20 --------- Co-authored-by: Aras Abbasi Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- test/internals/errors.test.js | 39 +++++++++++++++++++++++++++++++++++ test/types/errors.test-d.ts | 13 +++++++++++- types/errors.d.ts | 11 +++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index f5930d6c7d6..3f7794135de 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -931,3 +931,42 @@ test('Ensure that non-existing errors are not in Errors.md documented', t => { t.assert.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) } }) + +test('Ensure that all errors are in errors.d.ts', t => { + t.plan(expectedErrors) + + const errorsDts = readFileSync(resolve(__dirname, '../../types/errors.d.ts'), 'utf8') + + const FastifyErrorCodesRE = /export type FastifyErrorCodes = Record<([^>]+),\s*FastifyErrorConstructor>/m + + const [, errorCodeType] = errorsDts.match(FastifyErrorCodesRE) + + const errorCodeRE = /'([A-Z0-9_]+)'/g + const matches = errorCodeType.matchAll(errorCodeRE) + const errorTypes = [...matches].map(match => match[1]) + const exportedKeys = Object.keys(errors) + + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + t.assert.ok(errorTypes.includes(key), key) + } + } +}) + +test('Ensure that non-existing errors are not in errors.d.ts', t => { + t.plan(expectedErrors) + + const errorsDts = readFileSync(resolve(__dirname, '../../types/errors.d.ts'), 'utf8') + + const FastifyErrorCodesRE = /export type FastifyErrorCodes = Record<([^>]+),\s*FastifyErrorConstructor>/m + + const [, errorCodeType] = errorsDts.match(FastifyErrorCodesRE) + + const errorCodeRE = /'([A-Z0-9_]+)'/g + const matches = errorCodeType.matchAll(errorCodeRE) + const exportedKeys = Object.keys(errors) + + for (const match of matches) { + t.assert.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) + } +}) diff --git a/test/types/errors.test-d.ts b/test/types/errors.test-d.ts index 497759b0dfc..a81ffe4a977 100644 --- a/test/types/errors.test-d.ts +++ b/test/types/errors.test-d.ts @@ -2,7 +2,6 @@ import { FastifyErrorConstructor } from '@fastify/error' import { expectAssignable } from 'tsd' import { errorCodes } from '../../fastify' -expectAssignable(errorCodes.FST_ERR_VALIDATION) expectAssignable(errorCodes.FST_ERR_NOT_FOUND) expectAssignable(errorCodes.FST_ERR_OPTIONS_NOT_OBJ) expectAssignable(errorCodes.FST_ERR_QSP_NOT_FN) @@ -11,6 +10,9 @@ expectAssignable(errorCodes.FST_ERR_SCHEMA_ERROR_FORMAT expectAssignable(errorCodes.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ) expectAssignable(errorCodes.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR) expectAssignable(errorCodes.FST_ERR_VALIDATION) +expectAssignable(errorCodes.FST_ERR_LISTEN_OPTIONS_INVALID) +expectAssignable(errorCodes.FST_ERR_ERROR_HANDLER_NOT_FN) +expectAssignable(errorCodes.FST_ERR_ERROR_HANDLER_ALREADY_SET) expectAssignable(errorCodes.FST_ERR_CTP_ALREADY_PRESENT) expectAssignable(errorCodes.FST_ERR_CTP_INVALID_TYPE) expectAssignable(errorCodes.FST_ERR_CTP_EMPTY_TYPE) @@ -25,6 +27,8 @@ expectAssignable(errorCodes.FST_ERR_DEC_ALREADY_PRESENT expectAssignable(errorCodes.FST_ERR_DEC_DEPENDENCY_INVALID_TYPE) expectAssignable(errorCodes.FST_ERR_DEC_MISSING_DEPENDENCY) expectAssignable(errorCodes.FST_ERR_DEC_AFTER_START) +expectAssignable(errorCodes.FST_ERR_DEC_REFERENCE_TYPE) +expectAssignable(errorCodes.FST_ERR_DEC_UNDECLARED) expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_TYPE) expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_HANDLER) expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER) @@ -33,7 +37,12 @@ expectAssignable(errorCodes.FST_ERR_MISSING_MIDDLEWARE) expectAssignable(errorCodes.FST_ERR_HOOK_TIMEOUT) expectAssignable(errorCodes.FST_ERR_LOG_INVALID_DESTINATION) expectAssignable(errorCodes.FST_ERR_LOG_INVALID_LOGGER) +expectAssignable(errorCodes.FST_ERR_LOG_INVALID_LOGGER_INSTANCE) +expectAssignable(errorCodes.FST_ERR_LOG_INVALID_LOGGER_CONFIG) +expectAssignable(errorCodes.FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED) expectAssignable(errorCodes.FST_ERR_REP_INVALID_PAYLOAD_TYPE) +expectAssignable(errorCodes.FST_ERR_REP_RESPONSE_BODY_CONSUMED) +expectAssignable(errorCodes.FST_ERR_REP_READABLE_STREAM_LOCKED) expectAssignable(errorCodes.FST_ERR_REP_ALREADY_SENT) expectAssignable(errorCodes.FST_ERR_REP_SENT_VALUE) expectAssignable(errorCodes.FST_ERR_SEND_INSIDE_ONERR) @@ -57,6 +66,7 @@ expectAssignable(errorCodes.FST_ERR_FORCE_CLOSE_CONNECT expectAssignable(errorCodes.FST_ERR_DUPLICATED_ROUTE) expectAssignable(errorCodes.FST_ERR_BAD_URL) expectAssignable(errorCodes.FST_ERR_ASYNC_CONSTRAINT) +expectAssignable(errorCodes.FST_ERR_INVALID_URL) expectAssignable(errorCodes.FST_ERR_ROUTE_OPTIONS_NOT_OBJ) expectAssignable(errorCodes.FST_ERR_ROUTE_DUPLICATED_HANDLER) expectAssignable(errorCodes.FST_ERR_ROUTE_HANDLER_NOT_FN) @@ -71,6 +81,7 @@ expectAssignable(errorCodes.FST_ERR_REOPENED_SERVER) expectAssignable(errorCodes.FST_ERR_INSTANCE_ALREADY_LISTENING) expectAssignable(errorCodes.FST_ERR_PLUGIN_VERSION_MISMATCH) expectAssignable(errorCodes.FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE) +expectAssignable(errorCodes.FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) expectAssignable(errorCodes.FST_ERR_PLUGIN_CALLBACK_NOT_FN) expectAssignable(errorCodes.FST_ERR_PLUGIN_NOT_VALID) expectAssignable(errorCodes.FST_ERR_ROOT_PLG_BOOTED) diff --git a/types/errors.d.ts b/types/errors.d.ts index d59bf6301c3..3b831e30952 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -9,6 +9,9 @@ export type FastifyErrorCodes = Record< 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ' | 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR' | 'FST_ERR_VALIDATION' | + 'FST_ERR_LISTEN_OPTIONS_INVALID' | + 'FST_ERR_ERROR_HANDLER_NOT_FN' | + 'FST_ERR_ERROR_HANDLER_ALREADY_SET' | 'FST_ERR_CTP_ALREADY_PRESENT' | 'FST_ERR_CTP_INVALID_TYPE' | 'FST_ERR_CTP_EMPTY_TYPE' | @@ -24,6 +27,7 @@ export type FastifyErrorCodes = Record< 'FST_ERR_DEC_MISSING_DEPENDENCY' | 'FST_ERR_DEC_AFTER_START' | 'FST_ERR_DEC_REFERENCE_TYPE' | + 'FST_ERR_DEC_UNDECLARED' | 'FST_ERR_HOOK_INVALID_TYPE' | 'FST_ERR_HOOK_INVALID_HANDLER' | 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER' | @@ -32,7 +36,12 @@ export type FastifyErrorCodes = Record< 'FST_ERR_HOOK_TIMEOUT' | 'FST_ERR_LOG_INVALID_DESTINATION' | 'FST_ERR_LOG_INVALID_LOGGER' | + 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE' | + 'FST_ERR_LOG_INVALID_LOGGER_CONFIG' | + 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED' | 'FST_ERR_REP_INVALID_PAYLOAD_TYPE' | + 'FST_ERR_REP_RESPONSE_BODY_CONSUMED' | + 'FST_ERR_REP_READABLE_STREAM_LOCKED' | 'FST_ERR_REP_ALREADY_SENT' | 'FST_ERR_REP_SENT_VALUE' | 'FST_ERR_SEND_INSIDE_ONERR' | @@ -56,7 +65,6 @@ export type FastifyErrorCodes = Record< 'FST_ERR_DUPLICATED_ROUTE' | 'FST_ERR_BAD_URL' | 'FST_ERR_ASYNC_CONSTRAINT' | - 'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE' | 'FST_ERR_INVALID_URL' | 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ' | 'FST_ERR_ROUTE_DUPLICATED_HANDLER' | @@ -72,6 +80,7 @@ export type FastifyErrorCodes = Record< 'FST_ERR_INSTANCE_ALREADY_LISTENING' | 'FST_ERR_PLUGIN_VERSION_MISMATCH' | 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE' | + 'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER' | 'FST_ERR_PLUGIN_CALLBACK_NOT_FN' | 'FST_ERR_PLUGIN_NOT_VALID' | 'FST_ERR_ROOT_PLG_BOOTED' | From 7aef3e0a2cfb4068d0f88763c1e718e5f87bb862 Mon Sep 17 00:00:00 2001 From: AlvesJorge <60895482+AlvesJorge@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:34:28 +0200 Subject: [PATCH 1101/1295] docs: setErrorHandler description (#6227) * docs: update setErrorHandler * docs: re-add trailing spaces Signed-off-by: AlvesJorge <60895482+AlvesJorge@users.noreply.github.com> * docs: fix line too long Signed-off-by: AlvesJorge <60895482+AlvesJorge@users.noreply.github.com> --------- Signed-off-by: AlvesJorge <60895482+AlvesJorge@users.noreply.github.com> --- docs/Reference/Server.md | 9 +++++---- types/instance.d.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 9ae1b902f6d..e6dcbb32523 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1562,15 +1562,16 @@ plugins. `fastify.setErrorHandler(handler(error, request, reply))`: Set a function that -will be called whenever an error happens. The handler is bound to the Fastify -instance and is fully encapsulated, so different plugins can set different error -handlers. *async-await* is supported as well. +will be invoked whenever an exception is thrown during the request lifecycle. +The handler is bound to the Fastify instance and is fully encapsulated, so +different plugins can set different error handlers. *async-await* is +supported as well. If the error `statusCode` is less than 400, Fastify will automatically set it to 500 before calling the error handler. `setErrorHandler` will ***not*** catch: -- errors thrown in an `onResponse` hook because the response has already been +- exceptions thrown in an `onResponse` hook because the response has already been sent to the client. Use the `onSend` hook instead. - not found (404) errors. Use [`setNotFoundHandler`](#set-not-found-handler) instead. diff --git a/types/instance.d.ts b/types/instance.d.ts index bb04d51c096..d67be42062b 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -466,7 +466,7 @@ export interface FastifyInstance< errorHandler: (error: FastifyError, request: FastifyRequest, reply: FastifyReply) => void; /** - * Set a function that will be called whenever an error happens + * Set a function that will be invoked whenever an exception is thrown during the request lifecycle. */ setErrorHandler( handler: (this: FastifyInstance, error: TError, request: FastifyRequest, reply: FastifyReply) => any | Promise From e0315b9db2ac5e9eb447f95812785311b2df4eb9 Mon Sep 17 00:00:00 2001 From: Emanuel Covelli Date: Mon, 23 Jun 2025 13:25:02 +0200 Subject: [PATCH 1102/1295] docs: fix onError hook execution order documentation (#6225) * docs: fix onError hook execution order documentation - Update docs to reflect that onError hook runs BEFORE error handlers - Fix Reply Lifecycle diagram to show correct execution order - Update TypeScript documentation and type definitions - Resolves discrepancy between docs and actual implementation Fixes: #6135 Related: #6146 * docs: simplify onError hook documentation text Address review feedback from jean-michelet: - Remove redundant explanations about hook purpose - Simplify execution order descriptions - Make documentation more concise while preserving key information --- docs/Reference/Hooks.md | 8 ++------ docs/Reference/Lifecycle.md | 4 ++-- docs/Reference/TypeScript.md | 4 +--- types/hooks.d.ts | 2 +- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 10e23c60e65..ebc86f62172 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -189,12 +189,8 @@ specific header in case of error. It is not intended for changing the error, and calling `reply.send` will throw an exception. -This hook will be executed only after -the [Custom Error Handler set by `setErrorHandler`](./Server.md#seterrorhandler) -has been executed, and only if the custom error handler sends an error back to the -user -*(Note that the default error handler always sends the error back to the -user)*. +This hook will be executed before +the [Custom Error Handler set by `setErrorHandler`](./Server.md#seterrorhandler). > ℹ️ Note: Unlike the other hooks, passing an error to the `done` function is not > supported. diff --git a/docs/Reference/Lifecycle.md b/docs/Reference/Lifecycle.md index bd41c1de59a..a1b130b73c0 100644 --- a/docs/Reference/Lifecycle.md +++ b/docs/Reference/Lifecycle.md @@ -70,9 +70,9 @@ submitted, the data flow is as follows: ★ send or return │ │ │ │ │ │ ▼ │ - reply sent ◀── JSON ─┴─ Error instance ──▶ setErrorHandler ◀─────┘ + reply sent ◀── JSON ─┴─ Error instance ──▶ onError Hook ◀───────┘ │ - reply sent ◀── JSON ─┴─ Error instance ──▶ onError Hook + reply sent ◀── JSON ─┴─ Error instance ──▶ setErrorHandler │ └─▶ reply sent ``` diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 124a455c31f..b2181d6a0f6 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -1519,9 +1519,7 @@ specific header in case of error. It is not intended for changing the error, and calling reply.send will throw an exception. -This hook will be executed only after the customErrorHandler has been executed, -and only if the customErrorHandler sends an error back to the user (Note that -the default customErrorHandler always sends the error back to the user). +This hook will be executed before the customErrorHandler. Notice: unlike the other hooks, pass an error to the done function is not supported. diff --git a/types/hooks.d.ts b/types/hooks.d.ts index cbcda11dc49..ed045f10b1f 100644 --- a/types/hooks.d.ts +++ b/types/hooks.d.ts @@ -498,7 +498,7 @@ export type onTimeoutMetaHookHandler< /** * This hook is useful if you need to do some custom error logging or add some specific header in case of error. * It is not intended for changing the error, and calling reply.send will throw an exception. - * This hook will be executed only after the customErrorHandler has been executed, and only if the customErrorHandler sends an error back to the user (Note that the default customErrorHandler always sends the error back to the user). + * This hook will be executed before the customErrorHandler. * Notice: unlike the other hooks, pass an error to the done function is not supported. */ export interface onErrorHookHandler< From 55345987cb51f3266a3f3ea746c2473bf9956036 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 26 Jun 2025 03:54:54 +0800 Subject: [PATCH 1103/1295] fix: handle abort signal in fastify.listen (#6235) * fix: handle abort signal in fastify.listen * test: ensure .listen API signature * fix: aborted during ready or dns.lookup --- fastify.js | 1 + lib/server.js | 13 +++- test/server.test.js | 140 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index aa0b36551f7..e34113680c4 100644 --- a/fastify.js +++ b/fastify.js @@ -211,6 +211,7 @@ function fastify (options) { started: false, ready: false, booting: false, + aborted: false, readyPromise: null }, [kKeepAliveConnections]: keepAliveConnections, diff --git a/lib/server.js b/lib/server.js index a632e52d533..a0e7ae8fa85 100644 --- a/lib/server.js +++ b/lib/server.js @@ -41,10 +41,14 @@ function createServer (options, httpHandler) { throw new FST_ERR_LISTEN_OPTIONS_INVALID('Invalid options.signal') } - if (listenOptions.signal.aborted) { - this.close() + // copy the current signal state + this[kState].aborted = listenOptions.signal.aborted + + if (this[kState].aborted) { + return this.close() } else { const onAborted = () => { + this[kState].aborted = true this.close() } listenOptions.signal.addEventListener('abort', onAborted, { once: true }) @@ -126,7 +130,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o // let's check if we need to bind additional addresses dns.lookup(listenOptions.host, { all: true }, (dnsErr, addresses) => { - if (dnsErr) { + if (dnsErr || this[kState].aborted) { // not blocking the main server listening // this.log.warn('dns.lookup error:', dnsErr) onListen() @@ -239,6 +243,9 @@ function listenPromise (server, listenOptions) { } return this.ready().then(() => { + // skip listen when aborted during ready + if (this[kState].aborted) return + let errEventHandler let listeningEventHandler function cleanup () { diff --git a/test/server.test.js b/test/server.test.js index 1247c77a4d0..f54dca911f0 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -1,9 +1,11 @@ 'use strict' +const dns = require('node:dns') const { test } = require('node:test') const Fastify = require('..') const sget = require('simple-get').concat const undici = require('undici') +const proxyquire = require('proxyquire') test('listen should accept null port', async t => { const fastify = Fastify() @@ -82,6 +84,144 @@ test('Test for hostname and port', (t, end) => { }) test('abort signal', async t => { + await t.test('should close server when aborted after', (t, end) => { + t.plan(2) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + end() + } + + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + fastify.listen({ port: 1234, signal: controller.signal }, (err) => { + t.assert.ifError(err) + controller.abort() + }) + }) + + await t.test('should close server when aborted after - promise', async (t) => { + t.plan(2) + const resolver = {} + resolver.promise = new Promise(function (resolve) { + resolver.resolve = resolve + }) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + resolver.resolve() + } + + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + const address = await fastify.listen({ port: 1234, signal: controller.signal }) + t.assert.ok(address) + controller.abort() + await resolver.promise + }) + + await t.test('should close server when aborted during fastify.ready - promise', async (t) => { + t.plan(2) + const resolver = {} + resolver.promise = new Promise(function (resolve) { + resolver.resolve = resolve + }) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + resolver.resolve() + } + + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + const promise = fastify.listen({ port: 1234, signal: controller.signal }) + controller.abort() + const address = await promise + // since the main server is not listening yet, or will not listen + // it should return undefined + t.assert.strictEqual(address, undefined) + await resolver.promise + }) + + await t.test('should close server when aborted during dns.lookup - promise', async (t) => { + t.plan(2) + const Fastify = proxyquire('..', { + './lib/server.js': proxyquire('../lib/server.js', { + 'node:dns': { + lookup: function (host, option, callback) { + controller.abort() + dns.lookup(host, option, callback) + } + } + }) + }) + const resolver = {} + resolver.promise = new Promise(function (resolve) { + resolver.resolve = resolve + }) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + resolver.resolve() + } + + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + const address = await fastify.listen({ port: 1234, signal: controller.signal }) + // since the main server is already listening then close + // it should return address + t.assert.ok(address) + await resolver.promise + }) + + await t.test('should close server when aborted before', (t, end) => { + t.plan(1) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + end() + } + + const controller = new AbortController() + controller.abort() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + fastify.listen({ port: 1234, signal: controller.signal }, () => { + t.assert.fail('should not reach callback') + }) + }) + + await t.test('should close server when aborted before - promise', async (t) => { + t.plan(2) + const resolver = {} + resolver.promise = new Promise(function (resolve) { + resolver.resolve = resolve + }) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + resolver.resolve() + } + + const controller = new AbortController() + controller.abort() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + const address = await fastify.listen({ port: 1234, signal: controller.signal }) + t.assert.strictEqual(address, undefined) // ensure the API signature + await resolver.promise + }) + await t.test('listen should not start server', (t, end) => { t.plan(2) function onClose (instance, done) { From 32d224a84b781e1013f616a64b9b911df20c5904 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Thu, 26 Jun 2025 08:45:41 +0800 Subject: [PATCH 1104/1295] feat: prepare to use Promise.withResolvers (#6232) Signed-off-by: KaKa <23028015+climba03003@users.noreply.github.com> --- fastify.js | 27 ++++++--------- lib/promise.js | 23 +++++++++++++ lib/server.js | 58 ++++++++++++++----------------- test/fastify-instance.test.js | 2 +- test/internals/promise.test.js | 63 ++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 49 deletions(-) create mode 100644 lib/promise.js create mode 100644 test/internals/promise.test.js diff --git a/fastify.js b/fastify.js index e34113680c4..eda58074878 100644 --- a/fastify.js +++ b/fastify.js @@ -56,6 +56,7 @@ const { AVVIO_ERRORS_MAP, ...errorCodes } = require('./lib/errors') +const PonyPromise = require('./lib/promise') const { defaultInitOptions } = getSecuredInitialConfig @@ -212,7 +213,7 @@ function fastify (options) { ready: false, booting: false, aborted: false, - readyPromise: null + readyResolver: null }, [kKeepAliveConnections]: keepAliveConnections, [kSupportedHTTPMethods]: { @@ -588,18 +589,15 @@ function fastify (options) { } function ready (cb) { - if (this[kState].readyPromise !== null) { + if (this[kState].readyResolver !== null) { if (cb != null) { - this[kState].readyPromise.then(() => cb(null, fastify), cb) + this[kState].readyResolver.promise.then(() => cb(null, fastify), cb) return } - return this[kState].readyPromise + return this[kState].readyResolver.promise } - let resolveReady - let rejectReady - // run the hooks after returning the promise process.nextTick(runHooks) @@ -607,15 +605,12 @@ function fastify (options) { // It will work as a barrier for all the .ready() calls (ensuring single hook execution) // as well as a flow control mechanism to chain cbs and further // promises - this[kState].readyPromise = new Promise(function (resolve, reject) { - resolveReady = resolve - rejectReady = reject - }) + this[kState].readyResolver = PonyPromise.withResolvers() if (!cb) { - return this[kState].readyPromise + return this[kState].readyResolver.promise } else { - this[kState].readyPromise.then(() => cb(null, fastify), cb) + this[kState].readyResolver.promise.then(() => cb(null, fastify), cb) } function runHooks () { @@ -640,13 +635,13 @@ function fastify (options) { : err if (err) { - return rejectReady(err) + return fastify[kState].readyResolver.reject(err) } - resolveReady(fastify) + fastify[kState].readyResolver.resolve(fastify) fastify[kState].booting = false fastify[kState].ready = true - fastify[kState].readyPromise = null + fastify[kState].readyResolver = null } } diff --git a/lib/promise.js b/lib/promise.js new file mode 100644 index 00000000000..ec49ad6707a --- /dev/null +++ b/lib/promise.js @@ -0,0 +1,23 @@ +'use strict' + +const { kTestInternals } = require('./symbols') + +function withResolvers () { + let res, rej + const promise = new Promise((resolve, reject) => { + res = resolve + rej = reject + }) + return { promise, resolve: res, reject: rej } +} + +module.exports = { + // TODO(20.x): remove when node@20 is not supported + withResolvers: typeof Promise.withResolvers === 'function' + ? Promise.withResolvers.bind(Promise) // Promise.withResolvers must bind to itself + /* c8 ignore next */ + : withResolvers, // Tested using the kTestInternals + [kTestInternals]: { + withResolvers + } +} diff --git a/lib/server.js b/lib/server.js index a0e7ae8fa85..486de0bf072 100644 --- a/lib/server.js +++ b/lib/server.js @@ -14,6 +14,7 @@ const { FST_ERR_REOPENED_SERVER, FST_ERR_LISTEN_OPTIONS_INVALID } = require('./errors') +const PonyPromise = require('./promise') module.exports.createServer = createServer @@ -103,18 +104,18 @@ function createServer (options, httpHandler) { if (cb === undefined) { const listening = listenPromise.call(this, server, listenOptions) return listening.then(address => { - return new Promise((resolve, reject) => { - if (host === 'localhost') { - multipleBindings.call(this, server, httpHandler, options, listenOptions, () => { - this[kState].listening = true - resolve(address) - onListenHookRunner(this) - }) - } else { + const { promise, resolve } = PonyPromise.withResolvers() + if (host === 'localhost') { + multipleBindings.call(this, server, httpHandler, options, listenOptions, () => { + this[kState].listening = true resolve(address) onListenHookRunner(this) - } - }) + }) + } else { + resolve(address) + onListenHookRunner(this) + } + return promise }) } @@ -246,35 +247,28 @@ function listenPromise (server, listenOptions) { // skip listen when aborted during ready if (this[kState].aborted) return - let errEventHandler - let listeningEventHandler + const { promise, resolve, reject } = PonyPromise.withResolvers() + + const errEventHandler = (err) => { + cleanup() + this[kState].listening = false + reject(err) + } + const listeningEventHandler = () => { + cleanup() + this[kState].listening = true + resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)) + } function cleanup () { server.removeListener('error', errEventHandler) server.removeListener('listening', listeningEventHandler) } - const errEvent = new Promise((resolve, reject) => { - errEventHandler = (err) => { - cleanup() - this[kState].listening = false - reject(err) - } - server.once('error', errEventHandler) - }) - const listeningEvent = new Promise((resolve, reject) => { - listeningEventHandler = () => { - cleanup() - this[kState].listening = true - resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)) - } - server.once('listening', listeningEventHandler) - }) + server.once('error', errEventHandler) + server.once('listening', listeningEventHandler) server.listen(listenOptions) - return Promise.race([ - errEvent, // e.g invalid port range error is always emitted before the server listening - listeningEvent - ]) + return promise }) } diff --git a/test/fastify-instance.test.js b/test/fastify-instance.test.js index 13992008379..69996b6d40d 100644 --- a/test/fastify-instance.test.js +++ b/test/fastify-instance.test.js @@ -296,5 +296,5 @@ test('fastify instance should ensure ready promise cleanup on ready', async t => t.plan(1) const fastify = Fastify() await fastify.ready() - t.assert.strictEqual(fastify[kState].readyPromise, null) + t.assert.strictEqual(fastify[kState].readyResolver, null) }) diff --git a/test/internals/promise.test.js b/test/internals/promise.test.js new file mode 100644 index 00000000000..f1b06c5753e --- /dev/null +++ b/test/internals/promise.test.js @@ -0,0 +1,63 @@ +'use strict' + +const { test } = require('node:test') + +const { kTestInternals } = require('../../lib/symbols') +const PonyPromise = require('../../lib/promise') + +test('withResolvers', async (t) => { + t.plan(3) + await t.test('resolve', async (t) => { + t.plan(1) + const { promise, resolve } = PonyPromise.withResolvers() + resolve(true) + t.assert.ok(await promise) + }) + await t.test('reject', async (t) => { + t.plan(1) + const { promise, reject } = PonyPromise.withResolvers() + await t.assert.rejects(async () => { + reject(Error('reject')) + return promise + }, { + name: 'Error', + message: 'reject' + }) + }) + await t.test('thenable', async (t) => { + t.plan(1) + const { promise, resolve } = PonyPromise.withResolvers() + resolve(true) + promise.then((value) => { + t.assert.ok(value) + }) + }) +}) + +test('withResolvers - ponyfill', async (t) => { + await t.test('resolve', async (t) => { + t.plan(1) + const { promise, resolve } = PonyPromise[kTestInternals].withResolvers() + resolve(true) + t.assert.ok(await promise) + }) + await t.test('reject', async (t) => { + t.plan(1) + const { promise, reject } = PonyPromise[kTestInternals].withResolvers() + await t.assert.rejects(async () => { + reject(Error('reject')) + return promise + }, { + name: 'Error', + message: 'reject' + }) + }) + await t.test('thenable', async (t) => { + t.plan(1) + const { promise, resolve } = PonyPromise.withResolvers() + resolve(true) + promise.then((value) => { + t.assert.ok(value) + }) + }) +}) From f6e93cbd1977fef4ec793e7e5bb48ca483c763d5 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 26 Jun 2025 23:06:52 +0200 Subject: [PATCH 1105/1295] docs: updated SECURITY.md (#6233) * chore: copy security file from link Signed-off-by: Manuel Spigolon * feat: add github security advisory Signed-off-by: Manuel Spigolon --------- Signed-off-by: Manuel Spigolon --- SECURITY.md | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index c62a24c2bd1..48b2f20dc81 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,160 @@ # Security Policy -Please see Fastify's [organization-wide security policy -](https://github.com/fastify/.github/blob/main/SECURITY.md). \ No newline at end of file +This document describes the management of vulnerabilities for the Fastify +project and its official plugins. + +## Reporting vulnerabilities + +Individuals who find potential vulnerabilities in Fastify are invited to +complete a vulnerability report via the dedicated pages: + +1. [HackerOne](https://hackerone.com/fastify) +2. [GitHub Security Advisory](https://github.com/fastify/fastify/security/advisories/new) + +### Strict measures when reporting vulnerabilities + +It is of the utmost importance that you read carefully and follow these +guidelines to ensure the ecosystem as a whole isn't disrupted due to improperly +reported vulnerabilities: + +* Avoid creating new "informative" reports. Only create new + reports on a vulnerability if you are absolutely sure this should be + tagged as an actual vulnerability. Third-party vendors and individuals are + tracking any new vulnerabilities reported in HackerOne or GitHub and will flag + them as such for their customers (think about snyk, npm audit, ...). +* Security reports should never be created and triaged by the same person. If + you are creating a report for a vulnerability that you found, or on + behalf of someone else, there should always be a 2nd Security Team member who + triages it. If in doubt, invite more Fastify Collaborators to help triage the + validity of the report. In any case, the report should follow the same process + as outlined below of inviting the maintainers to review and accept the + vulnerability. +* ***Do not*** attempt to show CI/CD vulnerabilities by creating new pull + requests to any of the Fastify organization's repositories. Doing so will + result in a [content report][cr] to GitHub as an unsolicited exploit. + The proper way to provide such reports is by creating a new repository, + configured in the same manner as the repository you would like to submit + a report about, and with a pull request to your own repository showing + the proof of concept. + +[cr]: https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam#reporting-an-issue-or-pull-request + +### Vulnerabilities found outside this process + +⚠ The Fastify project does not support any reporting outside the process mentioned +in this document. + +## Handling vulnerability reports + +When a potential vulnerability is reported, the following actions are taken: + +### Triage + +**Delay:** 4 business days + +Within 4 business days, a member of the security team provides a first answer to +the individual who submitted the potential vulnerability. The possible responses +can be: + +* **Acceptance**: what was reported is considered as a new vulnerability +* **Rejection**: what was reported is not considered as a new vulnerability +* **Need more information**: the security team needs more information in order to + evaluate what was reported. + +Triaging should include updating issue fields: +* Asset - set/create the module affected by the report +* Severity - TBD, currently left empty + +Reference: [HackerOne: Submitting +Reports](https://docs.hackerone.com/hackers/submitting-reports.html) + +### Correction follow-up + +**Delay:** 90 days + +When a vulnerability is confirmed, a member of the security team volunteers to +follow up on this report. + +With the help of the individual who reported the vulnerability, they contact the +maintainers of the vulnerable package to make them aware of the vulnerability. +The maintainers can be invited as participants to the reported issue. + +With the package maintainer, they define a release date for the publication of +the vulnerability. Ideally, this release date should not happen before the +package has been patched. + +The report's vulnerable versions upper limit should be set to: +* `*` if there is no fixed version available by the time of publishing the + report. +* the last vulnerable version. For example: `<=1.2.3` if a fix exists in `1.2.4` + +### Publication + +**Delay:** 90 days + +Within 90 days after the triage date, the vulnerability must be made public. + +**Severity**: Vulnerability severity is assessed using [CVSS +v.3](https://www.first.org/cvss/user-guide). More information can be found on +[HackerOne documentation](https://docs.hackerone.com/hackers/severity.html) + +If the package maintainer is actively developing a patch, an additional delay +can be added with the approval of the security team and the individual who +reported the vulnerability. + +At this point, a CVE should be requested through the selected platform through +the UI, which should include the Report ID and a summary. + +Within HackerOne, this is handled through a "public disclosure request". + +Reference: [HackerOne: +Disclosure](https://docs.hackerone.com/hackers/disclosure.html) + +## The Fastify Security team + +The core team is responsible for the management of the security program and +this policy and process. + +Members of this team are expected to keep all information that they have +privileged access to by being on the team completely private to the team. This +includes agreeing to not notify anyone outside the team of issues that have not +yet been disclosed publicly, including the existence of issues, expectations of +upcoming releases, and patching of any issues other than in the process of their +work as a member of the Fastify Core team. + +### Members + +* [__Matteo Collina__](https://github.com/mcollina), + , +* [__Tomas Della Vedova__](https://github.com/delvedor), + , +* [__Vincent Le Goff__](https://github.com/zekth) +* [__KaKa Ng__](https://github.com/climba03003) +* [__James Sumners__](https://github.com/jsumners), + , + +## OpenSSF CII Best Practices + +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585) + +There are three “tiers”: passing, silver, and gold. + +### Passing +We meet 100% of the “passing” criteria. + +### Silver +We meet 87% of the “silver” criteria. The gaps are as follows: + - we do not have a DCO or a CLA process for contributions. + - we do not currently document + “what the user can and cannot expect in terms of security” for our project. + - we do not currently document ”the architecture (aka high-level design)” + for our project. + +### Gold +We meet 70% of the “gold” criteria. The gaps are as follows: + - we do not yet have the “silver” badge; see all the gaps above. + - We do not include a copyright or license statement in each source file. + Efforts are underway to change this archaic practice into a + suggestion instead of a hard requirement. + - There are a few unanswered questions around cryptography that are + waiting for clarification. From c3cd648461e958f48177d440936f37eca886649d Mon Sep 17 00:00:00 2001 From: Ian Woodard <17186604+IanWoodard@users.noreply.github.com> Date: Sun, 29 Jun 2025 06:33:58 -0700 Subject: [PATCH 1106/1295] Update Recommendations.md (#6238) Fixing link markdown to exclude the leading parenthesis. Signed-off-by: Ian Woodard <17186604+IanWoodard@users.noreply.github.com> --- docs/Guides/Recommendations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index 3e23be6224a..0d08cc36d01 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -285,7 +285,7 @@ server { ## Kubernetes -The `readinessProbe` uses [(by +The `readinessProbe` uses ([by default](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes)) the pod IP as the hostname. Fastify listens on `127.0.0.1` by default. The probe will not be able to reach the application in this case. To make it work, From 998254f3caf317850a5afdeec30a3d575b1618ab Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 2 Jul 2025 10:56:36 +0100 Subject: [PATCH 1107/1295] ci: fix thollander/actions-comment-pull-request commit-hash (#6244) --- .github/workflows/benchmark-parser.yml | 2 +- .github/workflows/benchmark.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml index 80f79bc10f7..e6a079f3e8f 100644 --- a/.github/workflows/benchmark-parser.yml +++ b/.github/workflows/benchmark-parser.yml @@ -75,7 +75,7 @@ jobs: pull-requests: write steps: - name: Comment PR - uses: thollander/actions-comment-pull-request@65f9e5c9a1f2cd378bd74b2e057c9736982a8e74 # v3.0.1 + uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} message: | diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 0d00025be6f..d8dbd7c142b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -75,7 +75,7 @@ jobs: pull-requests: write steps: - name: Comment PR - uses: thollander/actions-comment-pull-request@65f9e5c9a1f2cd378bd74b2e057c9736982a8e74 # v3.0.1 + uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} message: | From a0ba5d628ca0941b7158d5116812ccf4249734e7 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 2 Jul 2025 08:11:36 -0400 Subject: [PATCH 1108/1295] docs(routes): add payload to preParsing signature (#6240) Signed-off-by: Dave --- docs/Reference/Routes.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 64d17a49498..ec8e173bd44 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -59,8 +59,9 @@ fastify.route(options) one. * `onRequest(request, reply, done)`: a [function](./Hooks.md#onrequest) called as soon as a request is received, it could also be an array of functions. -* `preParsing(request, reply, done)`: a [function](./Hooks.md#preparsing) called - before parsing the request, it could also be an array of functions. +* `preParsing(request, reply, payload, done)`: a + [function](./Hooks.md#preparsing) called before parsing the request, it could + also be an array of functions. * `preValidation(request, reply, done)`: a [function](./Hooks.md#prevalidation) called after the shared `preValidation` hooks, useful if you need to perform authentication at route level for example, it could also be an array of From 7c9d56820e27986293417bd63bbf6a32bb9f618f Mon Sep 17 00:00:00 2001 From: Sahachai Date: Thu, 3 Jul 2025 19:40:07 +0700 Subject: [PATCH 1109/1295] docs: add fastify-route-preset to ecosystem (#6220) --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 4a4f7b63009..8fc84a9adc8 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -616,6 +616,9 @@ middlewares into Fastify plugins Fastify Rob-Config integration. - [`fastify-route-group`](https://github.com/TakNePoidet/fastify-route-group) Convenient grouping and inheritance of routes. +- [`fastify-route-preset`](https://github.com/inyourtime/fastify-route-preset) + A Fastify plugin that enables you to create route configurations that can be + applied to multiple routes. - [`fastify-s3-buckets`](https://github.com/kibertoad/fastify-s3-buckets) Ensure the existence of defined S3 buckets on the application startup. - [`fastify-schema-constraint`](https://github.com/Eomm/fastify-schema-constraint) From af13e36a8d9e756812e7248dff13d1431811bf1e Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Fri, 4 Jul 2025 09:19:43 +0200 Subject: [PATCH 1110/1295] chore: remove simple get from async request, get and register... (#6246) * chore: async request * chore: remove simple get from get * chore: register * chore: sync request * chore: sync request reply * chore: error request * chore: 404 * chore: refactor async request and 404 * fix: use fastify server * chore: custom http server --- test/custom-http-server.test.js | 22 +- test/diagnostics-channel/404.test.js | 22 +- .../diagnostics-channel/async-request.test.js | 24 +- .../diagnostics-channel/error-request.test.js | 22 +- .../sync-request-reply.test.js | 25 +- test/diagnostics-channel/sync-request.test.js | 25 +- test/http-methods/get.test.js | 274 +++++++++--------- test/register.test.js | 26 +- 8 files changed, 186 insertions(+), 254 deletions(-) diff --git a/test/custom-http-server.test.js b/test/custom-http-server.test.js index fc638edbc0d..b0cf7c64789 100644 --- a/test/custom-http-server.test.js +++ b/test/custom-http-server.test.js @@ -3,7 +3,6 @@ const { test } = require('node:test') const http = require('node:http') const dns = require('node:dns').promises -const sget = require('simple-get').concat const Fastify = require('..') const { FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE } = require('../lib/errors') @@ -11,7 +10,7 @@ async function setup () { const localAddresses = await dns.lookup('localhost', { all: true }) test('Should support a custom http server', { skip: localAddresses.length < 1 }, async t => { - t.plan(4) + t.plan(5) const fastify = Fastify({ serverFactory: (handler, opts) => { @@ -34,20 +33,13 @@ async function setup () { await fastify.listen({ port: 0 }) - await new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port, - rejectUnauthorized: false - }, (err, response, body) => { - if (err) { - return reject(err) - } - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - resolve() - }) + const response = await fetch('http://localhost:' + fastify.server.address().port, { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) test('Should not allow forceCloseConnection=idle if the server does not support closeIdleConnections', t => { diff --git a/test/diagnostics-channel/404.test.js b/test/diagnostics-channel/404.test.js index 8eff7dc779b..f02b0b644d1 100644 --- a/test/diagnostics-channel/404.test.js +++ b/test/diagnostics-channel/404.test.js @@ -2,13 +2,11 @@ const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const sget = require('simple-get').concat const Fastify = require('../..') -const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel sync events fire in expected order', (t, done) => { +test('diagnostics channel sync events fire in expected order', async t => { t.plan(9) let callOrder = 0 let firstEncounteredMessage @@ -40,18 +38,12 @@ test('diagnostics channel sync events fire in expected order', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 404) - done() - }) + const response = await fetch(fastifyServer, { + method: 'GET' }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) }) diff --git a/test/diagnostics-channel/async-request.test.js b/test/diagnostics-channel/async-request.test.js index 368c3e6614c..813eccdd8d7 100644 --- a/test/diagnostics-channel/async-request.test.js +++ b/test/diagnostics-channel/async-request.test.js @@ -2,13 +2,11 @@ const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const sget = require('simple-get').concat const Fastify = require('../..') -const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel async events fire in expected order', (t, done) => { +test('diagnostics channel async events fire in expected order', async t => { t.plan(18) let callOrder = 0 let firstEncounteredMessage @@ -54,19 +52,13 @@ test('diagnostics channel async events fire in expected order', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - t.after(() => { fastify.close() }) + t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const response = await fetch(fastifyServer) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) diff --git a/test/diagnostics-channel/error-request.test.js b/test/diagnostics-channel/error-request.test.js index ed035a6f57e..17ee85ef9c6 100644 --- a/test/diagnostics-channel/error-request.test.js +++ b/test/diagnostics-channel/error-request.test.js @@ -2,13 +2,11 @@ const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const sget = require('simple-get').concat const Fastify = require('../..') -const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel events report on errors', (t, done) => { +test('diagnostics channel events report on errors', async t => { t.plan(14) let callOrder = 0 let firstEncounteredMessage @@ -44,18 +42,12 @@ test('diagnostics channel events report on errors', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - done() - }) + const response = await fetch(fastifyServer, { + method: 'GET' }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 500) }) diff --git a/test/diagnostics-channel/sync-request-reply.test.js b/test/diagnostics-channel/sync-request-reply.test.js index 61d5774a523..d1e38b6f445 100644 --- a/test/diagnostics-channel/sync-request-reply.test.js +++ b/test/diagnostics-channel/sync-request-reply.test.js @@ -2,13 +2,11 @@ const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const sget = require('simple-get').concat const Fastify = require('../..') -const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel sync events fire in expected order', (t, done) => { +test('diagnostics channel sync events fire in expected order', async t => { t.plan(10) let callOrder = 0 let firstEncounteredMessage @@ -40,19 +38,14 @@ test('diagnostics channel sync events fire in expected order', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) + const response = await fetch(fastifyServer, { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) diff --git a/test/diagnostics-channel/sync-request.test.js b/test/diagnostics-channel/sync-request.test.js index fed1d319488..c80935397fa 100644 --- a/test/diagnostics-channel/sync-request.test.js +++ b/test/diagnostics-channel/sync-request.test.js @@ -2,13 +2,11 @@ const { test } = require('node:test') const diagnostics = require('node:diagnostics_channel') -const sget = require('simple-get').concat const Fastify = require('../..') -const { getServerUrl } = require('../helper') const Request = require('../../lib/request') const Reply = require('../../lib/reply') -test('diagnostics channel sync events fire in expected order', (t, done) => { +test('diagnostics channel sync events fire in expected order', async t => { t.plan(13) let callOrder = 0 let firstEncounteredMessage @@ -43,19 +41,14 @@ test('diagnostics channel sync events fire in expected order', (t, done) => { } }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - t.after(() => { fastify.close() }) - - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/7' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) + const response = await fetch(fastifyServer + '/7', { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) diff --git a/test/http-methods/get.test.js b/test/http-methods/get.test.js index ae02f6aeb91..b9dea10db5d 100644 --- a/test/http-methods/get.test.js +++ b/test/http-methods/get.test.js @@ -1,7 +1,7 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat +const { Client } = require('undici') const fastify = require('../../fastify')() const schema = { @@ -219,206 +219,194 @@ test('get test', async t => { await fastify.listen({ port: 0 }) - await t.test('shorthand - request get', (t, done) => { + await t.test('shorthand - request get', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port, { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - await t.test('shorthand - request get params schema', (t, done) => { + await t.test('shorthand - request get params schema', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/params/world/123' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/params/world/123', { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) }) - await t.test('shorthand - request get params schema error', (t, done) => { + await t.test('shorthand - request get params schema error', async t => { t.plan(3) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/params/world/string' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'params/test must be integer', - statusCode: 400 - }) - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/params/world/string', { + method: 'GET' + }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'params/test must be integer', + statusCode: 400 }) }) - await t.test('shorthand - request get headers schema', (t, done) => { + await t.test('shorthand - request get headers schema', async t => { t.plan(4) - sget({ + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/headers', { method: 'GET', headers: { 'x-test': '1', 'Y-Test': '3' - }, - json: true, - url: 'http://localhost:' + fastify.server.address().port + '/headers' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body['x-test'], 1) - t.assert.strictEqual(body['y-test'], 3) - done() + } }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.json() + t.assert.strictEqual(body['x-test'], 1) + t.assert.strictEqual(body['y-test'], 3) }) - await t.test('shorthand - request get headers schema error', (t, done) => { + await t.test('shorthand - request get headers schema error', async t => { t.plan(3) - sget({ + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/headers', { method: 'GET', headers: { 'x-test': 'abc' - }, - url: 'http://localhost:' + fastify.server.address().port + '/headers' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'headers/x-test must be number', - statusCode: 400 - }) - done() + } + }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'headers/x-test must be number', + statusCode: 400 }) }) - await t.test('shorthand - request get querystring schema', (t, done) => { + await t.test('shorthand - request get querystring schema', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/query?hello=123' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/query?hello=123', { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) }) - await t.test('shorthand - request get querystring schema error', (t, done) => { + await t.test('shorthand - request get querystring schema error', async t => { t.plan(3) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/query?hello=world' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'querystring/hello must be integer', - statusCode: 400 - }) - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/query?hello=world', { + method: 'GET' + }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'querystring/hello must be integer', + statusCode: 400 }) }) - await t.test('shorthand - request get missing schema', (t, done) => { + await t.test('shorthand - request get missing schema', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/missing' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/missing', { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - await t.test('shorthand - custom serializer', (t, done) => { + await t.test('shorthand - custom serializer', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/custom-serializer' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/custom-serializer', { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - await t.test('shorthand - empty response', (t, done) => { + await t.test('shorthand - empty response', async t => { t.plan(4) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/empty' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '0') - t.assert.deepStrictEqual(body.toString(), '') - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/empty', { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '0') + t.assert.deepStrictEqual(body.toString(), '') }) - await t.test('shorthand - send a falsy boolean', (t, done) => { + await t.test('shorthand - send a falsy boolean', async t => { t.plan(3) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/boolean' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), 'false') - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/boolean', { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(body.toString(), 'false') }) - await t.test('shorthand - send null value', (t, done) => { + await t.test('shorthand - send null value', async t => { t.plan(3) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/null' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), 'null') - done() + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/null', { + method: 'GET' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(body.toString(), 'null') }) - await t.test('shorthand - request get headers - test fall back port', (t, done) => { - t.plan(3) - sget({ + await t.test('shorthand - request get headers - test fall back port', async t => { + t.plan(2) + + const instance = new Client('http://localhost:' + fastify.server.address().port) + + const response = await instance.request({ + path: '/port', method: 'GET', headers: { host: 'example.com' - }, - json: true, - url: 'http://localhost:' + fastify.server.address().port + '/port' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.port, null) - done() + } }) + + t.assert.strictEqual(response.statusCode, 200) + const body = JSON.parse(await response.body.text()) + t.assert.strictEqual(body.port, null) }) }) diff --git a/test/register.test.js b/test/register.test.js index 87370694a98..e00d01b0277 100644 --- a/test/register.test.js +++ b/test/register.test.js @@ -1,11 +1,10 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('..') test('register', async (t) => { - t.plan(14) + t.plan(16) const fastify = Fastify() @@ -35,28 +34,19 @@ test('register', async (t) => { done() }) - await fastify.listen({ port: 0 }) + const fastifyServer = await fastify.listen({ port: 0 }) t.after(() => fastify.close()) await makeRequest('first') await makeRequest('second') async function makeRequest (path) { - return new Promise((resolve, reject) => { - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/' + path - }, (err, response, body) => { - if (err) { - t.assert.ifError(err) - return reject(err) - } - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - resolve() - }) - }) + const response = await fetch(fastifyServer + '/' + path) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) } }) From 45bc9143c6e60ed0cf935d5c1217b25a55c7e82c Mon Sep 17 00:00:00 2001 From: Emanuel Covelli Date: Fri, 4 Jul 2025 12:04:11 +0200 Subject: [PATCH 1111/1295] docs: improve custom validator documentation for async hooks (#6228) --- .../Reference/Validation-and-Serialization.md | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 703192fb805..b2ad7eed0d6 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -22,6 +22,13 @@ specification. > it should not be used for initial validation. Accessing databases during > validation may lead to Denial of Service attacks. Use > [Fastify's hooks](./Hooks.md) like `preHandler` for `async` tasks after validation. +> +> When using custom validators with async `preValidation` hooks, +> validators **must return** `{error}` objects instead of throwing errors. +> Throwing errors from custom validators will cause unhandled promise rejections +> that crash the application when combined with async hooks. See the +> [custom validator examples](#using-other-validation-libraries) below for the +> correct pattern. ### Core concepts Validation and serialization are handled by two customizable dependencies: @@ -446,14 +453,25 @@ JavaScript validation libraries like [joi](https://github.com/hapijs/joi/) or ```js const Joi = require('joi') +fastify.setValidatorCompiler(({ schema }) => { + return (data) => { + try { + const { error, value } = schema.validate(data) + if (error) { + return { error } // Return the error, do not throw it + } + return { value } + } catch (e) { + return { error: e } // Catch any unexpected errors too + } + } +}) + fastify.post('/the/url', { schema: { body: Joi.object().keys({ hello: Joi.string().required() }).required() - }, - validatorCompiler: ({ schema, method, url, httpPart }) => { - return data => schema.validate(data) } }, handler) ``` @@ -492,6 +510,40 @@ fastify.post('/the/url', { }, handler) ``` +##### Custom Validator Best Practices + +When implementing custom validators, follow these patterns to ensure compatibility +with all Fastify features: + +** Always return objects, never throw:** +```js +return { value: validatedData } // On success +return { error: validationError } // On failure +``` + +** Use try-catch for safety:** +```js +fastify.setValidatorCompiler(({ schema }) => { + return (data) => { + try { + // Validation logic here + const result = schema.validate(data) + if (result.error) { + return { error: result.error } + } + return { value: result.value } + } catch (e) { + // Catch any unexpected errors + return { error: e } + } + } +}) +``` + +This pattern ensures validators work correctly with both sync and async +`preValidation` hooks, preventing unhandled promise rejections that can crash +an application. + ##### .statusCode property All validation errors have a `.statusCode` property set to `400`, ensuring the From c01f3c15d53b881466a48f7e9227d2208accc5ed Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Fri, 4 Jul 2025 17:28:09 +0200 Subject: [PATCH 1112/1295] chore: route shorthand (#6245) --- test/route-shorthand.test.js | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/test/route-shorthand.test.js b/test/route-shorthand.test.js index e9585d0fc97..46e1b3662be 100644 --- a/test/route-shorthand.test.js +++ b/test/route-shorthand.test.js @@ -1,7 +1,7 @@ 'use strict' const { describe, test } = require('node:test') -const sget = require('simple-get').concat +const { Client } = require('undici') const Fastify = require('..') describe('route-shorthand', () => { @@ -19,19 +19,10 @@ describe('route-shorthand', () => { await fastify.listen({ port: 0 }) t.after(() => fastify.close()) - await new Promise((resolve, reject) => { - sget({ - method, - url: `http://localhost:${fastify.server.address().port}` - }, (err, response, body) => { - if (err) { - t.assert.ifError(err) - return reject(err) - } - t.assert.strictEqual(response.statusCode, 200) - resolve() - }) - }) + const instance = new Client(`http://localhost:${fastify.server.address().port}`) + + const response = await instance.request({ path: '/', method }) + t.assert.strictEqual(response.statusCode, 200) }) } @@ -48,19 +39,10 @@ describe('route-shorthand', () => { for (const method of supportedMethods) { currentMethod = method - await new Promise((resolve, reject) => { - sget({ - method, - url: `http://localhost:${fastify.server.address().port}` - }, (err, response, body) => { - if (err) { - t.assert.ifError(err) - return reject(err) - } - t.assert.strictEqual(response.statusCode, 200) - resolve() - }) - }) + const instance = new Client(`http://localhost:${fastify.server.address().port}`) + + const response = await instance.request({ path: '/', method }) + t.assert.strictEqual(response.statusCode, 200) } }) }) From 9083fe5ebec990d52aef94d76dd0ff389d179741 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:06:32 +0000 Subject: [PATCH 1113/1295] chore: Bump @stylistic/eslint-plugin (#6242) Bumps the dev-dependencies-eslint group with 1 update: [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin). Updates `@stylistic/eslint-plugin` from 4.4.1 to 5.1.0 - [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases) - [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v5.1.0/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@stylistic/eslint-plugin" dependency-version: 5.1.0 dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies-eslint ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 164a9f5bfa2..5ec18970c00 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "@jsumners/line-reporter": "^1.0.1", "@sinclair/typebox": "^0.34.13", "@sinonjs/fake-timers": "^11.2.2", - "@stylistic/eslint-plugin": "^4.1.0", + "@stylistic/eslint-plugin": "^5.1.0", "@stylistic/eslint-plugin-js": "^4.1.0", "@types/node": "^22.0.0", "ajv": "^8.12.0", From ff78d20cee20dce2ed83539f56b7e64e3ebbff3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:57:58 +0000 Subject: [PATCH 1114/1295] chore: Bump @types/node in the dev-dependencies-typescript group (#6243) Bumps the dev-dependencies-typescript group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 22.15.34 to 24.0.8 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 24.0.8 dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies-typescript ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ec18970c00..884ec9630fb 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "@sinonjs/fake-timers": "^11.2.2", "@stylistic/eslint-plugin": "^5.1.0", "@stylistic/eslint-plugin-js": "^4.1.0", - "@types/node": "^22.0.0", + "@types/node": "^24.0.12", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", From f18ebeae6798f0cd4fa72a8d44f28990feae93e1 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sat, 12 Jul 2025 13:10:49 +0200 Subject: [PATCH 1115/1295] chore(tests): remove simple get (#6249) * chore: move custom parser 3 * chore: delete * fix: custom parser import * chore: hooks async * chore: stream 1 * chore: stream 1 refactor * chore: stream 4 * chore: stream 5 * chore: server * chore: route 8 * chore: use json * chore: route hooks * fix: lint * chore: custom parser 3 refactor * chore: refactor delete --- test/custom-parser.3.test.js | 160 ++++++++++---------------- test/delete.test.js | 211 +++++++++++++++++------------------ test/hooks-async.test.js | 58 +++++----- test/route-hooks.test.js | 28 ++--- test/route.1.test.js | 205 ++++++++++++---------------------- test/route.8.test.js | 26 ++--- test/server.test.js | 8 +- test/stream.1.test.js | 83 ++++++-------- test/stream.4.test.js | 46 +++----- test/stream.5.test.js | 30 ++--- 10 files changed, 343 insertions(+), 512 deletions(-) diff --git a/test/custom-parser.3.test.js b/test/custom-parser.3.test.js index b81110a2536..b359637f441 100644 --- a/test/custom-parser.3.test.js +++ b/test/custom-parser.3.test.js @@ -1,15 +1,13 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('..') const jsonParser = require('fast-json-body') -const { getServerUrl } = require('./helper') process.removeAllListeners('warning') -test('should be able to use default parser for extra content type', (t, done) => { - t.plan(4) +test('should be able to use default parser for extra content type', async t => { + t.plan(3) const fastify = Fastify() t.after(() => fastify.close()) @@ -19,23 +17,18 @@ test('should be able to use default parser for extra content type', (t, done) => fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore')) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'text/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body.toString()), { hello: 'world' }) - done() - }) + const response = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'text/json' + } }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.json(), { hello: 'world' }) }) test('contentTypeParser should add a custom parser with RegExp value', async (t) => { @@ -56,46 +49,38 @@ test('contentTypeParser should add a custom parser with RegExp value', async (t) }) }) - fastify.listen({ port: 0 }, async err => { - t.assert.ifError(err) + const fastifyServer = await fastify.listen({ port: 0 }) + + await t.test('in POST', async t => { + t.plan(3) - await t.test('in POST', (t, done) => { - t.plan(3) - t.after(() => fastify.close()) - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'application/vnd.test+json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - done() - }) + const response = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/vnd.test+json' + } }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + }) - await t.test('in OPTIONS', (t, done) => { - t.plan(3) - t.after(() => fastify.close()) - - sget({ - method: 'OPTIONS', - url: getServerUrl(fastify), - body: '{"hello":"world"}', - headers: { - 'Content-Type': 'weird/content-type+json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - done() - }) + await t.test('in OPTIONS', async t => { + t.plan(3) + + const response = await fetch(fastifyServer, { + method: 'OPTIONS', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'weird/content-type+json' + } }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) }) }) @@ -169,8 +154,8 @@ test('contentTypeParser should add multiple custom parsers with RegExp values', }) }) -test('catch all content type parser should not interfere with content type parser', (t, done) => { - t.plan(10) +test('catch all content type parser should not interfere with content type parser', async t => { + t.plan(9) const fastify = Fastify() t.after(() => fastify.close()) @@ -200,57 +185,24 @@ test('catch all content type parser should not interfere with content type parse }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - - let pending = 3 - - function completed () { - if (--pending === 0) { - done() - } - } - - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: '{"myKey":"myValue"}', - headers: { - 'Content-Type': 'application/json' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ myKey: 'myValue' })) - completed() - }) + const fastifyServer = await fastify.listen({ port: 0 }) - sget({ - method: 'POST', - url: getServerUrl(fastify), - body: 'body', - headers: { - 'Content-Type': 'very-weird-content-type' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), 'body') - completed() - }) + const assertions = [ + { body: '{"myKey":"myValue"}', contentType: 'application/json', expected: JSON.stringify({ myKey: 'myValue' }) }, + { body: 'body', contentType: 'very-weird-content-type', expected: 'body' }, + { body: 'my text', contentType: 'text/html', expected: 'my texthtml' } + ] - sget({ + for (const { body, contentType, expected } of assertions) { + const response = await fetch(fastifyServer, { method: 'POST', - url: getServerUrl(fastify), - body: 'my text', + body, headers: { - 'Content-Type': 'text/html' + 'Content-Type': contentType } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), 'my texthtml') - completed() }) - }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.text(), expected) + } }) diff --git a/test/delete.test.js b/test/delete.test.js index e35725c249a..3621703846d 100644 --- a/test/delete.test.js +++ b/test/delete.test.js @@ -1,8 +1,6 @@ 'use strict' -const assert = require('node:assert') const { test } = require('node:test') -const sget = require('simple-get').concat const fastify = require('..')() const schema = { @@ -159,155 +157,148 @@ test('body - delete', t => { } }) -fastify.listen({ port: 0 }, err => { - assert.ifError(err) - test.after(() => { fastify.close() }) +test('delete tests', async t => { + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - test('shorthand - request delete', (t, done) => { + await t.test('shorthand - request delete', async t => { t.plan(4) - sget({ - method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() + + const response = await fetch(fastifyServer, { + method: 'DELETE' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - test('shorthand - request delete params schema', (t, done) => { + await t.test('shorthand - request delete params schema', async t => { t.plan(4) - sget({ - method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + '/params/world/123' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) - done() + + const response = await fetch(fastifyServer + '/params/world/123', { + method: 'DELETE' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) }) - test('shorthand - request delete params schema error', (t, done) => { + await t.test('shorthand - request delete params schema error', async t => { t.plan(3) - sget({ - method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + '/params/world/string' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'params/test must be integer', - statusCode: 400 - }) - done() + + const response = await fetch(fastifyServer + '/params/world/string', { + method: 'DELETE' + }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + t.assert.deepStrictEqual(await response.json(), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'params/test must be integer', + statusCode: 400 }) }) - test('shorthand - request delete headers schema', (t, done) => { + await t.test('shorthand - request delete headers schema', async t => { t.plan(4) - sget({ + + const response = await fetch(fastifyServer + '/headers', { method: 'DELETE', headers: { - 'x-test': 1 - }, - url: 'http://localhost:' + fastify.server.address().port + '/headers' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.strictEqual(JSON.parse(body)['x-test'], 1) - done() + 'x-test': '1' + } }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.strictEqual(JSON.parse(body)['x-test'], 1) }) - test('shorthand - request delete headers schema error', (t, done) => { + await t.test('shorthand - request delete headers schema error', async t => { t.plan(3) - sget({ + + const response = await fetch(fastifyServer + '/headers', { method: 'DELETE', headers: { 'x-test': 'abc' - }, - url: 'http://localhost:' + fastify.server.address().port + '/headers' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'headers/x-test must be number', - statusCode: 400 - }) - done() + } + }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'headers/x-test must be number', + statusCode: 400 }) }) - test('shorthand - request delete querystring schema', (t, done) => { + await t.test('shorthand - request delete querystring schema', async t => { t.plan(4) - sget({ - method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + '/query?hello=123' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) - done() + + const response = await fetch(fastifyServer + '/query?hello=123', { + method: 'DELETE' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) }) - test('shorthand - request delete querystring schema error', (t, done) => { + await t.test('shorthand - request delete querystring schema error', async t => { t.plan(3) - sget({ - method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + '/query?hello=world' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(JSON.parse(body), { - error: 'Bad Request', - code: 'FST_ERR_VALIDATION', - message: 'querystring/hello must be integer', - statusCode: 400 - }) - done() + + const response = await fetch(fastifyServer + '/query?hello=world', { + method: 'DELETE' + }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'querystring/hello must be integer', + statusCode: 400 }) }) - test('shorthand - request delete missing schema', (t, done) => { + await t.test('shorthand - request delete missing schema', async t => { t.plan(4) - sget({ - method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + '/missing' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() + + const response = await fetch(fastifyServer + '/missing', { + method: 'DELETE' }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - test('shorthand - delete with body', (t, done) => { + await t.test('shorthand - delete with body', async t => { t.plan(3) - sget({ + + const response = await fetch(fastifyServer + '/body', { method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port + '/body', - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: 'world' }) - done() + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.json() + t.assert.deepStrictEqual(body, { hello: 'world' }) }) }) diff --git a/test/hooks-async.test.js b/test/hooks-async.test.js index 026a931ce00..698eb99e228 100644 --- a/test/hooks-async.test.js +++ b/test/hooks-async.test.js @@ -2,7 +2,6 @@ const { Readable } = require('node:stream') const { test, describe } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('../fastify') const fs = require('node:fs') const { sleep } = require('./helper') @@ -10,8 +9,8 @@ const { waitForCb } = require('./toolkit') process.removeAllListeners('warning') -test('async hooks', t => { - t.plan(21) +test('async hooks', async t => { + t.plan(20) const fastify = Fastify({ exposeHeadRoutes: false }) fastify.addHook('onRequest', async function (request, reply) { await sleep(1) @@ -59,37 +58,32 @@ test('async hooks', t => { reply.code(200).send({ hello: 'world' }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(response.headers['content-length'], '' + body.length) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - completion.stepIn() - }) - sget({ - method: 'HEAD', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - completion.stepIn() - }) - sget({ - method: 'DELETE', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - completion.stepIn() - }) + const response1 = await fetch(fastifyServer, { + method: 'GET' + }) + t.assert.ok(response1.ok) + t.assert.strictEqual(response1.status, 200) + const body1 = await response1.text() + t.assert.strictEqual(response1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world' }) + completion.stepIn() + + const response2 = await fetch(fastifyServer, { + method: 'HEAD' + }) + t.assert.ok(!response2.ok) + t.assert.strictEqual(response2.status, 500) + completion.stepIn() + + const response3 = await fetch(fastifyServer, { + method: 'DELETE' }) + t.assert.ok(!response3.ok) + t.assert.strictEqual(response3.status, 500) + completion.stepIn() return completion.patience }) diff --git a/test/route-hooks.test.js b/test/route-hooks.test.js index 8b62696724b..107b561c453 100644 --- a/test/route-hooks.test.js +++ b/test/route-hooks.test.js @@ -2,7 +2,6 @@ const { Readable } = require('node:stream') const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('../') process.removeAllListeners('warning') @@ -582,8 +581,8 @@ test('onRequest option should be called before preParsing', (t, testDone) => { }) }) -test('onTimeout on route', (t, testDone) => { - t.plan(4) +test('onTimeout on route', async (t) => { + t.plan(3) const fastify = Fastify({ connectionTimeout: 500 }) fastify.get('/timeout', { @@ -594,19 +593,16 @@ test('onTimeout on route', (t, testDone) => { } }) - fastify.listen({ port: 0 }, (err, address) => { - t.assert.ifError(err) - t.after(() => fastify.close()) - - sget({ - method: 'GET', - url: `${address}/timeout` - }, (err, response, body) => { - t.assert.ok(err instanceof Error) - t.assert.strictEqual(err.message, 'socket hang up') - testDone() - }) - }) + const address = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + try { + await fetch(`${address}/timeout`) + t.assert.fail('Should have thrown an error') + } catch (err) { + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'fetch failed') + } }) test('onError on route', (t, testDone) => { diff --git a/test/route.1.test.js b/test/route.1.test.js index 6c568e3e5c1..36907519861 100644 --- a/test/route.1.test.js +++ b/test/route.1.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('..') const { FST_ERR_INSTANCE_ALREADY_LISTENING, @@ -12,7 +11,7 @@ const { getServerUrl } = require('./helper') test('route', async t => { t.plan(10) - await t.test('route - get', (t, done) => { + await t.test('route - get', async (t) => { t.plan(4) const fastify = Fastify() @@ -38,22 +37,16 @@ test('route', async t => { }) ) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) - t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(getServerUrl(fastify) + '/') + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.json(), { hello: 'world' }) }) - await t.test('missing schema - route', (t, done) => { + await t.test('missing schema - route', async (t) => { t.plan(4) const fastify = Fastify() @@ -67,19 +60,13 @@ test('route', async t => { }) ) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) - t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/missing' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(getServerUrl(fastify) + '/missing') + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.json(), { hello: 'world' }) }) await t.test('invalid handler attribute - route', t => { @@ -89,7 +76,7 @@ test('route', async t => { t.assert.throws(() => fastify.get('/', { handler: 'not a function' }, () => { })) }) - await t.test('Add Multiple methods per route all uppercase', (t, done) => { + await t.test('Add Multiple methods per route all uppercase', async (t) => { t.plan(7) const fastify = Fastify() @@ -102,31 +89,21 @@ test('route', async t => { } })) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) - t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - }) + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'DELETE', - url: getServerUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const getResponse = await fetch(getServerUrl(fastify) + '/multiple') + t.assert.ok(getResponse.ok) + t.assert.strictEqual(getResponse.status, 200) + t.assert.deepStrictEqual(await getResponse.json(), { hello: 'world' }) + + const deleteResponse = await fetch(getServerUrl(fastify) + '/multiple', { method: 'DELETE' }) + t.assert.ok(deleteResponse.ok) + t.assert.strictEqual(deleteResponse.status, 200) + t.assert.deepStrictEqual(await deleteResponse.json(), { hello: 'world' }) }) - await t.test('Add Multiple methods per route all lowercase', (t, done) => { + await t.test('Add Multiple methods per route all lowercase', async (t) => { t.plan(7) const fastify = Fastify() @@ -139,31 +116,21 @@ test('route', async t => { } })) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) - t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - }) + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'DELETE', - url: getServerUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const getResponse = await fetch(getServerUrl(fastify) + '/multiple') + t.assert.ok(getResponse.ok) + t.assert.strictEqual(getResponse.status, 200) + t.assert.deepStrictEqual(await getResponse.json(), { hello: 'world' }) + + const deleteResponse = await fetch(getServerUrl(fastify) + '/multiple', { method: 'DELETE' }) + t.assert.ok(deleteResponse.ok) + t.assert.strictEqual(deleteResponse.status, 200) + t.assert.deepStrictEqual(await deleteResponse.json(), { hello: 'world' }) }) - await t.test('Add Multiple methods per route mixed uppercase and lowercase', (t, done) => { + await t.test('Add Multiple methods per route mixed uppercase and lowercase', async (t) => { t.plan(7) const fastify = Fastify() @@ -176,28 +143,18 @@ test('route', async t => { } })) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) - t.after(() => { fastify.close() }) - sget({ - method: 'GET', - url: getServerUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - }) + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'DELETE', - url: getServerUrl(fastify) + '/multiple' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const getResponse = await fetch(getServerUrl(fastify) + '/multiple') + t.assert.ok(getResponse.ok) + t.assert.strictEqual(getResponse.status, 200) + t.assert.deepStrictEqual(await getResponse.json(), { hello: 'world' }) + + const deleteResponse = await fetch(getServerUrl(fastify) + '/multiple', { method: 'DELETE' }) + t.assert.ok(deleteResponse.ok) + t.assert.strictEqual(deleteResponse.status, 200) + t.assert.deepStrictEqual(await deleteResponse.json(), { hello: 'world' }) }) t.test('Add invalid Multiple methods per route', t => { @@ -228,7 +185,7 @@ test('route', async t => { }), new FST_ERR_ROUTE_METHOD_INVALID()) }) - await t.test('Add additional multiple methods to existing route', (t, done) => { + await t.test('Add additional multiple methods to existing route', async (t) => { t.plan(7) const fastify = Fastify() @@ -245,49 +202,35 @@ test('route', async t => { }) }) - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) - t.after(() => { fastify.close() }) - sget({ - method: 'PUT', - url: getServerUrl(fastify) + '/add-multiple' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - }) + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget({ - method: 'DELETE', - url: getServerUrl(fastify) + '/add-multiple' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) - done() - }) - }) + const putResponse = await fetch(getServerUrl(fastify) + '/add-multiple', { method: 'PUT' }) + t.assert.ok(putResponse.ok) + t.assert.strictEqual(putResponse.status, 200) + t.assert.deepStrictEqual(await putResponse.json(), { hello: 'world' }) + + const deleteResponse = await fetch(getServerUrl(fastify) + '/add-multiple', { method: 'DELETE' }) + t.assert.ok(deleteResponse.ok) + t.assert.strictEqual(deleteResponse.status, 200) + t.assert.deepStrictEqual(await deleteResponse.json(), { hello: 'world' }) }) - await t.test('cannot add another route after binding', (t, done) => { + await t.test('cannot add another route after binding', async (t) => { t.plan(1) const fastify = Fastify() - fastify.listen({ port: 0 }, function (err) { - if (err) t.assert.ifError(err) - t.after(() => { fastify.close() }) - - t.assert.throws(() => fastify.route({ - method: 'GET', - url: '/another-get-route', - handler: function (req, reply) { - reply.send({ hello: 'world' }) - } - }), new FST_ERR_INSTANCE_ALREADY_LISTENING('Cannot add route!')) + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - done() - }) + t.assert.throws(() => fastify.route({ + method: 'GET', + url: '/another-get-route', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_INSTANCE_ALREADY_LISTENING('Cannot add route!')) }) }) diff --git a/test/route.8.test.js b/test/route.8.test.js index 6289eb9d692..2f479eaf4dc 100644 --- a/test/route.8.test.js +++ b/test/route.8.test.js @@ -1,12 +1,10 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const Fastify = require('..') const { FST_ERR_INVALID_URL } = require('../lib/errors') -const { getServerUrl } = require('./helper') test('Request and Reply share the route options', async t => { t.plan(3) @@ -64,8 +62,8 @@ test('Will not try to re-createprefixed HEAD route if it already exists and expo t.assert.ok(true) }) -test('route with non-english characters', (t, done) => { - t.plan(4) +test('route with non-english characters', async (t) => { + t.plan(3) const fastify = Fastify() @@ -73,20 +71,14 @@ test('route with non-english characters', (t, done) => { reply.send('here /föö') }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget({ - method: 'GET', - url: getServerUrl(fastify) + encodeURI('/föö') - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), 'here /föö') - done() - }) - }) + const response = await fetch(fastifyServer + encodeURI('/föö')) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(body, 'here /föö') }) test('invalid url attribute - non string URL', t => { diff --git a/test/server.test.js b/test/server.test.js index f54dca911f0..6e11adc87ec 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -3,7 +3,6 @@ const dns = require('node:dns') const { test } = require('node:test') const Fastify = require('..') -const sget = require('simple-get').concat const undici = require('undici') const proxyquire = require('proxyquire') @@ -67,7 +66,7 @@ test('listen should reject string port', async (t) => { } }) -test('Test for hostname and port', (t, end) => { +test('Test for hostname and port', async (t) => { const app = Fastify() t.after(() => app.close()) app.get('/host', (req, res) => { @@ -78,9 +77,8 @@ test('Test for hostname and port', (t, end) => { res.send('ok') }) - app.listen({ port: 8000 }, () => { - sget('http://localhost:8000/host', () => { end() }) - }) + await app.listen({ port: 8000 }) + await fetch('http://localhost:8000/host') }) test('abort signal', async t => { diff --git a/test/stream.1.test.js b/test/stream.1.test.js index 1efb8f61185..8f3902e6a66 100644 --- a/test/stream.1.test.js +++ b/test/stream.1.test.js @@ -1,12 +1,11 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const fs = require('node:fs') const Fastify = require('../fastify') -test('should respond with a stream', (t, testDone) => { - t.plan(6) +test('should respond with a stream', async t => { + t.plan(4) const fastify = Fastify() fastify.get('/', function (req, reply) { @@ -14,26 +13,21 @@ test('should respond with a stream', (t, testDone) => { reply.code(200).send(stream) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - sget(`http://localhost:${fastify.server.address().port}`, function (err, response, data) { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], undefined) - t.assert.strictEqual(response.statusCode, 200) - - fs.readFile(__filename, (err, expected) => { - t.assert.ifError(err) - t.assert.strictEqual(expected.toString(), data.toString()) - testDone() - }) - }) - }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(fastifyServer) + t.assert.ok(response.ok) + t.assert.strictEqual(response.headers.get('content-type'), null) + t.assert.strictEqual(response.status, 200) + + const data = await response.text() + const expected = await fs.promises.readFile(__filename, 'utf8') + t.assert.strictEqual(expected.toString(), data.toString()) }) -test('should respond with a stream (error)', (t, testDone) => { - t.plan(3) +test('should respond with a stream (error)', async t => { + t.plan(2) const fastify = Fastify() fastify.get('/error', function (req, reply) { @@ -41,20 +35,16 @@ test('should respond with a stream (error)', (t, testDone) => { reply.code(200).send(stream) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - testDone() - }) - }) + const response = await fetch(`${fastifyServer}/error`) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 500) }) -test('should trigger the onSend hook', (t, testDone) => { - t.plan(4) +test('should trigger the onSend hook', async (t) => { + t.plan(3) const fastify = Fastify() fastify.get('/', (req, reply) => { @@ -67,19 +57,16 @@ test('should trigger the onSend hook', (t, testDone) => { done() }) - fastify.inject({ + const res = await fastify.inject({ url: '/' - }, (err, res) => { - t.assert.ifError(err) - t.assert.strictEqual(res.headers['content-type'], 'application/javascript') - t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8')) - fastify.close() - testDone() }) + t.assert.strictEqual(res.headers['content-type'], 'application/javascript') + t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8')) + return fastify.close() }) -test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', (t, testDone) => { - t.plan(5) +test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', async t => { + t.plan(4) const fastify = Fastify() fastify.get('/', (req, reply) => { @@ -98,14 +85,10 @@ test('should trigger the onSend hook only twice if pumping the stream fails, fir done() }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) - sget(`http://localhost:${fastify.server.address().port}`, function (err, response) { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 500) - testDone() - }) - }) + const response = await fetch(fastifyServer) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 500) }) diff --git a/test/stream.4.test.js b/test/stream.4.test.js index 04231993e1a..9ae386db949 100644 --- a/test/stream.4.test.js +++ b/test/stream.4.test.js @@ -1,7 +1,6 @@ 'use strict' const { test } = require('node:test') -const sget = require('simple-get').concat const errors = require('http-errors') const JSONStream = require('JSONStream') const Readable = require('node:stream').Readable @@ -126,8 +125,8 @@ test('Destroying streams prematurely, log is disabled', (t, testDone) => { }) }) -test('should respond with a stream1', (t, testDone) => { - t.plan(5) +test('should respond with a stream1', async (t) => { + t.plan(4) const fastify = Fastify() fastify.get('/', function (req, reply) { @@ -137,22 +136,19 @@ test('should respond with a stream1', (t, testDone) => { stream.end({ a: 42 }) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget(`http://localhost:${fastify.server.address().port}`, function (err, response, body) { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'application/json') - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(JSON.parse(body), [{ hello: 'world' }, { a: 42 }]) - testDone() - }) - }) + const response = await fetch(fastifyServer) + t.assert.ok(response.ok) + t.assert.strictEqual(response.headers.get('content-type'), 'application/json') + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), [{ hello: 'world' }, { a: 42 }]) }) -test('return a 404 if the stream emits a 404 error', (t, testDone) => { - t.plan(5) +test('return a 404 if the stream emits a 404 error', async (t) => { + t.plan(4) const fastify = Fastify() @@ -170,17 +166,11 @@ test('return a 404 if the stream emits a 404 error', (t, testDone) => { reply.send(reallyLongStream) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => { fastify.close() }) - - const port = fastify.server.address().port + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget(`http://localhost:${port}`, function (err, response) { - t.assert.ifError(err) - t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') - t.assert.strictEqual(response.statusCode, 404) - testDone() - }) - }) + const response = await fetch(fastifyServer) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.strictEqual(response.status, 404) }) diff --git a/test/stream.5.test.js b/test/stream.5.test.js index 05a5d79b7c9..77165f60868 100644 --- a/test/stream.5.test.js +++ b/test/stream.5.test.js @@ -4,11 +4,10 @@ const { test } = require('node:test') const proxyquire = require('proxyquire') const fs = require('node:fs') const Readable = require('node:stream').Readable -const sget = require('simple-get').concat const Fastify = require('..') -test('should destroy stream when response is ended', (t, done) => { - t.plan(4) +test('should destroy stream when response is ended', async (t) => { + t.plan(3) const stream = require('node:stream') const fastify = Fastify() @@ -24,20 +23,16 @@ test('should destroy stream when response is ended', (t, done) => { reply.raw.end(Buffer.from('hello\n')) }) - fastify.listen({ port: 0 }, err => { - t.assert.ifError(err) - t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) - sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - done() - }) - }) + const response = await fetch(`${fastifyServer}/error`) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) }) -test('should mark reply as sent before pumping the payload stream into response for async route handler', (t, done) => { - t.plan(3) +test('should mark reply as sent before pumping the payload stream into response for async route handler', async (t) => { + t.plan(2) t.after(() => fastify.close()) const handleRequest = proxyquire('../lib/handleRequest', { @@ -63,14 +58,11 @@ test('should mark reply as sent before pumping the payload stream into response return reply.code(200).send(stream) }) - fastify.inject({ + const res = await fastify.inject({ url: '/', method: 'GET' - }, (err, res) => { - t.assert.ifError(err) - t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8')) - done() }) + t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8')) }) test('reply.send handles aborted requests', (t, done) => { From d5c2a3bbbd4a340b06cc9e6b766624f6e3d513c4 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 13 Jul 2025 12:57:19 +0200 Subject: [PATCH 1116/1295] chore: finally remove simple get (#6251) * chore: remove simple-get from input validation * chore: migrate input validation * chore: FINALLY remote simple-get * use fetch --------- Co-authored-by: Aras Abbasi --- docs/Guides/Migration-Guide-V5.md | 16 +- package.json | 1 - test/helper.js | 256 +++++++++++++------------- test/input-validation.js | 289 ++++++++++++++---------------- 4 files changed, 267 insertions(+), 295 deletions(-) diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index 1af012dbfd5..6998d1ae9dc 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -557,7 +557,6 @@ and provides a way to trace the lifecycle of a request. 'use strict' const diagnostics = require('node:diagnostics_channel') -const sget = require('simple-get').concat const Fastify = require('fastify') diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { @@ -583,15 +582,12 @@ fastify.route({ } }) -fastify.listen({ port: 0 }, function () { - sget({ - method: 'GET', - url: fastify.listeningOrigin + '/7' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.same(JSON.parse(body), { hello: 'world' }) - }) +fastify.listen({ port: 0 }, async function () { + const result = await fetch(fastify.listeningOrigin + '/7') + + t.assert.ok(result.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) ``` diff --git a/package.json b/package.json index 884ec9630fb..ae6c72671df 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,6 @@ "neostandard": "^0.12.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", - "simple-get": "^4.0.1", "split2": "^4.2.0", "tsd": "^0.32.0", "typescript": "~5.8.2", diff --git a/test/helper.js b/test/helper.js index 65abcc5cc3b..f15382b3463 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,6 +1,5 @@ 'use strict' -const sget = require('simple-get').concat const dns = require('node:dns').promises const stream = require('node:stream') const { promisify } = require('node:util') @@ -105,89 +104,85 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { t.after(() => { fastify.close() }) - test(`${upMethod} - correctly replies`, (t, testDone) => { + test(`${upMethod} - correctly replies`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: 'world' }) - testDone() + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) - test(`${upMethod} - correctly replies with very large body`, (t, testDone) => { + test(`${upMethod} - correctly replies with very large body`, async (t) => { t.plan(3) const largeString = 'world'.repeat(13200) - sget({ + const result = await fetch('http://localhost:' + fastify.server.address().port, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, - body: { hello: largeString }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: largeString }) - testDone() + body: JSON.stringify({ hello: largeString }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: largeString }) }) - test(`${upMethod} - correctly replies if the content type has the charset`, (t, testDone) => { + test(`${upMethod} - correctly replies if the content type has the charset`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, body: JSON.stringify({ hello: 'world' }), headers: { 'content-type': 'application/json; charset=utf-8' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) - testDone() }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ hello: 'world' })) }) - test(`${upMethod} without schema - correctly replies`, (t, testDone) => { + test(`${upMethod} without schema - correctly replies`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/missing', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/missing', - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: 'world' }) - testDone() + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) }) - test(`${upMethod} with body and querystring - correctly replies`, (t, testDone) => { + test(`${upMethod} with body and querystring - correctly replies`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/with-query?foo=hello', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/with-query?foo=hello', - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: 'worldhello' }) - testDone() + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'worldhello' }) }) test(`${upMethod} with no body - correctly replies`, t => { @@ -195,14 +190,13 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { const { stepIn, patience } = waitForCb({ steps: 2 }) - sget({ + fetch('http://localhost:' + fastify.server.address().port + '/missing', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/missing', headers: { 'Content-Length': '0' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.strictEqual(body.toString(), '') + }).then(async (response) => { + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.strictEqual(await response.text(), '') stepIn() }) @@ -220,88 +214,87 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { return patience }) - test(`${upMethod} returns 415 - incorrect media type if body is not json`, (t, testDone) => { + test(`${upMethod} returns 415 - incorrect media type if body is not json`, async (t) => { t.plan(2) - sget({ - method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/missing', - body: 'hello world' - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - testDone() + const result = await fetch('http://localhost:' + fastify.server.address().port + '/missing', { + method: upMethod, + body: 'hello world', + headers: { + 'Content-Type': undefined + } }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) }) if (loMethod === 'options') { - test('OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text', (t, testDone) => { + test('OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text', async (t) => { t.plan(2) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/missing', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/missing', body: 'hello world', headers: { 'Content-Type': 'text/xml' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 415) - testDone() }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) }) } test(`${upMethod} returns 400 - Bad Request`, t => { - t.plan(4) + const isOptions = upMethod === 'OPTIONS' + t.plan(isOptions ? 2 : 4) - const { stepIn, patience } = waitForCb({ steps: 2 }) + const { stepIn, patience } = waitForCb({ steps: isOptions ? 1 : 2 }) - sget({ + fetch('http://localhost:' + fastify.server.address().port, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, body: 'hello world', headers: { 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) + }).then((response) => { + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) stepIn() }) - sget({ - method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': '0' - } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - stepIn() - }) + if (!isOptions) { + fetch(`http://localhost:${fastify.server.address().port}`, { + method: upMethod, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': '0' + } + }).then((response) => { + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + stepIn() + }) + } return patience }) test(`${upMethod} returns 413 - Payload Too Large`, t => { const isOptions = upMethod === 'OPTIONS' - t.plan(isOptions ? 4 : 6) + t.plan(isOptions ? 3 : 5) const { stepIn, patience } = waitForCb({ steps: isOptions ? 2 : 3 }) - sget({ + fetch(`http://localhost:${fastify.server.address().port}`, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, + body: JSON.stringify({ w: 'w'.repeat(1024 * 1024 + 1) }), headers: { - 'Content-Type': 'application/json', - 'Content-Length': 1024 * 1024 + 1 + 'Content-Type': 'application/json' } - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 413) + }).then((response) => { + t.assert.strictEqual(response.status, 413) stepIn() }) @@ -314,27 +307,25 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { chunk = null } }) - sget({ + fetch('http://localhost:' + fastify.server.address().port, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, headers: { 'Content-Type': 'application/json' }, - body: largeStream - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 413) + body: largeStream, + duplex: 'half' + }).then((response) => { + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 413) stepIn() }) } - sget({ + fetch(`http://localhost:${fastify.server.address().port}/with-limit`, { method: upMethod, - url: `http://localhost:${fastify.server.address().port}/with-limit`, headers: { 'Content-Type': 'application/json' }, - body: {}, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 413) + body: JSON.stringify({}) + }).then((response) => { + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 413) stepIn() }) @@ -364,15 +355,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }) }) - sget({ + fetch(`http://localhost:${fastify.server.address().port}`, { method: upMethod, - url: `http://localhost:${fastify.server.address().port}`, headers: { 'Content-Type': 'application/json' } - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body.toString()), { + }).then(async (res) => { + t.assert.ok(!res.ok) + t.assert.deepStrictEqual(await res.json(), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', @@ -399,16 +389,15 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { stepIn() }) - sget({ + fetch(`http://localhost:${fastify.server.address().port}`, { method: upMethod, - url: `http://localhost:${fastify.server.address().port}`, headers: { 'Content-Type': 'application/json' }, - payload: null - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body.toString()), { + body: null + }).then(async (res) => { + t.assert.ok(!res.ok) + t.assert.deepStrictEqual(await res.json(), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', @@ -435,16 +424,15 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { stepIn() }) - sget({ + fetch(`http://localhost:${fastify.server.address().port}`, { method: upMethod, - url: `http://localhost:${fastify.server.address().port}`, headers: { 'Content-Type': 'application/json' }, - payload: undefined - }, (err, res, body) => { - t.assert.ifError(err) - t.assert.deepStrictEqual(JSON.parse(body.toString()), { + body: undefined + }).then(async (res) => { + t.assert.ok(!res.ok) + t.assert.deepStrictEqual(await res.json(), { error: 'Bad Request', code: 'FST_ERR_CTP_EMPTY_JSON_BODY', message: 'Body cannot be empty when content-type is set to \'application/json\'', diff --git a/test/input-validation.js b/test/input-validation.js index b8bf94ee4f4..52ae26784bb 100644 --- a/test/input-validation.js +++ b/test/input-validation.js @@ -1,6 +1,5 @@ 'use strict' -const sget = require('simple-get').concat const Ajv = require('ajv') const Joi = require('joi') const yup = require('yup') @@ -139,207 +138,197 @@ module.exports.payloadMethod = function (method, t) { t.after(() => { fastify.close() }) - test(`${upMethod} - correctly replies`, (t, testDone) => { + test(`${upMethod} - correctly replies`, async (t) => { if (upMethod === 'HEAD') { t.plan(2) - sget({ - method: upMethod, - url: 'http://localhost:' + fastify.server.address().port - }, (err, response) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - testDone() + const result = await fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) } else { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, - body: { - hello: 42 - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: 42 }) - testDone() + body: JSON.stringify({ hello: 42 }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 42 }) } }) - test(`${upMethod} - 400 on bad parameters`, (t, testDone) => { + test(`${upMethod} - 400 on bad parameters`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, - body: { - hello: 'world' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(body, { - error: 'Bad Request', - message: 'body/hello must be integer', - statusCode: 400, - code: 'FST_ERR_VALIDATION' - }) - testDone() + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: 'body/hello must be integer', + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) - test(`${upMethod} - input-validation coerce`, (t, testDone) => { + test(`${upMethod} - input-validation coerce`, async (t) => { t.plan(3) - sget({ + + const restult = await fetch('http://localhost:' + fastify.server.address().port, { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port, - body: { - hello: '42' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: 42 }) - testDone() + body: JSON.stringify({ hello: '42' }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(restult.ok) + t.assert.strictEqual(restult.status, 200) + t.assert.deepStrictEqual(await restult.json(), { hello: 42 }) }) - test(`${upMethod} - input-validation custom schema compiler`, (t, testDone) => { + test(`${upMethod} - input-validation custom schema compiler`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/custom', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/custom', - body: { - hello: '42', - world: 55 - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: 42 }) - testDone() + body: JSON.stringify({ hello: '42', world: 55 }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 42 }) }) - test(`${upMethod} - input-validation joi schema compiler ok`, (t, testDone) => { + test(`${upMethod} - input-validation joi schema compiler ok`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/joi', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/joi', - body: { - hello: '42' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: '42' }) - testDone() + body: JSON.stringify({ hello: '42' }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: '42' }) }) - test(`${upMethod} - input-validation joi schema compiler ko`, (t, testDone) => { + test(`${upMethod} - input-validation joi schema compiler ko`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/joi', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/joi', - body: { - hello: 44 - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(body, { - error: 'Bad Request', - message: '"hello" must be a string', - statusCode: 400, - code: 'FST_ERR_VALIDATION' - }) - testDone() + body: JSON.stringify({ hello: 44 }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: '"hello" must be a string', + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) - test(`${upMethod} - input-validation yup schema compiler ok`, (t, testDone) => { + test(`${upMethod} - input-validation yup schema compiler ok`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/yup', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/yup', - body: { - hello: '42' - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 200) - t.assert.deepStrictEqual(body, { hello: '42' }) - testDone() + body: JSON.stringify({ hello: '42' }), + headers: { + 'Content-Type': 'application/json' + } }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: '42' }) }) - test(`${upMethod} - input-validation yup schema compiler ko`, (t, testDone) => { + test(`${upMethod} - input-validation yup schema compiler ko`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/yup', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/yup', - body: { - hello: 44 - }, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(body, { - error: 'Bad Request', - message: 'body hello must be a `string` type, but the final value was: `44`.', - statusCode: 400, - code: 'FST_ERR_VALIDATION' - }) - testDone() + body: JSON.stringify({ hello: 44 }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: 'body hello must be a `string` type, but the final value was: `44`.', + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) - test(`${upMethod} - input-validation instance custom schema compiler encapsulated`, (t, testDone) => { + test(`${upMethod} - input-validation instance custom schema compiler encapsulated`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/plugin', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/plugin', - body: {}, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(body, { - error: 'Bad Request', - message: 'From custom schema compiler!', - statusCode: 400, - code: 'FST_ERR_VALIDATION' - }) - testDone() + body: JSON.stringify({}), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: 'From custom schema compiler!', + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) - test(`${upMethod} - input-validation custom schema compiler encapsulated`, (t, testDone) => { + test(`${upMethod} - input-validation custom schema compiler encapsulated`, async (t) => { t.plan(3) - sget({ + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/plugin/custom', { method: upMethod, - url: 'http://localhost:' + fastify.server.address().port + '/plugin/custom', - body: {}, - json: true - }, (err, response, body) => { - t.assert.ifError(err) - t.assert.strictEqual(response.statusCode, 400) - t.assert.deepStrictEqual(body, { - error: 'Bad Request', - message: 'Always fail!', - statusCode: 400, - code: 'FST_ERR_VALIDATION' - }) - testDone() + body: JSON.stringify({}), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: 'Always fail!', + statusCode: 400, + code: 'FST_ERR_VALIDATION' }) }) }) From 458e0f773412f31043266172b13e355c2555cad1 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sun, 13 Jul 2025 15:43:26 +0200 Subject: [PATCH 1117/1295] chore: remove undici from schema-validation.test.js (#6252) --- test/schema-validation.test.js | 61 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index ef8cd7fcd2f..9dd377158db 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -2,7 +2,6 @@ const { test } = require('node:test') const Fastify = require('..') -const { request } = require('undici') const AJV = require('ajv') const Schema = require('fluent-json-schema') @@ -1405,7 +1404,7 @@ test('Schema validation will not be bypass by different content type', async t = t.after(() => fastify.close()) const address = fastify.listeningOrigin - const correct1 = await request(address, { + const correct1 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1413,10 +1412,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ foo: 'string' }) }) - t.assert.strictEqual(correct1.statusCode, 200) - await correct1.body.dump() + t.assert.strictEqual(correct1.status, 200) + await correct1.bytes() - const correct2 = await request(address, { + const correct2 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1424,10 +1423,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ foo: 'string' }) }) - t.assert.strictEqual(correct2.statusCode, 200) - await correct2.body.dump() + t.assert.strictEqual(correct2.status, 200) + await correct2.bytes() - const invalid1 = await request(address, { + const invalid1 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1435,10 +1434,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid1.statusCode, 400) - t.assert.strictEqual((await invalid1.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid1.status, 400) + t.assert.strictEqual((await invalid1.json()).code, 'FST_ERR_VALIDATION') - const invalid2 = await request(address, { + const invalid2 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1446,10 +1445,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid2.statusCode, 400) - t.assert.strictEqual((await invalid2.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid2.status, 400) + t.assert.strictEqual((await invalid2.json()).code, 'FST_ERR_VALIDATION') - const invalid3 = await request(address, { + const invalid3 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1457,10 +1456,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid3.statusCode, 400) - t.assert.strictEqual((await invalid3.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid3.status, 400) + t.assert.strictEqual((await invalid3.json()).code, 'FST_ERR_VALIDATION') - const invalid4 = await request(address, { + const invalid4 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1468,10 +1467,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid4.statusCode, 400) - t.assert.strictEqual((await invalid4.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid4.status, 400) + t.assert.strictEqual((await invalid4.json()).code, 'FST_ERR_VALIDATION') - const invalid5 = await request(address, { + const invalid5 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1479,10 +1478,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid5.statusCode, 400) - t.assert.strictEqual((await invalid5.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid5.status, 400) + t.assert.strictEqual((await invalid5.json()).code, 'FST_ERR_VALIDATION') - const invalid6 = await request(address, { + const invalid6 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1490,10 +1489,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid6.statusCode, 415) - t.assert.strictEqual((await invalid6.body.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + t.assert.strictEqual(invalid6.status, 415) + t.assert.strictEqual((await invalid6.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') - const invalid7 = await request(address, { + const invalid7 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1501,10 +1500,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid7.statusCode, 400) - t.assert.strictEqual((await invalid7.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid7.status, 400) + t.assert.strictEqual((await invalid7.json()).code, 'FST_ERR_VALIDATION') - const invalid8 = await request(address, { + const invalid8 = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1512,6 +1511,6 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid8.statusCode, 400) - t.assert.strictEqual((await invalid8.body.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(invalid8.status, 400) + t.assert.strictEqual((await invalid8.json()).code, 'FST_ERR_VALIDATION') }) From 76281098f57dfe9a2af4dd86576995cb772f95c2 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sun, 13 Jul 2025 18:23:09 +0200 Subject: [PATCH 1118/1295] test: fix flakyness of close-pipelining-test, upgrade undici to v7 (#6256) * test: fix flakyness of close-pipelining-test, upgrade undici to v7 * maybe ore robust --- package.json | 2 +- test/close-pipelining.test.js | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index ae6c72671df..b3b86dff859 100644 --- a/package.json +++ b/package.json @@ -197,7 +197,7 @@ "split2": "^4.2.0", "tsd": "^0.32.0", "typescript": "~5.8.2", - "undici": "^6.13.0", + "undici": "^7.11.0", "vary": "^1.1.2", "yup": "^1.4.0" }, diff --git a/test/close-pipelining.test.js b/test/close-pipelining.test.js index f16c8a08912..81e6325ddcf 100644 --- a/test/close-pipelining.test.js +++ b/test/close-pipelining.test.js @@ -11,8 +11,10 @@ test('Should return 503 while closing - pipelining', async t => { }) fastify.get('/', async (req, reply) => { + // Simulate a delay to allow pipelining to kick in + await new Promise(resolve => setTimeout(resolve, 5)) + reply.send({ hello: 'world' }) fastify.close() - return { hello: 'world' } }) await fastify.listen({ port: 0 }) @@ -22,9 +24,9 @@ test('Should return 503 while closing - pipelining', async t => { }) const [firstRequest, secondRequest, thirdRequest] = await Promise.allSettled([ - instance.request({ path: '/', method: 'GET' }), - instance.request({ path: '/', method: 'GET' }), - instance.request({ path: '/', method: 'GET' }) + instance.request({ path: '/', method: 'GET', blocking: false }), + instance.request({ path: '/', method: 'GET', blocking: false }), + instance.request({ path: '/', method: 'GET', blocking: false }) ]) t.assert.strictEqual(firstRequest.status, 'fulfilled') t.assert.strictEqual(secondRequest.status, 'fulfilled') @@ -32,11 +34,8 @@ test('Should return 503 while closing - pipelining', async t => { t.assert.strictEqual(firstRequest.value.statusCode, 200) t.assert.strictEqual(secondRequest.value.statusCode, 200) - if (thirdRequest.status === 'fulfilled') { - t.assert.strictEqual(thirdRequest.value.statusCode, 503) - } else { - t.assert.strictEqual(thirdRequest.reason.code, 'ECONNREFUSED') - } + t.assert.strictEqual(thirdRequest.status, 'fulfilled') + t.assert.strictEqual(thirdRequest.value.statusCode, 503) await instance.close() }) @@ -50,6 +49,8 @@ test('Should close the socket abruptly - pipelining - return503OnClosing: false' }) fastify.get('/', async (req, reply) => { + // Simulate a delay to allow pipelining to kick in + await new Promise(resolve => setTimeout(resolve, 5)) reply.send({ hello: 'world' }) fastify.close() }) @@ -57,21 +58,21 @@ test('Should close the socket abruptly - pipelining - return503OnClosing: false' await fastify.listen({ port: 0 }) const instance = new Client('http://localhost:' + fastify.server.address().port, { - pipelining: 2 + pipelining: 1 }) const responses = await Promise.allSettled([ - instance.request({ path: '/', method: 'GET' }), - instance.request({ path: '/', method: 'GET' }), - instance.request({ path: '/', method: 'GET' }), - instance.request({ path: '/', method: 'GET' }) + instance.request({ path: '/', method: 'GET', blocking: false }), + instance.request({ path: '/', method: 'GET', blocking: false }), + instance.request({ path: '/', method: 'GET', blocking: false }), + instance.request({ path: '/', method: 'GET', blocking: false }) ]) const fulfilled = responses.filter(r => r.status === 'fulfilled') const rejected = responses.filter(r => r.status === 'rejected') - t.assert.strictEqual(fulfilled.length, 2) - t.assert.strictEqual(rejected.length, 2) + t.assert.strictEqual(fulfilled.length, 1) + t.assert.strictEqual(rejected.length, 3) await instance.close() }) From 4ff4c1296267f9b004f812a9cf2b3eeedcb5bab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 13 Jul 2025 18:59:56 +0200 Subject: [PATCH 1119/1295] fix: account for EPIPE fetch errors in tests (#6255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add todo flag to flaky tests * add a specific check for EPIPE error * fix: skip test instead of marking todo * Update test/close-pipelining.test.js Co-authored-by: Aras Abbasi Signed-off-by: Gürgün Dayıoğlu * Update test/close-pipelining.test.js Co-authored-by: Aras Abbasi Signed-off-by: Gürgün Dayıoğlu --------- Signed-off-by: Gürgün Dayıoğlu Co-authored-by: Aras Abbasi --- test/helper.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/helper.js b/test/helper.js index f15382b3463..43c1db27e8c 100644 --- a/test/helper.js +++ b/test/helper.js @@ -296,6 +296,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { }).then((response) => { t.assert.strictEqual(response.status, 413) stepIn() + }).catch((err) => { + // Handle EPIPE error - server closed connection after sending 413 + if (err.cause?.code === 'EPIPE' || err.message.includes('fetch failed')) { + t.assert.ok(true, 'Expected EPIPE error due to server closing connection on 413') + } else { + throw err + } + stepIn() }) // Node errors for OPTIONS requests with a stream body and no Content-Length header @@ -316,6 +324,14 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { t.assert.ok(!response.ok) t.assert.strictEqual(response.status, 413) stepIn() + }).catch((err) => { + // Handle EPIPE error - server closed connection after sending 413 + if (err.cause?.code === 'EPIPE' || err.message.includes('fetch failed')) { + t.assert.ok(true, 'Expected EPIPE error due to server closing connection on 413') + } else { + throw err + } + stepIn() }) } From 81d8796bb984644529b87ba59ff7949e2bee5946 Mon Sep 17 00:00:00 2001 From: Sahachai Date: Tue, 15 Jul 2025 14:16:02 +0700 Subject: [PATCH 1120/1295] docs: correct parameter name in frameworkErrors handler (#6257) --- docs/Reference/Routes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index ec8e173bd44..c7bd2aa01fb 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -782,7 +782,7 @@ const secret = { > const Fastify = require('fastify') > > const fastify = Fastify({ -> frameworkErrors: function (err, res, res) { +> frameworkErrors: function (err, req, res) { > if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) { > res.code(400) > return res.send("Invalid header provided") From 95292098426943822fcbc40beaacb314db4bbfbd Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Wed, 16 Jul 2025 18:41:01 +0800 Subject: [PATCH 1121/1295] docs: fix server page headings level (#6258) Signed-off-by: Chiawen Chen --- docs/Reference/Server.md | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index e6dcbb32523..3bd2ebb074e 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -52,7 +52,6 @@ describes the properties available in that options object. - [after](#after) - [ready](#ready) - [listen](#listen) - - [`listenTextResolver`](#listentextresolver) - [addresses](#addresses) - [routing](#routing) - [route](#route) @@ -65,7 +64,7 @@ describes the properties available in that options object. - [prefix](#prefix) - [pluginName](#pluginname) - [hasPlugin](#hasplugin) - - [listeningOrigin](#listeningorigin) + - [listeningOrigin](#listeningorigin) - [log](#log) - [version](#version) - [inject](#inject) @@ -980,20 +979,16 @@ core](https://nodejs.org/api/net.html#serverlistenoptions-callback) options object. Thus, all core options are available with the following additional Fastify specific options: -### `listenTextResolver` - +* listenTextResolver: Set an optional resolver for the text to log after server +has been successfully started. It is possible to override the default +`Server listening at [address]` log entry using this option. -Set an optional resolver for the text to log after server has been successfully -started. -It is possible to override the default `Server listening at [address]` log -entry using this option. - -```js -server.listen({ - port: 9080, - listenTextResolver: (address) => { return `Prometheus metrics server is listening at ${address}` } -}) -``` + ```js + server.listen({ + port: 9080, + listenTextResolver: (address) => { return `Prometheus metrics server is listening at ${address}` } + }) + ``` By default, the server will listen on the address(es) resolved by `localhost` when no specific host is provided. If listening on any available interface is @@ -1271,7 +1266,7 @@ fastify.ready(() => { }) ``` -### listeningOrigin +#### listeningOrigin The current origin the server is listening to. From c277b9f0ec27356de6551d7777117bd739057e99 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sun, 20 Jul 2025 18:36:53 +0100 Subject: [PATCH 1122/1295] fix: add FST_ERR_CTP_INVALID_JSON_BODY (#5925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add FST_ERR_CTP_INVALID_JSON_BODY * improve * fix * Update lib/contentTypeParser.js Co-authored-by: Gürgün Dayıoğlu Signed-off-by: Aras Abbasi * feedback * should expose 86 errors --------- Signed-off-by: Aras Abbasi Co-authored-by: Gürgün Dayıoğlu --- docs/Reference/Errors.md | 4 +++- lib/contentTypeParser.js | 10 ++++++---- lib/errors.js | 5 +++++ test/buffer.test.js | 22 ++++++++++++++++++++++ test/internals/errors.test.js | 12 +++++++++++- test/types/errors.test-d.ts | 1 + types/errors.d.ts | 1 + 7 files changed, 49 insertions(+), 6 deletions(-) diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 2f8fb3a7f18..9d592277686 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -29,6 +29,7 @@ - [FST_ERR_CTP_INVALID_MEDIA_TYPE](#fst_err_ctp_invalid_media_type) - [FST_ERR_CTP_INVALID_CONTENT_LENGTH](#fst_err_ctp_invalid_content_length) - [FST_ERR_CTP_EMPTY_JSON_BODY](#fst_err_ctp_empty_json_body) + - [FST_ERR_CTP_INVALID_JSON_BODY](#fst_err_ctp_invalid_json_body) - [FST_ERR_CTP_INSTANCE_ALREADY_STARTED](#fst_err_ctp_instance_already_started) - [FST_ERR_INSTANCE_ALREADY_LISTENING](#fst_err_instance_already_listening) - [FST_ERR_DEC_ALREADY_PRESENT](#fst_err_dec_already_present) @@ -299,7 +300,8 @@ Below is a table with all the error codes used by Fastify. | FST_ERR_CTP_BODY_TOO_LARGE | The request body is larger than the provided limit. | Increase the limit in the Fastify server instance setting: [bodyLimit](./Server.md#bodylimit) | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_CTP_INVALID_MEDIA_TYPE | The received media type is not supported (i.e. there is no suitable `Content-Type` parser for it). | Use a different content type. | [#1168](https://github.com/fastify/fastify/pull/1168) | | FST_ERR_CTP_INVALID_CONTENT_LENGTH | Request body size did not match Content-Length. | Check the request body size and the Content-Length header. | [#1168](https://github.com/fastify/fastify/pull/1168) | -| FST_ERR_CTP_EMPTY_JSON_BODY | Body cannot be empty when content-type is set to application/json. | Check the request body. | [#1253](https://github.com/fastify/fastify/pull/1253) | +| FST_ERR_CTP_EMPTY_JSON_BODY | Body is not valid JSON but content-type is set to application/json. | Check if the request body is valid JSON. | [#5925](https://github.com/fastify/fastify/pull/5925) | +| FST_ERR_CTP_INVALID_JSON_BODY | Body cannot be empty when content-type is set to application/json. | Check the request body. | [#1253](https://github.com/fastify/fastify/pull/1253) | | FST_ERR_CTP_INSTANCE_ALREADY_STARTED | Fastify is already started. | - | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_INSTANCE_ALREADY_LISTENING | Fastify instance is already listening. | - | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_DEC_ALREADY_PRESENT | A decorator with the same name is already registered. | Use a different decorator name. | [#1168](https://github.com/fastify/fastify/pull/1168) | diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index 57c29844ca2..ffb3c425c90 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -24,7 +24,8 @@ const { FST_ERR_CTP_INVALID_MEDIA_TYPE, FST_ERR_CTP_INVALID_CONTENT_LENGTH, FST_ERR_CTP_EMPTY_JSON_BODY, - FST_ERR_CTP_INSTANCE_ALREADY_STARTED + FST_ERR_CTP_INSTANCE_ALREADY_STARTED, + FST_ERR_CTP_INVALID_JSON_BODY } = require('./errors') const { FSTSEC001 } = require('./warnings') @@ -301,6 +302,8 @@ function rawBody (request, reply, options, parser, done) { } function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) { + const parseOptions = { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning } + return defaultJsonParser function defaultJsonParser (req, body, done) { @@ -309,10 +312,9 @@ function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) { return } try { - done(null, secureJsonParse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning })) + done(null, secureJsonParse(body, parseOptions)) } catch (err) { - err.statusCode = 400 - done(err, undefined) + done(new FST_ERR_CTP_INVALID_JSON_BODY(), undefined) } } } diff --git a/lib/errors.js b/lib/errors.js index 52d17a110a4..53c14abfae1 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -124,6 +124,11 @@ const codes = { "Body cannot be empty when content-type is set to 'application/json'", 400 ), + FST_ERR_CTP_INVALID_JSON_BODY: createError( + 'FST_ERR_CTP_INVALID_JSON_BODY', + "Body is not valid JSON but content-type is set to 'application/json'", + 400 + ), FST_ERR_CTP_INSTANCE_ALREADY_STARTED: createError( 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED', 'Cannot call "%s" when fastify instance is already started!', diff --git a/test/buffer.test.js b/test/buffer.test.js index 2f09456ead3..61c37dfc925 100644 --- a/test/buffer.test.js +++ b/test/buffer.test.js @@ -49,4 +49,26 @@ test('Buffer test', async t => { statusCode: 400 }) }) + + await test('should return 400 if the body is invalid json', async t => { + t.plan(3) + + const response = await fastify.inject({ + method: 'DELETE', + url: '/', + payload: Buffer.from(']'), + headers: { + 'content-type': 'application/json' + } + }) + + t.assert.ifError(response.error) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(response.payload.toString()), { + error: 'Bad Request', + code: 'FST_ERR_CTP_INVALID_JSON_BODY', + message: 'Body is not valid JSON but content-type is set to \'application/json\'', + statusCode: 400 + }) + }) }) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 3f7794135de..2499b380f18 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -const expectedErrors = 85 +const expectedErrors = 86 test(`should expose ${expectedErrors} errors`, t => { t.plan(1) @@ -190,6 +190,16 @@ test('FST_ERR_CTP_EMPTY_JSON_BODY', t => { t.assert.ok(error instanceof Error) }) +test('FST_ERR_CTP_INVALID_JSON_BODY', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_JSON_BODY() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_JSON_BODY') + t.assert.strictEqual(error.message, "Body is not valid JSON but content-type is set to 'application/json'") + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) +}) + test('FST_ERR_CTP_INSTANCE_ALREADY_STARTED', t => { t.plan(5) const error = new errors.FST_ERR_CTP_INSTANCE_ALREADY_STARTED() diff --git a/test/types/errors.test-d.ts b/test/types/errors.test-d.ts index a81ffe4a977..c14dbdb6f45 100644 --- a/test/types/errors.test-d.ts +++ b/test/types/errors.test-d.ts @@ -22,6 +22,7 @@ expectAssignable(errorCodes.FST_ERR_CTP_BODY_TOO_LARGE) expectAssignable(errorCodes.FST_ERR_CTP_INVALID_MEDIA_TYPE) expectAssignable(errorCodes.FST_ERR_CTP_INVALID_CONTENT_LENGTH) expectAssignable(errorCodes.FST_ERR_CTP_EMPTY_JSON_BODY) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_JSON_BODY) expectAssignable(errorCodes.FST_ERR_CTP_INSTANCE_ALREADY_STARTED) expectAssignable(errorCodes.FST_ERR_DEC_ALREADY_PRESENT) expectAssignable(errorCodes.FST_ERR_DEC_DEPENDENCY_INVALID_TYPE) diff --git a/types/errors.d.ts b/types/errors.d.ts index 3b831e30952..777f97b0f74 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -21,6 +21,7 @@ export type FastifyErrorCodes = Record< 'FST_ERR_CTP_INVALID_MEDIA_TYPE' | 'FST_ERR_CTP_INVALID_CONTENT_LENGTH' | 'FST_ERR_CTP_EMPTY_JSON_BODY' | + 'FST_ERR_CTP_INVALID_JSON_BODY' | 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED' | 'FST_ERR_DEC_ALREADY_PRESENT' | 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE' | From b84061926bb9f7afda65104e1d8d29abbd64090e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Mon, 28 Jul 2025 09:58:17 +0200 Subject: [PATCH 1123/1295] feat: optimize content type parser by using AsyncResource.bind() (#6262) * feat: optimize content type parser by using AsyncResource.bind() * fix: improve condition checks * simplify rawBody * fix: make sure to call emitDestroy once resource is created * fix: byteLength for string * add test * fix typo * add kReplyIsError --- lib/contentTypeParser.js | 77 +++++++++++++++++++--------------------- test/body-limit.test.js | 51 ++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 41 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index ffb3c425c90..adda30b5540 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -175,17 +175,18 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply const parser = this.getParser(contentType) if (parser === undefined) { - if (request.is404) { + if (request.is404 === true) { handler(request, reply) - } else { - reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined)) + return } - // Early return to avoid allocating an AsyncResource if it's not needed + reply[kReplyIsError] = true + reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined)) return } const resource = new AsyncResource('content-type-parser:run', request) + const done = resource.bind(onDone) if (parser.asString === true || parser.asBuffer === true) { rawBody( @@ -195,48 +196,44 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply parser, done ) - } else { - const result = parser.fn(request, request[kRequestPayloadStream], done) + return + } - if (result && typeof result.then === 'function') { - result.then(body => done(null, body), done) - } + const result = parser.fn(request, request[kRequestPayloadStream], done) + if (result && typeof result.then === 'function') { + result.then(body => { done(null, body) }, done) } - function done (error, body) { - // We cannot use resource.bind() because it is broken in node v12 and v14 - resource.runInAsyncScope(() => { - resource.emitDestroy() - if (error) { - reply[kReplyIsError] = true - reply.send(error) - } else { - request.body = body - handler(request, reply) - } - }) + function onDone (error, body) { + resource.emitDestroy() + if (error != null) { + // We must close the connection as the client may + // send more data + reply.header('connection', 'close') + reply[kReplyIsError] = true + reply.send(error) + return + } + request.body = body + handler(request, reply) } } function rawBody (request, reply, options, parser, done) { - const asString = parser.asString + const asString = parser.asString === true const limit = options.limit === null ? parser.bodyLimit : options.limit const contentLength = Number(request.headers['content-length']) if (contentLength > limit) { - // We must close the connection as the client is going - // to send this data anyway - reply.header('connection', 'close') - reply.send(new FST_ERR_CTP_BODY_TOO_LARGE()) + done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined) return } let receivedLength = 0 - let body = asString === true ? '' : [] - + let body = asString ? '' : [] const payload = request[kRequestPayloadStream] || request.raw - if (asString === true) { + if (asString) { payload.setEncoding('utf8') } @@ -246,7 +243,7 @@ function rawBody (request, reply, options, parser, done) { payload.resume() function onData (chunk) { - receivedLength += chunk.length + receivedLength += asString ? Buffer.byteLength(chunk) : chunk.length const { receivedEncodedLength = 0 } = payload // The resulting body length must not exceed bodyLimit (see "zip bomb"). // The case when encoded length is larger than received length is rather theoretical, @@ -255,11 +252,11 @@ function rawBody (request, reply, options, parser, done) { payload.removeListener('data', onData) payload.removeListener('end', onEnd) payload.removeListener('error', onEnd) - reply.send(new FST_ERR_CTP_BODY_TOO_LARGE()) + done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined) return } - if (asString === true) { + if (asString) { body += chunk } else { body.push(chunk) @@ -271,32 +268,30 @@ function rawBody (request, reply, options, parser, done) { payload.removeListener('end', onEnd) payload.removeListener('error', onEnd) - if (err !== undefined) { + if (err != null) { if (!(typeof err.statusCode === 'number' && err.statusCode >= 400)) { err.statusCode = 400 } - reply[kReplyIsError] = true - reply.code(err.statusCode).send(err) + done(err, undefined) return } - if (asString === true) { + if (asString) { receivedLength = Buffer.byteLength(body) } if (!Number.isNaN(contentLength) && (payload.receivedEncodedLength || receivedLength) !== contentLength) { - reply.header('connection', 'close') - reply.send(new FST_ERR_CTP_INVALID_CONTENT_LENGTH()) + done(new FST_ERR_CTP_INVALID_CONTENT_LENGTH(), undefined) return } - if (asString === false) { + if (!asString) { body = Buffer.concat(body) } const result = parser.fn(request, body, done) if (result && typeof result.then === 'function') { - result.then(body => done(null, body), done) + result.then(body => { done(null, body) }, done) } } } @@ -313,7 +308,7 @@ function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) { } try { done(null, secureJsonParse(body, parseOptions)) - } catch (err) { + } catch { done(new FST_ERR_CTP_INVALID_JSON_BODY(), undefined) } } diff --git a/test/body-limit.test.js b/test/body-limit.test.js index 1eece839251..8d9c282322d 100644 --- a/test/body-limit.test.js +++ b/test/body-limit.test.js @@ -171,3 +171,54 @@ test('request.routeOptions.bodyLimit should be equal to server limit', async t = t.assert.ok(result.ok) t.assert.strictEqual(result.status, 200) }) + +test('bodyLimit should use byte length for UTF-8 strings, not character length', async t => { + t.plan(4) + + // Create a string with multi-byte UTF-8 characters + // Use Japanese characters that are 3 bytes each in UTF-8 + const multiByteString = 'あああ' // 3 characters, 9 bytes in UTF-8 + t.assert.strictEqual(multiByteString.length, 3) // 3 characters + t.assert.strictEqual(Buffer.byteLength(multiByteString, 'utf8'), 9) // 9 bytes + + const fastify = Fastify() + + // Add a custom text parser that returns the string as-is + fastify.addContentTypeParser('text/plain', { parseAs: 'string' }, (req, body, done) => { + done(null, body) + }) + + // Set body limit to 7 bytes - this should reject the string (9 bytes) + // even though string.length (3) would be under any reasonable limit + fastify.post('/test-utf8', { + bodyLimit: 7 + }, (request, reply) => { + reply.send({ body: request.body, length: request.body.length }) + }) + + await t.test('should reject body when byte length exceeds limit', async (t) => { + const result = await fastify.inject({ + method: 'POST', + url: '/test-utf8', + headers: { 'Content-Type': 'text/plain', 'Content-Length': null }, + payload: multiByteString + }) + + t.assert.strictEqual(result.statusCode, 413) + }) + + await t.test('should accept body when byte length is within limit', async (t) => { + const smallString = 'あ' // 1 character, 3 bytes, under the 7 byte limit + + const result = await fastify.inject({ + method: 'POST', + url: '/test-utf8', + headers: { 'Content-Type': 'text/plain' }, + payload: smallString + }) + + t.assert.strictEqual(result.statusCode, 200) + t.assert.strictEqual(result.json().body, smallString) + t.assert.strictEqual(result.json().length, 1) // 1 character + }) +}) From 9be9fc07b7697a4818699e26effe3364d9d3ce4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 30 Jul 2025 09:55:03 +0200 Subject: [PATCH 1124/1295] fix: remove unnecessary body length check in contentTypeParser (#6266) --- lib/contentTypeParser.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/contentTypeParser.js b/lib/contentTypeParser.js index adda30b5540..35dcfb031c8 100644 --- a/lib/contentTypeParser.js +++ b/lib/contentTypeParser.js @@ -276,10 +276,6 @@ function rawBody (request, reply, options, parser, done) { return } - if (asString) { - receivedLength = Buffer.byteLength(body) - } - if (!Number.isNaN(contentLength) && (payload.receivedEncodedLength || receivedLength) !== contentLength) { done(new FST_ERR_CTP_INVALID_CONTENT_LENGTH(), undefined) return From 1e6300bc4e38086542dd7c9fa48d8a4110a88f4b Mon Sep 17 00:00:00 2001 From: Guilherme D'Amato Date: Thu, 31 Jul 2025 07:22:42 -0300 Subject: [PATCH 1125/1295] docs(ecosystem): add fastify-multilingual (#6268) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 8fc84a9adc8..f2705769167 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -507,6 +507,8 @@ middlewares into Fastify plugins [MS Graph Change Notifications webhooks](https://learn.microsoft.com/it-it/graph/change-notifications-delivery-webhooks?tabs=http). - [`fastify-multer`](https://github.com/fox1t/fastify-multer) Multer is a plugin for handling multipart/form-data, which is primarily used for uploading files. +- [`fastify-multilingual`](https://github.com/gbrugger/fastify-multilingual) Unobtrusively + decorates fastify request with Polyglot.js for i18n. - [`fastify-nats`](https://github.com/mahmed8003/fastify-nats) Plugin to share [NATS](https://nats.io) client across Fastify. - [`fastify-next-auth`](https://github.com/wobsoriano/fastify-next-auth) From c483ded8bc5471d51e1b4d207534c5148bca2366 Mon Sep 17 00:00:00 2001 From: ts0307 Date: Fri, 1 Aug 2025 18:12:34 +0800 Subject: [PATCH 1126/1295] chore: fix docs Request.md (#6270) Signed-off-by: ts0307 --- docs/Reference/Request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 846a7d62078..f1fadfa3cc9 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -237,7 +237,7 @@ const newValidate = request.compileValidationSchema(newSchema) console.log(newValidate === validate) // false ``` -### .validateInput(data, [schema | httpStatus], [httpStatus]) +### .validateInput(data, [schema | httpPart], [httpPart]) This function validates the input based on the provided schema or HTTP part. If From 89c517d7f47d6bdeab7974e937eda4e35737b751 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:24:10 +0000 Subject: [PATCH 1127/1295] chore: Bump typescript in the dev-dependencies-typescript group (#6273) Bumps the dev-dependencies-typescript group with 1 update: [typescript](https://github.com/microsoft/TypeScript). Updates `typescript` from 5.8.3 to 5.9.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2) --- updated-dependencies: - dependency-name: typescript dependency-version: 5.9.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies-typescript ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3b86dff859..4c6c66a0857 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,7 @@ "proxyquire": "^2.1.3", "split2": "^4.2.0", "tsd": "^0.32.0", - "typescript": "~5.8.2", + "typescript": "~5.9.2", "undici": "^7.11.0", "vary": "^1.1.2", "yup": "^1.4.0" From 1d35138994940855059264530b2ae16f2ac1ac02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:25:37 +0000 Subject: [PATCH 1128/1295] chore: Bump cross-env from 7.0.3 to 10.0.0 (#6274) Bumps [cross-env](https://github.com/kentcdodds/cross-env) from 7.0.3 to 10.0.0. - [Release notes](https://github.com/kentcdodds/cross-env/releases) - [Changelog](https://github.com/kentcdodds/cross-env/blob/main/CHANGELOG.md) - [Commits](https://github.com/kentcdodds/cross-env/compare/v7.0.3...v10.0.0) --- updated-dependencies: - dependency-name: cross-env dependency-version: 10.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c6c66a0857..5bae1d10b2e 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "borp": "^0.20.0", "branch-comparer": "^1.1.0", "concurrently": "^9.1.2", - "cross-env": "^7.0.3", + "cross-env": "^10.0.0", "eslint": "^9.0.0", "fast-json-body": "^1.1.0", "fastify-plugin": "^5.0.0", From ad97fbb5630488b2c830ff7bc27f495b21b87243 Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Sat, 2 Aug 2025 01:49:46 +1000 Subject: [PATCH 1129/1295] feat(types): enforce reply status code types with type providers (#6250) --- test/types/type-provider.test-d.ts | 55 ++++++++++++++++++++++++++++++ types/reply.d.ts | 4 +-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index faa59b66a86..6cc20d87046 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -1009,6 +1009,37 @@ expectAssignable(server.withTypeProvider().get<{ Reply: } )) +// ------------------------------------------------------------------- +// Reply Status Code (Different Status Codes) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + res.code(200) + res.code(500) + expectError(() => res.code(201)) + expectError(() => res.code(400)) + } +)) + // ------------------------------------------------------------------- // RouteGeneric Reply Type Return (Different Status Codes) // ------------------------------------------------------------------- @@ -1032,6 +1063,30 @@ expectAssignable(server.get<{ } )) +// ------------------------------------------------------------------- +// RouteGeneric Status Code (Different Status Codes) +// ------------------------------------------------------------------- + +expectAssignable(server.get<{ + Reply: { + 200: string | { msg: string } + 400: number + '5xx': { error: string } + } +}>( + '/', + async (_, res) => { + res.code(200) + res.code(400) + res.code(500) + res.code(502) + expectError(() => res.code(201)) + expectError(() => res.code(300)) + expectError(() => res.code(404)) + return 'hello' + } +)) + // ------------------------------------------------------------------- // RouteGeneric Reply Type Return: Non Assignable (Different Status Codes) // ------------------------------------------------------------------- diff --git a/types/reply.d.ts b/types/reply.d.ts index 0491d8a02be..631c4037720 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -46,8 +46,8 @@ export interface FastifyReply< log: FastifyBaseLogger; request: FastifyRequest; server: FastifyInstance; - code>(statusCode: Code): FastifyReply>; - status>(statusCode: Code): FastifyReply>; + code : keyof SchemaCompiler['response'] extends ReplyKeysToCodes ? keyof SchemaCompiler['response'] : ReplyKeysToCodes>(statusCode: Code): FastifyReply>; + status : keyof SchemaCompiler['response'] extends ReplyKeysToCodes ? keyof SchemaCompiler['response'] : ReplyKeysToCodes>(statusCode: Code): FastifyReply>; statusCode: number; sent: boolean; send(payload?: ReplyType): FastifyReply; From 66c191c8cb2c5b0c4d85fbed19487c2bb9920b71 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Sun, 3 Aug 2025 04:29:16 -0400 Subject: [PATCH 1130/1295] feat: move router options to own key (#5985) --- build/build-validation.js | 20 +- docs/Reference/Server.md | 449 +++++++++++++++--------- docs/Reference/Warnings.md | 3 +- fastify.js | 37 +- lib/configValidator.js | 224 ++++++++++-- lib/route.js | 40 ++- lib/warnings.js | 12 +- test/constrained-routes.test.js | 231 +++++++++++++ test/custom-querystring-parser.test.js | 18 + test/router-options.test.js | 450 +++++++++++++++++++++++++ test/use-semicolon-delimiter.test.js | 84 +++++ 11 files changed, 1359 insertions(+), 209 deletions(-) diff --git a/build/build-validation.js b/build/build-validation.js index 0d880a11f65..ae3a881d862 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -43,7 +43,14 @@ const defaultInitOptions = { http2SessionTimeout: 72000, // 72 seconds exposeHeadRoutes: true, useSemicolonDelimiter: false, - allowErrorHandlerOverride: true // TODO: set to false in v6 + allowErrorHandlerOverride: true, // TODO: set to false in v6 + routerOptions: { + ignoreTrailingSlash: false, + ignoreDuplicateSlashes: false, + maxParamLength: 100, + allowUnsafeRegex: false, + useSemicolonDelimiter: false + } } const schema = { @@ -103,6 +110,17 @@ const schema = { http2SessionTimeout: { type: 'integer', default: defaultInitOptions.http2SessionTimeout }, exposeHeadRoutes: { type: 'boolean', default: defaultInitOptions.exposeHeadRoutes }, useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.useSemicolonDelimiter }, + routerOptions: { + type: 'object', + additionalProperties: false, + properties: { + ignoreTrailingSlash: { type: 'boolean', default: defaultInitOptions.routerOptions.ignoreTrailingSlash }, + ignoreDuplicateSlashes: { type: 'boolean', default: defaultInitOptions.routerOptions.ignoreDuplicateSlashes }, + maxParamLength: { type: 'integer', default: defaultInitOptions.routerOptions.maxParamLength }, + allowUnsafeRegex: { type: 'boolean', default: defaultInitOptions.routerOptions.allowUnsafeRegex }, + useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.routerOptions.useSemicolonDelimiter } + } + }, constraints: { type: 'object', additionalProperties: { diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 3bd2ebb074e..73bd761fb9f 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -17,9 +17,6 @@ describes the properties available in that options object. - [`forceCloseConnections`](#forcecloseconnections) - [`maxRequestsPerSocket`](#maxrequestspersocket) - [`requestTimeout`](#requesttimeout) - - [`ignoreTrailingSlash`](#ignoretrailingslash) - - [`ignoreDuplicateSlashes`](#ignoreduplicateslashes) - - [`maxParamLength`](#maxparamlength) - [`bodyLimit`](#bodylimit) - [`onProtoPoisoning`](#onprotopoisoning) - [`onConstructorPoisoning`](#onconstructorpoisoning) @@ -27,16 +24,12 @@ describes the properties available in that options object. - [`loggerInstance`](#loggerInstance) - [`disableRequestLogging`](#disablerequestlogging) - [`serverFactory`](#serverfactory) - - [`caseSensitive`](#casesensitive) - - [`allowUnsafeRegex`](#allowunsaferegex) - [`requestIdHeader`](#requestidheader) - [`requestIdLogLabel`](#requestidloglabel) - [`genReqId`](#genreqid) - [`trustProxy`](#trustproxy) - [`pluginTimeout`](#plugintimeout) - - [`querystringParser`](#querystringparser) - [`exposeHeadRoutes`](#exposeheadroutes) - - [`constraints`](#constraints) - [`return503OnClosing`](#return503onclosing) - [`ajv`](#ajv) - [`serializerOpts`](#serializeropts) @@ -44,8 +37,19 @@ describes the properties available in that options object. - [`frameworkErrors`](#frameworkerrors) - [`clientErrorHandler`](#clienterrorhandler) - [`rewriteUrl`](#rewriteurl) - - [`useSemicolonDelimiter`](#usesemicolondelimiter) - [`allowErrorHandlerOverride`](#allowerrorhandleroverride) + - [RouterOptions](#routeroptions) + - [`allowUnsafeRegex`](#allowunsaferegex) + - [`buildPrettyMeta`](#buildprettymeta) + - [`caseSensitive`](#casesensitive) + - [`constraints`](#constraints) + - [`defaultRoute`](#defaultroute) + - [`ignoreDuplicateSlashes`](#ignoreduplicateslashes) + - [`ignoreTrailingSlash`](#ignoretrailingslash) + - [`maxParamLength`](#maxparamlength) + - [`onBadUrl`](#onbadurl) + - [`querystringParser`](#querystringparser) + - [`useSemicolonDelimiter`](#usesemicolondelimiter) - [Instance](#instance) - [Server Methods](#server-methods) - [server](#server) @@ -214,74 +218,6 @@ in front. > ℹ️ Note: > At the time of writing, only node >= v14.11.0 supports this option -### `ignoreTrailingSlash` - - -+ Default: `false` - -Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle -routing. By default, Fastify will take into account the trailing slashes. -Paths like `/foo` and `/foo/` are treated as different paths. If you want to -change this, set this flag to `true`. That way, both `/foo` and `/foo/` will -point to the same route. This option applies to *all* route registrations for -the resulting server instance. - -```js -const fastify = require('fastify')({ - ignoreTrailingSlash: true -}) - -// registers both "/foo" and "/foo/" -fastify.get('/foo/', function (req, reply) { - reply.send('foo') -}) - -// registers both "/bar" and "/bar/" -fastify.get('/bar', function (req, reply) { - reply.send('bar') -}) -``` - -### `ignoreDuplicateSlashes` - - -+ Default: `false` - -Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle -routing. You can use `ignoreDuplicateSlashes` option to remove duplicate slashes -from the path. It removes duplicate slashes in the route path and the request -URL. This option applies to *all* route registrations for the resulting server -instance. - -When `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set -to `true` Fastify will remove duplicate slashes, and then trailing slashes, -meaning `//a//b//c//` will be converted to `/a/b/c`. - -```js -const fastify = require('fastify')({ - ignoreDuplicateSlashes: true -}) - -// registers "/foo/bar/" -fastify.get('///foo//bar//', function (req, reply) { - reply.send('foo') -}) -``` - -### `maxParamLength` - - -+ Default: `100` - -You can set a custom length for parameters in parametric (standard, regex, and -multi) routes by using `maxParamLength` option; the default value is 100 -characters. If the maximum length limit is reached, the not found route will -be invoked. - -This can be useful especially if you have a regex-based route, protecting you -against [ReDoS -attacks](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). - ### `bodyLimit` @@ -446,44 +382,6 @@ custom server you must be sure to have the same API exposed. If not, you can enhance the server instance inside the `serverFactory` function before the `return` statement. -### `caseSensitive` - - -+ Default: `true` - -When `true` routes are registered as case-sensitive. That is, `/foo` -is not equal to `/Foo`. -When `false` then routes are case-insensitive. - -Please note that setting this option to `false` goes against -[RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1). - -By setting `caseSensitive` to `false`, all paths will be matched as lowercase, -but the route parameters or wildcards will maintain their original letter -casing. -This option does not affect query strings, please refer to -[`querystringParser`](#querystringparser) to change their handling. - -```js -fastify.get('/user/:username', (request, reply) => { - // Given the URL: /USER/NodeJS - console.log(request.params.username) // -> 'NodeJS' -}) -``` - -### `allowUnsafeRegex` - - -+ Default `false` - -Disabled by default, so routes only allow safe regular expressions. To use -unsafe expressions, set `allowUnsafeRegex` to `true`. - -```js -fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => { - // Throws an error without allowUnsafeRegex = true -}) -``` ### `requestIdHeader` @@ -635,37 +533,6 @@ Automatically creates a sibling `HEAD` route for each `GET` route defined. If you want a custom `HEAD` handler without disabling this option, make sure to define it before the `GET` route. -### `constraints` - - -Fastify's built-in route constraints are provided by `find-my-way`, which -allows constraining routes by `version` or `host`. You can add new constraint -strategies, or override the built-in strategies, by providing a `constraints` -object with strategies for `find-my-way`. You can find more information on -constraint strategies in the -[find-my-way](https://github.com/delvedor/find-my-way) documentation. - -```js -const customVersionStrategy = { - storage: function () { - const versions = {} - return { - get: (version) => { return versions[version] || null }, - set: (version, store) => { versions[version] = store } - } - }, - deriveVersion: (req, ctx) => { - return req.headers['accept'] - } -} - -const fastify = require('fastify')({ - constraints: { - version: customVersionStrategy - } -}) -``` - ### `return503OnClosing` @@ -842,6 +709,254 @@ function rewriteUrl (req) { } ``` +## RouterOptions + + +Fastify uses [`find-my-way`](https://github.com/delvedor/find-my-way) for its +HTTP router. The `routerOptions` parameter allows passing +[`find-my-way` options](https://github.com/delvedor/find-my-way?tab=readme-ov-file#findmywayoptions) +to customize the HTTP router within Fastify. + +### `allowUnsafeRegex` + + ++ Default `false` + +Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which is, +disabled by default, so routes only allow safe regular expressions. To use +unsafe expressions, set `allowUnsafeRegex` to `true`. + +```js +fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => { + // Throws an error without allowUnsafeRegex = true +}) +``` + + +### `buildPrettyMeta` + + +Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which +supports, `buildPrettyMeta` where you can assign a `buildPrettyMeta` +function to sanitize a route's store object to use with the `prettyPrint` +functions. This function should accept a single object and return an object. + +```js +fastify.get('/user/:username', (request, reply) => { + routerOptions: { + buildPrettyMeta: route => { + const cleanMeta = Object.assign({}, route.store) + + // remove private properties + Object.keys(cleanMeta).forEach(k => { + if (typeof k === 'symbol') delete cleanMeta[k] + }) + + return cleanMeta // this will show up in the pretty print output! + }) + } +}) +``` + +### `caseSensitive` + + ++ Default: `true` + +When `true` routes are registered as case-sensitive. That is, `/foo` +is not equal to `/Foo`. +When `false` then routes are case-insensitive. + +Please note that setting this option to `false` goes against +[RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1). + +By setting `caseSensitive` to `false`, all paths will be matched as lowercase, +but the route parameters or wildcards will maintain their original letter +casing. +This option does not affect query strings, please refer to +[`querystringParser`](#querystringparser) to change their handling. + +```js +fastify.get('/user/:username', (request, reply) => { + // Given the URL: /USER/NodeJS + console.log(request.params.username) // -> 'NodeJS' +}) +``` + +### `constraints` + + +Fastify's built-in route constraints are provided by `find-my-way`, which +allows constraining routes by `version` or `host`. You can add new constraint +strategies, or override the built-in strategies, by providing a `constraints` +object with strategies for `find-my-way`. You can find more information on +constraint strategies in the +[find-my-way](https://github.com/delvedor/find-my-way) documentation. + +```js +const customVersionStrategy = { + storage: function () { + const versions = {} + return { + get: (version) => { return versions[version] || null }, + set: (version, store) => { versions[version] = store } + } + }, + deriveVersion: (req, ctx) => { + return req.headers['accept'] + } +} + +const fastify = require('fastify')({ + routerOptions: { + constraints: { + version: customVersionStrategy + } + } +}) +``` + +### `defaultRoute` + + +Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which supports, +can pass a default route with the option defaultRoute. + +```js +const fastify = require('fastify')({ + routerOptions: { + defaultRoute: (req, res) => { + res.statusCode = 404 + res.end() + } + } +}) +``` + +### `ignoreDuplicateSlashes` + + ++ Default: `false` + +Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle +routing. You can use `ignoreDuplicateSlashes` option to remove duplicate slashes +from the path. It removes duplicate slashes in the route path and the request +URL. This option applies to *all* route registrations for the resulting server +instance. + +When `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set +to `true` Fastify will remove duplicate slashes, and then trailing slashes, +meaning `//a//b//c//` will be converted to `/a/b/c`. + +```js +const fastify = require('fastify')({ + routerOptions: { + ignoreDuplicateSlashes: true + } +}) + +// registers "/foo/bar/" +fastify.get('///foo//bar//', function (req, reply) { + reply.send('foo') +}) +``` + +### `ignoreTrailingSlash` + + ++ Default: `false` + +Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle +routing. By default, Fastify will take into account the trailing slashes. +Paths like `/foo` and `/foo/` are treated as different paths. If you want to +change this, set this flag to `true`. That way, both `/foo` and `/foo/` will +point to the same route. This option applies to *all* route registrations for +the resulting server instance. + +```js +const fastify = require('fastify')({ + routerOptions: { + ignoreTrailingSlash: true + } +}) + +// registers both "/foo" and "/foo/" +fastify.get('/foo/', function (req, reply) { + reply.send('foo') +}) + +// registers both "/bar" and "/bar/" +fastify.get('/bar', function (req, reply) { + reply.send('bar') +}) +``` + +### `maxParamLength` + + ++ Default: `100` + +You can set a custom length for parameters in parametric (standard, regex, and +multi) routes by using `maxParamLength` option; the default value is 100 +characters. If the maximum length limit is reached, the not found route will +be invoked. + +This can be useful especially if you have a regex-based route, protecting you +against [ReDoS +attacks](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). + + +### `onBadUrl` + + +Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which supports, +the use case of a badly formatted url (eg: /hello/%world), by default find-my-way +will invoke the defaultRoute, unless you specify the onBadUrl option. + +```js +const fastify = require('fastify')({ + routerOptions: { + onBadUrl: (path, req, res) => { + res.statusCode = 400 + res.end(`Bad path: ${path}`) + } + } +}) +``` + +### `querystringParser` + + +The default query string parser that Fastify uses is the Node.js's core +`querystring` module. + +You can use this option to use a custom parser, such as +[`qs`](https://www.npmjs.com/package/qs). + +If you only want the keys (and not the values) to be case insensitive we +recommend using a custom parser to convert only the keys to lowercase. + +```js +const qs = require('qs') +const fastify = require('fastify')({ + routerOptions: { + querystringParser: str => qs.parse(str) + } +}) +``` + +You can also use Fastify's default parser but change some handling behavior, +like the example below for case insensitive keys and values: + +```js +const querystring = require('node:querystring') +const fastify = require('fastify')({ + routerOptions: { + querystringParser: str => querystring.parse(str.toLowerCase()) + } +}) +``` + ### `useSemicolonDelimiter` @@ -856,7 +971,9 @@ on `;` set `useSemicolonDelimiter` to `true`. ```js const fastify = require('fastify')({ - useSemicolonDelimiter: true + routerOptions: { + useSemicolonDelimiter: true + } }) fastify.get('/dev', async (request, reply) => { @@ -1985,20 +2102,28 @@ The properties that can currently be exposed are: - keepAliveTimeout - bodyLimit - caseSensitive -- allowUnsafeRegex - http2 - https (it will return `false`/`true` or `{ allowHTTP1: true/false }` if explicitly passed) -- ignoreTrailingSlash - disableRequestLogging -- maxParamLength - onProtoPoisoning - onConstructorPoisoning - pluginTimeout - requestIdHeader - requestIdLogLabel - http2SessionTimeout -- useSemicolonDelimiter +- routerOptions + - allowUnsafeRegex + - buildPrettyMeta + - caseSensitive + - constraints + - defaultRoute + - ignoreDuplicateSlashes + - ignoreTrailingSlash + - maxParamLength + - onBadUrl + - querystringParser + - useSemicolonDelimiter ```js const { readFileSync } = require('node:fs') @@ -2011,9 +2136,11 @@ const fastify = Fastify({ cert: readFileSync('./fastify.cert') }, logger: { level: 'trace'}, - ignoreTrailingSlash: true, - maxParamLength: 200, - caseSensitive: true, + routerOptions: { + ignoreTrailingSlash: true, + maxParamLength: 200, + caseSensitive: true, + }, trustProxy: '127.0.0.1,192.168.1.1/24', }) @@ -2021,10 +2148,12 @@ console.log(fastify.initialConfig) /* will log : { - caseSensitive: true, https: { allowHTTP1: true }, - ignoreTrailingSlash: true, - maxParamLength: 200 + routerOptions: { + caseSensitive: true, + ignoreTrailingSlash: true, + maxParamLength: 200 + } } */ @@ -2034,10 +2163,12 @@ fastify.register(async (instance, opts) => { /* will return : { - caseSensitive: true, https: { allowHTTP1: true }, - ignoreTrailingSlash: true, - maxParamLength: 200 + routerOptions: { + caseSensitive: true, + ignoreTrailingSlash: true, + maxParamLength: 200 + } } */ }) diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md index 165baa44775..d07b23d1705 100644 --- a/docs/Reference/Warnings.md +++ b/docs/Reference/Warnings.md @@ -8,7 +8,7 @@ - [FSTWRN001](#FSTWRN001) - [FSTWRN002](#FSTWRN002) - [Fastify Deprecation Codes](#fastify-deprecation-codes) - + - [FSTDEP022](#FSTDEP022) ## Warnings @@ -55,3 +55,4 @@ Deprecation codes are supported by the Node.js CLI options: | Code | Description | How to solve | Discussion | | ---- | ----------- | ------------ | ---------- | +| FSTDEP022 | You are trying to access the deprecated router options on top option properties. | Use `options.routerOptions`. | [#5985](https://github.com/fastify/fastify/pull/5985) diff --git a/fastify.js b/fastify.js index eda58074878..0bb20a08189 100644 --- a/fastify.js +++ b/fastify.js @@ -46,7 +46,7 @@ const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks') const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory') const pluginUtils = require('./lib/pluginUtils') const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory') -const { buildRouting, validateBodyLimitOption } = require('./lib/route') +const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route') const build404 = require('./lib/fourOhFour') const getSecuredInitialConfig = require('./lib/initialConfigValidation') const override = require('./lib/pluginOverride') @@ -108,8 +108,14 @@ function fastify (options) { options = Object.assign({}, options) } - if (options.querystringParser && typeof options.querystringParser !== 'function') { - throw new FST_ERR_QSP_NOT_FN(typeof options.querystringParser) + if ( + (options.querystringParser && typeof options.querystringParser !== 'function') || + ( + options.routerOptions?.querystringParser && + typeof options.routerOptions.querystringParser !== 'function' + ) + ) { + throw new FST_ERR_QSP_NOT_FN(typeof (options.querystringParser ?? options.routerOptions.querystringParser)) } if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') { @@ -160,21 +166,20 @@ function fastify (options) { // exposeHeadRoutes have its default set from the validator options.exposeHeadRoutes = initialConfig.exposeHeadRoutes + options.routerOptions = buildRouterOptions(options, { + defaultRoute, + onBadUrl, + ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash, + ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes, + maxParamLength: defaultInitOptions.maxParamLength, + allowUnsafeRegex: defaultInitOptions.allowUnsafeRegex, + buildPrettyMeta: defaultBuildPrettyMeta, + useSemicolonDelimiter: defaultInitOptions.useSemicolonDelimiter + }) + // Default router const router = buildRouting({ - config: { - defaultRoute, - onBadUrl, - constraints: options.constraints, - ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash, - ignoreDuplicateSlashes: options.ignoreDuplicateSlashes || defaultInitOptions.ignoreDuplicateSlashes, - maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength, - caseSensitive: options.caseSensitive, - allowUnsafeRegex: options.allowUnsafeRegex || defaultInitOptions.allowUnsafeRegex, - buildPrettyMeta: defaultBuildPrettyMeta, - querystringParser: options.querystringParser, - useSemicolonDelimiter: options.useSemicolonDelimiter ?? defaultInitOptions.useSemicolonDelimiter - } + config: options.routerOptions }) // 404 router, used for handling encapsulated 404 handlers diff --git a/lib/configValidator.js b/lib/configValidator.js index c2d842896dc..a110fb6bcb5 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -3,7 +3,7 @@ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":false,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -1002,43 +1002,210 @@ data["useSemicolonDelimiter"] = coerced25; } var valid0 = _errs67 === errors; if(valid0){ -if(data.constraints !== undefined){ -let data23 = data.constraints; +if(data.routerOptions !== undefined){ +let data23 = data.routerOptions; const _errs69 = errors; if(errors === _errs69){ if(data23 && typeof data23 == "object" && !Array.isArray(data23)){ +if(data23.ignoreTrailingSlash === undefined){ +data23.ignoreTrailingSlash = false; +} +if(data23.ignoreDuplicateSlashes === undefined){ +data23.ignoreDuplicateSlashes = false; +} +if(data23.maxParamLength === undefined){ +data23.maxParamLength = 100; +} +if(data23.allowUnsafeRegex === undefined){ +data23.allowUnsafeRegex = false; +} +if(data23.useSemicolonDelimiter === undefined){ +data23.useSemicolonDelimiter = false; +} +const _errs71 = errors; for(const key2 in data23){ -let data24 = data23[key2]; +if(!(((((key2 === "ignoreTrailingSlash") || (key2 === "ignoreDuplicateSlashes")) || (key2 === "maxParamLength")) || (key2 === "allowUnsafeRegex")) || (key2 === "useSemicolonDelimiter"))){ +delete data23[key2]; +} +} +if(_errs71 === errors){ +let data24 = data23.ignoreTrailingSlash; const _errs72 = errors; -if(errors === _errs72){ -if(data24 && typeof data24 == "object" && !Array.isArray(data24)){ +if(typeof data24 !== "boolean"){ +let coerced26 = undefined; +if(!(coerced26 !== undefined)){ +if(data24 === "false" || data24 === 0 || data24 === null){ +coerced26 = false; +} +else if(data24 === "true" || data24 === 1){ +coerced26 = true; +} +else { +validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +return false; +} +} +if(coerced26 !== undefined){ +data24 = coerced26; +if(data23 !== undefined){ +data23["ignoreTrailingSlash"] = coerced26; +} +} +} +var valid7 = _errs72 === errors; +if(valid7){ +let data25 = data23.ignoreDuplicateSlashes; +const _errs74 = errors; +if(typeof data25 !== "boolean"){ +let coerced27 = undefined; +if(!(coerced27 !== undefined)){ +if(data25 === "false" || data25 === 0 || data25 === null){ +coerced27 = false; +} +else if(data25 === "true" || data25 === 1){ +coerced27 = true; +} +else { +validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreDuplicateSlashes",schemaPath:"#/properties/routerOptions/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +return false; +} +} +if(coerced27 !== undefined){ +data25 = coerced27; +if(data23 !== undefined){ +data23["ignoreDuplicateSlashes"] = coerced27; +} +} +} +var valid7 = _errs74 === errors; +if(valid7){ +let data26 = data23.maxParamLength; +const _errs76 = errors; +if(!(((typeof data26 == "number") && (!(data26 % 1) && !isNaN(data26))) && (isFinite(data26)))){ +let dataType28 = typeof data26; +let coerced28 = undefined; +if(!(coerced28 !== undefined)){ +if(dataType28 === "boolean" || data26 === null + || (dataType28 === "string" && data26 && data26 == +data26 && !(data26 % 1))){ +coerced28 = +data26; +} +else { +validate10.errors = [{instancePath:instancePath+"/routerOptions/maxParamLength",schemaPath:"#/properties/routerOptions/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +return false; +} +} +if(coerced28 !== undefined){ +data26 = coerced28; +if(data23 !== undefined){ +data23["maxParamLength"] = coerced28; +} +} +} +var valid7 = _errs76 === errors; +if(valid7){ +let data27 = data23.allowUnsafeRegex; +const _errs78 = errors; +if(typeof data27 !== "boolean"){ +let coerced29 = undefined; +if(!(coerced29 !== undefined)){ +if(data27 === "false" || data27 === 0 || data27 === null){ +coerced29 = false; +} +else if(data27 === "true" || data27 === 1){ +coerced29 = true; +} +else { +validate10.errors = [{instancePath:instancePath+"/routerOptions/allowUnsafeRegex",schemaPath:"#/properties/routerOptions/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +return false; +} +} +if(coerced29 !== undefined){ +data27 = coerced29; +if(data23 !== undefined){ +data23["allowUnsafeRegex"] = coerced29; +} +} +} +var valid7 = _errs78 === errors; +if(valid7){ +let data28 = data23.useSemicolonDelimiter; +const _errs80 = errors; +if(typeof data28 !== "boolean"){ +let coerced30 = undefined; +if(!(coerced30 !== undefined)){ +if(data28 === "false" || data28 === 0 || data28 === null){ +coerced30 = false; +} +else if(data28 === "true" || data28 === 1){ +coerced30 = true; +} +else { +validate10.errors = [{instancePath:instancePath+"/routerOptions/useSemicolonDelimiter",schemaPath:"#/properties/routerOptions/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +return false; +} +} +if(coerced30 !== undefined){ +data28 = coerced30; +if(data23 !== undefined){ +data23["useSemicolonDelimiter"] = coerced30; +} +} +} +var valid7 = _errs80 === errors; +} +} +} +} +} +} +else { +validate10.errors = [{instancePath:instancePath+"/routerOptions",schemaPath:"#/properties/routerOptions/type",keyword:"type",params:{type: "object"},message:"must be object"}]; +return false; +} +} +var valid0 = _errs69 === errors; +} +else { +var valid0 = true; +} +if(valid0){ +if(data.constraints !== undefined){ +let data29 = data.constraints; +const _errs82 = errors; +if(errors === _errs82){ +if(data29 && typeof data29 == "object" && !Array.isArray(data29)){ +for(const key3 in data29){ +let data30 = data29[key3]; +const _errs85 = errors; +if(errors === _errs85){ +if(data30 && typeof data30 == "object" && !Array.isArray(data30)){ let missing1; -if(((((data24.name === undefined) && (missing1 = "name")) || ((data24.storage === undefined) && (missing1 = "storage"))) || ((data24.validate === undefined) && (missing1 = "validate"))) || ((data24.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){ -validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; +if(((((data30.name === undefined) && (missing1 = "name")) || ((data30.storage === undefined) && (missing1 = "storage"))) || ((data30.validate === undefined) && (missing1 = "validate"))) || ((data30.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){ +validate10.errors = [{instancePath:instancePath+"/constraints/" + key3.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; return false; } else { -if(data24.name !== undefined){ -let data25 = data24.name; -if(typeof data25 !== "string"){ -let dataType26 = typeof data25; -let coerced26 = undefined; -if(!(coerced26 !== undefined)){ -if(dataType26 == "number" || dataType26 == "boolean"){ -coerced26 = "" + data25; +if(data30.name !== undefined){ +let data31 = data30.name; +if(typeof data31 !== "string"){ +let dataType31 = typeof data31; +let coerced31 = undefined; +if(!(coerced31 !== undefined)){ +if(dataType31 == "number" || dataType31 == "boolean"){ +coerced31 = "" + data31; } -else if(data25 === null){ -coerced26 = ""; +else if(data31 === null){ +coerced31 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/constraints/" + key3.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } -if(coerced26 !== undefined){ -data25 = coerced26; -if(data24 !== undefined){ -data24["name"] = coerced26; +if(coerced31 !== undefined){ +data31 = coerced31; +if(data30 !== undefined){ +data30["name"] = coerced31; } } } @@ -1046,12 +1213,12 @@ data24["name"] = coerced26; } } else { -validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/type",keyword:"type",params:{type: "object"},message:"must be object"}]; +validate10.errors = [{instancePath:instancePath+"/constraints/" + key3.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; } } -var valid7 = _errs72 === errors; -if(!valid7){ +var valid8 = _errs85 === errors; +if(!valid8){ break; } } @@ -1061,7 +1228,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs69 === errors; +var valid0 = _errs82 === errors; } else { var valid0 = true; @@ -1090,6 +1257,7 @@ var valid0 = true; } } } +} else { validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; @@ -1100,5 +1268,5 @@ return errors === 0; } -module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false,"allowErrorHandlerOverride":true} +module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false,"allowErrorHandlerOverride":true,"routerOptions":{"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"allowUnsafeRegex":false,"useSemicolonDelimiter":false}} /* c8 ignore stop */ diff --git a/lib/route.js b/lib/route.js index d396a02d1b0..d5e5d832a81 100644 --- a/lib/route.js +++ b/lib/route.js @@ -29,6 +29,8 @@ const { FST_ERR_HOOK_INVALID_ASYNC_HANDLER } = require('./errors') +const { FSTDEP022 } = require('./warnings') + const { kRoutePrefix, kSupportedHTTPMethods, @@ -52,6 +54,20 @@ const { buildErrorHandler } = require('./error-handler') const { createChildLogger } = require('./logger-factory.js') const { getGenReqId } = require('./reqIdGenFactory.js') +const routerKeys = [ + 'allowUnsafeRegex', + 'buildPrettyMeta', + 'caseSensitive', + 'constraints', + 'defaultRoute', + 'ignoreDuplicateSlashes', + 'ignoreTrailingSlash', + 'maxParamLength', + 'onBadUrl', + 'querystringParser', + 'useSemicolonDelimiter' +] + function buildRouting (options) { const router = FindMyWay(options.config) @@ -85,8 +101,8 @@ function buildRouting (options) { globalExposeHeadRoutes = options.exposeHeadRoutes disableRequestLogging = options.disableRequestLogging - ignoreTrailingSlash = options.ignoreTrailingSlash - ignoreDuplicateSlashes = options.ignoreDuplicateSlashes + ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash + ignoreDuplicateSlashes = options.routerOptions.ignoreDuplicateSlashes return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true keepAliveConnections = fastifyArgs.keepAliveConnections }, @@ -572,6 +588,24 @@ function runPreParsing (err, request, reply) { } } +function buildRouterOptions (options, defaultOptions) { + const routerOptions = options.routerOptions || Object.create(null) + + const usedDeprecatedOptions = routerKeys.filter(key => Object.hasOwn(options, key)) + + if (usedDeprecatedOptions.length > 0) { + FSTDEP022(usedDeprecatedOptions.join(', ')) + } + + for (const key of routerKeys) { + if (!Object.hasOwn(routerOptions, key)) { + routerOptions[key] = options[key] ?? defaultOptions[key] + } + } + + return routerOptions +} + /** * Used within the route handler as a `net.Socket.close` event handler. * The purpose is to remove a socket from the tracked sockets collection when @@ -583,4 +617,4 @@ function removeTrackedSocket () { function noop () { } -module.exports = { buildRouting, validateBodyLimitOption } +module.exports = { buildRouting, validateBodyLimitOption, buildRouterOptions } diff --git a/lib/warnings.js b/lib/warnings.js index 07a620e3991..f79f342e48e 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -6,8 +6,10 @@ const { createWarning } = require('process-warning') * Deprecation codes: * - FSTWRN001 * - FSTSEC001 + * - FSTDEP022 * * Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT not be reused. + * - FSTDEP022 is used by v5 and MUST NOT be reused. * Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT not be reused. */ @@ -39,9 +41,17 @@ const FSTSEC001 = createWarning({ unlimited: true }) +const FSTDEP022 = createWarning({ + name: 'FastifyWarning', + code: 'FSTDPE022', + message: 'The router options for %s property access is deprecated. Please use "options.routerOptions" instead for accessing router options. The router options will be removed in `fastify@6`.', + unlimited: true +}) + module.exports = { FSTWRN001, FSTWRN003, FSTWRN004, - FSTSEC001 + FSTSEC001, + FSTDEP022 } diff --git a/test/constrained-routes.test.js b/test/constrained-routes.test.js index 6e5d8a9222f..b8b057ae2b9 100644 --- a/test/constrained-routes.test.js +++ b/test/constrained-routes.test.js @@ -905,3 +905,234 @@ test('Allow regex constraints in routes', async t => { t.assert.strictEqual(res.statusCode, 404) } }) + +test('Should allow registering custom rotuerOptions constrained routes', async t => { + t.plan(5) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers['x-secret'] + }, + validate () { return true } + } + + const fastify = Fastify({ routerOptions: { constraints: { secret: constraint } } }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'beta' }, + handler: (req, reply) => { + reply.send({ hello: 'from beta' }) + } + }) + + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'alpha' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from alpha' }) + t.assert.strictEqual(res.statusCode, 200) + } + + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'beta' + } + }) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from beta' }) + t.assert.strictEqual(res.statusCode, 200) + } + + { + const res = await fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'X-Secret': 'gamma' + } + }) + t.assert.strictEqual(res.statusCode, 404) + } +}) + +test('Custom rotuerOptions constrained routes registered also for HEAD method generated by fastify', (t, done) => { + t.plan(3) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers['x-secret'] + }, + validate () { return true } + } + + const fastify = Fastify({ routerOptions: { constraints: { secret: constraint } } }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'mySecret' }, + handler: (req, reply) => { + reply.send('from mySecret - my length is 31') + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/', + headers: { + 'X-Secret': 'mySecret' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.headers['content-length'], '31') + t.assert.strictEqual(res.statusCode, 200) + done() + }) +}) + +test('allow async rotuerOptions constraints', async (t) => { + t.plan(5) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(null, req.headers['x-secret']) + }, + validate () { return true } + } + + const fastify = Fastify({ routerOptions: { constraints: { secret: constraint } } }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'beta' }, + handler: (req, reply) => { + reply.send({ hello: 'from beta' }) + } + }) + + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'alpha' } }) + t.assert.deepStrictEqual(JSON.parse(payload), { hello: 'from alpha' }) + t.assert.strictEqual(statusCode, 200) + } + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'beta' } }) + t.assert.deepStrictEqual(JSON.parse(payload), { hello: 'from beta' }) + t.assert.strictEqual(statusCode, 200) + } + { + const { statusCode } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'gamma' } }) + t.assert.strictEqual(statusCode, 404) + } +}) + +test('error in async rotuerOptions constraints', async (t) => { + t.plan(8) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const fastify = Fastify({ routerOptions: { constraints: { secret: constraint } } }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'beta' }, + handler: (req, reply) => { + reply.send({ hello: 'from beta' }) + } + }) + + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'alpha' } }) + t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.assert.strictEqual(statusCode, 500) + } + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'beta' } }) + t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.assert.strictEqual(statusCode, 500) + } + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'gamma' } }) + t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.assert.strictEqual(statusCode, 500) + } + { + const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/' }) + t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 }) + t.assert.strictEqual(statusCode, 500) + } +}) diff --git a/test/custom-querystring-parser.test.js b/test/custom-querystring-parser.test.js index 2f24a0d27db..a6f8a24d8d1 100644 --- a/test/custom-querystring-parser.test.js +++ b/test/custom-querystring-parser.test.js @@ -109,3 +109,21 @@ test('Custom querystring parser should be a function', t => { ) } }) + +test('Custom querystring parser should be a function', t => { + t.plan(1) + + try { + Fastify({ + routerOptions: { + querystringParser: 10 + } + }) + t.fail('Should throw') + } catch (err) { + t.assert.equal( + err.message, + "querystringParser option should be a function, instead got 'number'" + ) + } +}) diff --git a/test/router-options.test.js b/test/router-options.test.js index e575a76a994..ad5fafd57eb 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -2,6 +2,7 @@ const split = require('split2') const { test } = require('node:test') +const querystring = require('node:querystring') const Fastify = require('../') const { FST_ERR_BAD_URL, @@ -445,3 +446,452 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST } ) }) + +test('Should honor routerOptions.defaultRoute', async t => { + t.plan(3) + const fastify = Fastify({ + routerOptions: { + defaultRoute: function (_, res) { + t.assert.ok('default route called') + res.statusCode = 404 + res.end('default route') + } + } + }) + + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'default route') +}) + +test('Should honor routerOptions.badUrl', async t => { + t.plan(3) + const fastify = Fastify({ + routerOptions: { + defaultRoute: function (_, res) { + t.asset.fail('default route should not be called') + }, + onBadUrl: function (path, _, res) { + t.assert.ok('bad url called') + res.statusCode = 400 + res.end(`Bath URL: ${path}`) + } + } + }) + + fastify.get('/hello/:id', (req, res) => { + res.send({ hello: 'world' }) + }) + + const res = await fastify.inject('/hello/%world') + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.payload, 'Bath URL: /hello/%world') +}) + +test('Should honor routerOptions.ignoreTrailingSlash', async t => { + t.plan(4) + const fastify = Fastify({ + routerOptions: { + ignoreTrailingSlash: true + } + }) + + fastify.get('/test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor routerOptions.ignoreDuplicateSlashes', async t => { + t.plan(4) + const fastify = Fastify({ + routerOptions: { + ignoreDuplicateSlashes: true + } + }) + + fastify.get('/test//test///test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test/test/test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test//test///test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor routerOptions.ignoreTrailingSlash and routerOptions.ignoreDuplicateSlashes', async t => { + t.plan(4) + const fastify = Fastify({ + routerOptions: { + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true + } + }) + + t.after(() => fastify.close()) + + fastify.get('/test//test///test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test/test/test/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test//test///test//') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor routerOptions.maxParamLength', async (t) => { + const fastify = Fastify({ + routerOptions: + { + maxParamLength: 10 + } + }) + + fastify.get('/test/:id', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test/123456789' + }) + t.assert.strictEqual(res.statusCode, 200) + + const resError = await fastify.inject({ + method: 'GET', + url: '/test/123456789abcd' + }) + t.assert.strictEqual(resError.statusCode, 404) +}) + +test('Should honor routerOptions.allowUnsafeRegex', async (t) => { + const fastify = Fastify({ + routerOptions: + { + allowUnsafeRegex: true + } + }) + + fastify.get('/test/:id(([a-f0-9]{3},?)+)', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/test/bac,1ea' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/test/qwerty' + }) + + t.assert.strictEqual(res.statusCode, 404) +}) + +test('Should honor routerOptions.caseSensitive', async (t) => { + const fastify = Fastify({ + routerOptions: + { + caseSensitive: false + } + }) + + fastify.get('/TeSt', (req, reply) => { + reply.send('test') + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/test' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/tEsT' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/TEST' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.queryStringParser', async (t) => { + t.plan(4) + const fastify = Fastify({ + routerOptions: + { + querystringParser: function (str) { + t.assert.ok('custom query string parser called') + return querystring.parse(str) + } + } + }) + + fastify.get('/test', (req, reply) => { + t.assert.deepStrictEqual(req.query.foo, 'bar') + t.assert.deepStrictEqual(req.query.baz, 'faz') + reply.send('test') + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test?foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.useSemicolonDelimiter', async (t) => { + t.plan(6) + const fastify = Fastify({ + routerOptions: + { + useSemicolonDelimiter: true + } + }) + + fastify.get('/test', (req, reply) => { + t.assert.deepStrictEqual(req.query.foo, 'bar') + t.assert.deepStrictEqual(req.query.baz, 'faz') + reply.send('test') + }) + + // Support semicolon delimiter + let res = await fastify.inject({ + method: 'GET', + url: '/test;foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) + + // Support query string `?` delimiter + res = await fastify.inject({ + method: 'GET', + url: '/test?foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.buildPrettyMeta', async (t) => { + t.plan(10) + const fastify = Fastify({ + routerOptions: + { + buildPrettyMeta: function (route) { + t.assert.ok('custom buildPrettyMeta called') + return { metaKey: route.path } + } + } + }) + + fastify.get('/test', () => {}) + fastify.get('/test/hello', () => {}) + fastify.get('/testing', () => {}) + fastify.get('/testing/:param', () => {}) + fastify.put('/update', () => {}) + + await fastify.ready() + + const result = fastify.printRoutes({ includeMeta: true }) + const expected = `\ +└── / + ├── test (GET, HEAD) + │ • (metaKey) "/test" + │ ├── /hello (GET, HEAD) + │ │ • (metaKey) "/test/hello" + │ └── ing (GET, HEAD) + │ • (metaKey) "/testing" + │ └── / + │ └── :param (GET, HEAD) + │ • (metaKey) "/testing/:param" + └── update (PUT) + • (metaKey) "/update" +` + + t.assert.strictEqual(result, expected) +}) + +test('Should honor routerOptions.ignoreTrailingSlash and routerOptions.ignoreDuplicateSlashes over top level options', async t => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: false, + ignoreDuplicateSlashes: false, + routerOptions: { + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true + } + }) + + fastify.get('/test//test///test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test/test/test/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test//test///test//') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor routerOptions.maxParamLength over maxParamLength option', async (t) => { + const fastify = Fastify({ + maxParamLength: 0, + routerOptions: + { + maxParamLength: 10 + } + }) + + fastify.get('/test/:id', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test/123456789' + }) + t.assert.strictEqual(res.statusCode, 200) + + const resError = await fastify.inject({ + method: 'GET', + url: '/test/123456789abcd' + }) + t.assert.strictEqual(resError.statusCode, 404) +}) + +test('Should honor routerOptions.allowUnsafeRegex over allowUnsafeRegex option', async (t) => { + const fastify = Fastify({ + allowUnsafeRegex: false, + routerOptions: + { + allowUnsafeRegex: true + } + }) + + fastify.get('/test/:id(([a-f0-9]{3},?)+)', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/test/bac,1ea' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/test/qwerty' + }) + + t.assert.strictEqual(res.statusCode, 404) +}) + +test('Should honor routerOptions.caseSensitive over caseSensitive option', async (t) => { + const fastify = Fastify({ + caseSensitive: true, + routerOptions: + { + caseSensitive: false + } + }) + + fastify.get('/TeSt', (req, reply) => { + reply.send('test') + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/test' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/tEsT' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/TEST' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.queryStringParser over queryStringParser option', async (t) => { + t.plan(4) + const fastify = Fastify({ + queryStringParser: undefined, + routerOptions: + { + querystringParser: function (str) { + t.assert.ok('custom query string parser called') + return querystring.parse(str) + } + } + }) + + fastify.get('/test', (req, reply) => { + t.assert.deepStrictEqual(req.query.foo, 'bar') + t.assert.deepStrictEqual(req.query.baz, 'faz') + reply.send('test') + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test?foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.useSemicolonDelimiter over useSemicolonDelimiter option', async (t) => { + t.plan(6) + const fastify = Fastify({ + useSemicolonDelimiter: false, + routerOptions: + { + useSemicolonDelimiter: true + } + }) + + fastify.get('/test', (req, reply) => { + t.assert.deepStrictEqual(req.query.foo, 'bar') + t.assert.deepStrictEqual(req.query.baz, 'faz') + reply.send('test') + }) + + // Support semicolon delimiter + let res = await fastify.inject({ + method: 'GET', + url: '/test;foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) + + // Support query string `?` delimiter + res = await fastify.inject({ + method: 'GET', + url: '/test?foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) +}) diff --git a/test/use-semicolon-delimiter.test.js b/test/use-semicolon-delimiter.test.js index efefc619e1c..5d0506d61b1 100644 --- a/test/use-semicolon-delimiter.test.js +++ b/test/use-semicolon-delimiter.test.js @@ -82,3 +82,87 @@ test('use semicolon delimiter set to false return 404', async (t) => { t.assert.ok(!result.ok) t.assert.strictEqual(result.status, 404) }) + +test('use routerOptions semicolon delimiter default false', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/1234;foo=bar', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), {}) +}) + +test('use routerOptions semicolon delimiter set to true', async t => { + t.plan(3) + const fastify = Fastify({ + routerOptions: { + useSemicolonDelimiter: true + } + }) + + fastify.get('/1234', async (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { + foo: 'bar' + }) +}) + +test('use routerOptions semicolon delimiter set to false', async t => { + t.plan(3) + + const fastify = Fastify({ + routerOptions: { + useSemicolonDelimiter: false + } + }) + + fastify.get('/1234;foo=bar', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), {}) +}) + +test('use routerOptions semicolon delimiter set to false return 404', async t => { + t.plan(2) + + const fastify = Fastify({ + routerOptions: { + useSemicolonDelimiter: false + } + }) + + fastify.get('/1234', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 404) +}) From bbc46dbb2c2d66c2ac3e94d0de81630a040122ab Mon Sep 17 00:00:00 2001 From: Dipali Bohara <110589664+Dipali127@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:36:07 +0530 Subject: [PATCH 1131/1295] docs(contributing): fix grammar and clarify instructions (#6277) --- CONTRIBUTING.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f71f53e595..95ec7c6d554 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ There are a few basic ground rules for contributors: ### Fastify previous versions -Every Fastify's version is on its own branch. All Fastify related +Every version of Fastify has its own branch. All Fastify related changes should be based on the corresponding branch. We have a [Long Term Support](./docs/Reference/LTS.md) policy that defines @@ -58,8 +58,8 @@ not bump version numbers in pull requests. ## Plugins -The contributors to the Fastify's plugins must attend the same rules of the -Fastify repository with a few adjustments: +Contributors to Fastify plugins must follow the same rules as the main Fastify repository, + with a few adjustments: 1. Any member can publish a release. 1. The plugin version must follow the [semver](https://semver.org/) @@ -67,7 +67,7 @@ Fastify repository with a few adjustments: 1. The Node.js compatibility must match with Fastify's main branch. 1. The new release must have the changelog information stored in the GitHub release. For this we suggest adopting a tool like - [`releasify`](https://github.com/fastify/releasify) to archive this. + [`releasify`](https://github.com/fastify/releasify) to achieve this. 1. PR opened by bots (like Dependabot) can be merged if the CI is green and the Node.js versions supported are the same as the plugin. @@ -97,7 +97,7 @@ the following tasks: [`fastify/fastify:HEAD`](https://github.com/fastify/fastify/pulls) that adds your name, username, and email to the team you have chosen in the [README.md](./README.md) and [package.json](./package.json) *(if you are part - of the core team)* files. The members lists are sorted alphabetically by last + of the core team)* files. The member lists are sorted alphabetically by last name; make sure to add your name in the proper order. 4. Open a pull request to [`fastify/website:HEAD`](https://github.com/fastify/website/pulls) adding @@ -107,7 +107,7 @@ the following tasks: in the proper order. Use your GitHub profile icon for the `picture:` field. 5. Read the [pinned announcements](https://github.com/orgs/fastify/discussions/categories/announcements) to be updated with the organization’s news. -6. The person that does the onboarding must add you to the [npm +6. The person who does the onboarding must add you to the [npm org](https://www.npmjs.com/org/fastify), so that you can help maintain the official plugins. 7. Optionally, the person can be added as an Open Collective member @@ -115,7 +115,7 @@ the following tasks: ### Offboarding Collaborators -We are thankful to you and we are really glad to have worked with you. We'll be +We are thankful to you and we are really glad to have worked with you. We'd be really happy to see you here again if you want to come back, but for now the person that did the onboarding must: 1. Ask the collaborator if they want to stay or not. From b3e868f7865756168d828e3942bd23fbcd07156f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Tue, 5 Aug 2025 20:50:53 +0200 Subject: [PATCH 1132/1295] chore: refactor reply.send and prioritize kReplyIsError (#6267) Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- lib/error-handler.js | 6 +++--- lib/reply.js | 36 +++++++++++++++++------------------- package.json | 1 + 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/error-handler.js b/lib/error-handler.js index 3904779fa0c..c5ebf629a2f 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -39,7 +39,7 @@ function handleError (reply, error, cb) { if (!reply.log[kDisableRequestLogging]) { reply.log.warn( { req: reply.request, res: reply, err: error }, - error && error.message + error?.message ) } reply.raw.writeHead(reply.raw.statusCode) @@ -89,14 +89,14 @@ function defaultErrorHandler (error, request, reply) { if (!reply.log[kDisableRequestLogging]) { reply.log.info( { res: reply, err: error }, - error && error.message + error?.message ) } } else { if (!reply.log[kDisableRequestLogging]) { reply.log.error( { req: request, res: reply, err: error }, - error && error.message + error?.message ) } } diff --git a/lib/reply.js b/lib/reply.js index 1aaeaf17419..3bc7c722cf5 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -126,16 +126,16 @@ Reply.prototype.hijack = function () { } Reply.prototype.send = function (payload) { - if (this[kReplyIsRunningOnErrorHook] === true) { + if (this[kReplyIsRunningOnErrorHook]) { throw new FST_ERR_SEND_INSIDE_ONERR() } - if (this.sent) { + if (this.sent === true) { this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) }) return this } - if (payload instanceof Error || this[kReplyIsError] === true) { + if (this[kReplyIsError] || payload instanceof Error) { this[kReplyIsError] = false onErrorHook(this, payload, onSendHook) return this @@ -162,8 +162,8 @@ Reply.prototype.send = function (payload) { return this } - if (payload?.buffer instanceof ArrayBuffer) { - if (hasContentType === false) { + if (payload.buffer instanceof ArrayBuffer) { + if (!hasContentType) { this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET } const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength) @@ -171,7 +171,7 @@ Reply.prototype.send = function (payload) { return this } - if (hasContentType === false && typeof payload === 'string') { + if (!hasContentType && typeof payload === 'string') { this[kReplyHeaders]['content-type'] = CONTENT_TYPE.PLAIN onSendHook(this, payload) return this @@ -182,26 +182,24 @@ Reply.prototype.send = function (payload) { if (typeof payload !== 'string') { preSerializationHook(this, payload) return this - } else { - payload = this[kReplySerializer](payload) } + payload = this[kReplySerializer](payload) // The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json' - } else if (hasContentType === false || contentType.indexOf('json') > -1) { - if (hasContentType === false) { + } else if (!hasContentType || contentType.indexOf('json') !== -1) { + if (!hasContentType) { this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON - } else { + } else if (contentType.indexOf('charset') === -1) { // If user doesn't set charset, we will set charset to utf-8 - if (contentType.indexOf('charset') === -1) { - const customContentType = contentType.trim() - if (customContentType.endsWith(';')) { - // custom content-type is ended with ';' - this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8` - } else { - this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8` - } + const customContentType = contentType.trim() + if (customContentType.endsWith(';')) { + // custom content-type is ended with ';' + this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8` + } else { + this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8` } } + if (typeof payload !== 'string') { preSerializationHook(this, payload) return this diff --git a/package.json b/package.json index 5bae1d10b2e..6b6334dcc69 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"", "benchmark": "concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"autocannon -c 100 -d 30 -p 10 localhost:3000/\"", "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", + "benchmark:parser:error": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -H \"content-length:123\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100 ", "coverage:ci-check-coverage": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100", From 31db1bd9aad60fb25708d9057450f7dd6d2d769a Mon Sep 17 00:00:00 2001 From: Krishnadas PC Date: Wed, 6 Aug 2025 12:47:22 +0530 Subject: [PATCH 1133/1295] docs(ecosystem): add fastify-permissions plugin (#6265) --- docs/Guides/Ecosystem.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f2705769167..7b908e44d8f 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -559,6 +559,9 @@ middlewares into Fastify plugins A set of Fastify plugins to integrate Apple Wallet Web Service specification - [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo) Fastify plugin for memoize responses by expressive settings. +- [`fastify-permissions`](https://github.com/pckrishnadas88/fastify-permissions) + Route-level permission middleware for Fastify supports + custom permission checks. - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker thread pool plugin using [Piscina](https://github.com/piscinajs/piscina). - [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin to @@ -739,6 +742,7 @@ middlewares into Fastify plugins - [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple and updated Typeorm plugin for use with Fastify. + #### [Community Tools](#community-tools) - [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows) From 0d7182129610148dda7f996f1a0962c43842a228 Mon Sep 17 00:00:00 2001 From: Lubos Date: Fri, 8 Aug 2025 16:04:17 +0800 Subject: [PATCH 1134/1295] docs: Add Hey API to ecosystem (#6280) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 7b908e44d8f..852198e11e1 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -199,6 +199,8 @@ section. Run REST APIs and other web applications using your existing Node.js application framework (Express, Koa, Hapi and Fastify), on top of AWS Lambda, Huawei and many other clouds. +- [`@hey-api/openapi-ts`](https://heyapi.dev/openapi-ts/plugins/fastify) + The OpenAPI to TypeScript codegen. Generate clients, SDKs, validators, and more. - [`@immobiliarelabs/fastify-metrics`](https://github.com/immobiliare/fastify-metrics) Minimalistic and opinionated plugin that collects usage/process metrics and dispatches to [statsd](https://github.com/statsd/statsd). From b0e255a2cdab05ed382a99db70fde482f6464bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 10 Aug 2025 16:01:11 +0200 Subject: [PATCH 1135/1295] fix: OPTIONS Content-Type handling (#6263) * fix: OPTIONS Content-Type handling * remove unnecessary comment * add more tests * add no content-type test * add more * typo * one test * remove unnecessary test --- lib/handleRequest.js | 30 ++++++++++------------- test/internals/handle-request.test.js | 34 +++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/lib/handleRequest.js b/lib/handleRequest.js index ceb3800f939..be8b43eacd7 100644 --- a/lib/handleRequest.js +++ b/lib/handleRequest.js @@ -21,9 +21,7 @@ function handleRequest (err, request, reply) { return } - const method = request.raw.method - const headers = request.headers - const context = request[kRouteContext] + const method = request.method if (this[kSupportedHTTPMethods].bodyless.has(method)) { handler(request, reply) @@ -31,28 +29,26 @@ function handleRequest (err, request, reply) { } if (this[kSupportedHTTPMethods].bodywith.has(method)) { + const headers = request.headers const contentType = headers['content-type'] - const contentLength = headers['content-length'] - const transferEncoding = headers['transfer-encoding'] if (contentType === undefined) { - if ( - (contentLength === undefined || contentLength === '0') && - transferEncoding === undefined - ) { + const contentLength = headers['content-length'] + const transferEncoding = headers['transfer-encoding'] + const isEmptyBody = transferEncoding === undefined && + (contentLength === undefined || contentLength === '0') + + if (isEmptyBody) { // Request has no body to parse handler(request, reply) - } else { - context.contentTypeParser.run('', handler, request, reply) - } - } else { - if (contentLength === undefined && transferEncoding === undefined && method === 'OPTIONS') { - // OPTIONS can have a Content-Type header without a body - handler(request, reply) return } - context.contentTypeParser.run(contentType, handler, request, reply) + + request[kRouteContext].contentTypeParser.run('', handler, request, reply) + return } + + request[kRouteContext].contentTypeParser.run(contentType, handler, request, reply) return } diff --git a/test/internals/handle-request.test.js b/test/internals/handle-request.test.js index 0c5abd684b4..a1752123adb 100644 --- a/test/internals/handle-request.test.js +++ b/test/internals/handle-request.test.js @@ -191,7 +191,7 @@ test('request should be defined in onSend Hook on post request with content type }) test('request should be defined in onSend Hook on options request with content type application/x-www-form-urlencoded', async t => { - t.plan(5) + t.plan(15) const fastify = require('../..')() t.after(() => { @@ -209,16 +209,40 @@ test('request should be defined in onSend Hook on options request with content t reply.send(200) }) - const fastifyServer = await fastify.listen({ port: 0 }) - const result = await fetch(fastifyServer, { + // Test 1: OPTIONS with body and content-type header + const result1 = await fastify.inject({ + method: 'OPTIONS', + url: '/', + body: 'first-name=OPTIONS&last-name=METHOD', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + } + }) + + // Content-Type is not supported + t.assert.strictEqual(result1.statusCode, 415) + + // Test 2: OPTIONS with content-type header only (no body) + const result2 = await fastify.inject({ method: 'OPTIONS', + url: '/', headers: { 'content-type': 'application/x-www-form-urlencoded' } }) - // Body parsing skipped, so no body sent - t.assert.strictEqual(result.status, 200) + // Content-Type is not supported + t.assert.strictEqual(result2.statusCode, 415) + + // Test 3: OPTIONS with body but no content-type header + const result3 = await fastify.inject({ + method: 'OPTIONS', + url: '/', + body: 'first-name=OPTIONS&last-name=METHOD' + }) + + // No content-type with payload + t.assert.strictEqual(result3.statusCode, 415) }) test('request should respond with an error if an unserialized payload is sent inside an async handler', async t => { From b84733e997340076240d93db5bbeef716df58756 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 11 Aug 2025 14:30:14 +0200 Subject: [PATCH 1136/1295] Bumped v5.5.0 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 0bb20a08189..e97762b3dbb 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.4.0' +const VERSION = '5.5.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 6b6334dcc69..3d23ad31cfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.4.0", + "version": "5.5.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 812ef58f8ab1e765a148177d5491d6384670b19e Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Thu, 14 Aug 2025 05:33:30 -0400 Subject: [PATCH 1137/1295] fix: update typescript pino type to pick (#6287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gürgün Dayıoğlu --- types/logger.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/logger.d.ts b/types/logger.d.ts index 30199a25e18..a6982ef7fcb 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -20,7 +20,7 @@ export type Bindings = pino.Bindings export type ChildLoggerOptions = pino.ChildLoggerOptions -export interface FastifyBaseLogger extends pino.BaseLogger { +export interface FastifyBaseLogger extends Pick { child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger } From 7d7f065e0efb1b2d9f3ba318ac3915a1ac59d5c6 Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Fri, 15 Aug 2025 02:13:18 -0400 Subject: [PATCH 1138/1295] feat(types): router option types (#6282) * feat(types): router option types * fix * fix * pr feedback --- fastify.d.ts | 17 +++++++++++++++++ test/types/instance.test-d.ts | 19 ++++++++++++++++++- types/instance.d.ts | 2 ++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/fastify.d.ts b/fastify.d.ts index 84cb64d7384..4c0fe0ca181 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -89,6 +89,22 @@ declare namespace fastify { type TrustProxyFunction = (address: string, hop: number) => boolean + export type FastifyRouterOptions = { + allowUnsafeRegex?: boolean, + buildPrettyMeta?: (route: { [k: string]: unknown, store: { [k: string]: unknown } }) => object, + caseSensitive?: boolean, + constraints?: { + [name: string]: ConstraintStrategy, unknown>, + }, + defaultRoute?: (req: FastifyRequest, res: FastifyReply) => void, + ignoreDuplicateSlashes?: boolean, + ignoreTrailingSlash?: boolean, + maxParamLength?: number, + onBadUrl?: (path: string, req: FastifyRequest, res: FastifyReply) => void, + querystringParser?: (str: string) => { [key: string]: unknown }, + useSemicolonDelimiter?: boolean, + } + /** * Options for a fastify server instance. Utilizes conditional logic on the generic server parameter to enforce certain https and http2 */ @@ -159,6 +175,7 @@ declare namespace fastify { clientErrorHandler?: (error: ConnectionError, socket: Socket) => void, childLoggerFactory?: FastifyChildLoggerFactory, allowErrorHandlerOverride?: boolean + routerOptions?: FastifyRouterOptions, } /** diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index fdd7369d118..3780ebd2ba1 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -15,6 +15,8 @@ import { FastifyRequest } from '../../types/request' import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema' import { AddressInfo } from 'node:net' import { Bindings, ChildLoggerOptions } from '../../types/logger' +import { ConstraintStrategy } from 'find-my-way' +import { FindMyWayVersion } from '../../types/instance' const server = fastify() @@ -309,7 +311,22 @@ type InitialConfig = Readonly<{ requestIdHeader?: string | false, requestIdLogLabel?: string, http2SessionTimeout?: number, - useSemicolonDelimiter?: boolean + useSemicolonDelimiter?: boolean, + routerOptions?: { + allowUnsafeRegex?: boolean, + buildPrettyMeta?: (route: { [k: string]: unknown, store: { [k: string]: unknown } }) => object, + caseSensitive?: boolean, + constraints?: { + [name: string]: ConstraintStrategy, unknown> + } + defaultRoute?: (req: FastifyRequest, res: FastifyReply) => void, + ignoreDuplicateSlashes?: boolean, + ignoreTrailingSlash?: boolean, + maxParamLength?: number, + onBadUrl?: (path: string, req: FastifyRequest, res: FastifyReply) => void, + querystringParser?: (str: string) => { [key: string]: unknown }, + useSemicolonDelimiter?: boolean, + } }> expectType(fastify().initialConfig) diff --git a/types/instance.d.ts b/types/instance.d.ts index d67be42062b..0173dff9ade 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -23,6 +23,7 @@ import { SafePromiseLike } from './type-provider' import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' +import { FastifyRouterOptions } from '../fastify' export interface PrintRoutesOptions { method?: HTTPMethods; @@ -603,5 +604,6 @@ export interface FastifyInstance< requestIdLogLabel?: string, http2SessionTimeout?: number, useSemicolonDelimiter?: boolean, + routerOptions?: FastifyRouterOptions }> } From d44b3f7b2d2e89ef17fe514f52cb0dff74124ac0 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Fri, 15 Aug 2025 18:01:29 -0400 Subject: [PATCH 1139/1295] fix(types): Fix use of "esModuleInterop: false" (#6292) * fix(types): Fix use of "esModuleInterop: false" Pino 9.8 changed its type definitions to be more correct (see https://github.com/pinojs/pino/pull/2223 and linked discussions), but that causes problems for projects that don't set "esModuleInterop: true" (see https://github.com/pinojs/pino/issues/2253). Doing a namespace import (so fastify.d.ts can see pino's types) should address this. * Switch to named exports Now that Fastify simply re-exports Pino types, its "Standard Fastify logging function" docblock has little or no effect. (For example, VS Code goes straight to Pino's definition.) --- types/logger.d.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/types/logger.d.ts b/types/logger.d.ts index a6982ef7fcb..c9f64804644 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -7,20 +7,24 @@ import { FastifySchema } from './schema' import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' import { ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' -import pino from 'pino' +import type { + BaseLogger, + LogFn as FastifyLogFn, + LevelWithSilent as LogLevel, + Bindings, + ChildLoggerOptions, + LoggerOptions as PinoLoggerOptions +} from 'pino' -/** - * Standard Fastify logging function - */ -export type FastifyLogFn = pino.LogFn - -export type LogLevel = pino.LevelWithSilent - -export type Bindings = pino.Bindings - -export type ChildLoggerOptions = pino.ChildLoggerOptions +export type { + FastifyLogFn, + LogLevel, + Bindings, + ChildLoggerOptions, + PinoLoggerOptions +} -export interface FastifyBaseLogger extends Pick { +export interface FastifyBaseLogger extends Pick { child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger } @@ -34,8 +38,6 @@ export interface FastifyLoggerStreamDestination { write(msg: string): void; } -export type PinoLoggerOptions = pino.LoggerOptions - // TODO: once node 18 is EOL, this type can be replaced with plain FastifyReply. /** * Specialized reply type used for the `res` log serializer, since only `statusCode` is passed in certain cases. From 3c05b1f00d221a8dd29193f1da7c69c562406229 Mon Sep 17 00:00:00 2001 From: Hugo CM <43181249+hmanzoni@users.noreply.github.com> Date: Thu, 28 Aug 2025 08:01:42 +0200 Subject: [PATCH 1140/1295] docs: fix typo in Reference: TypeScript.md (#6302) Signed-off-by: Hugo Manzoni --- docs/Reference/TypeScript.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index b2181d6a0f6..96409fcf202 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -147,7 +147,7 @@ route-level `request` object. reply.code(200).send('uh-oh'); // it even works for wildcards reply.code(404).send({ error: 'Not found' }); - return `logged in!` + return { success: true } }) ``` @@ -173,7 +173,7 @@ route-level `request` object. }, async (request, reply) => { const customerHeader = request.headers['h-Custom'] // do something with request data - return `logged in!` + return { success: true } }) ``` 7. Build and run and query with the `username` query string option set to From 4ed5beac8438ffaeff64f3f1f98992e6362fe74b Mon Sep 17 00:00:00 2001 From: Prasad Gujar Date: Thu, 4 Sep 2025 17:33:23 +0530 Subject: [PATCH 1141/1295] docs: clarify router performance note (#6306) --- docs/Reference/Routes.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index c7bd2aa01fb..8dc0937e22d 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -636,8 +636,7 @@ has a version set, and will prefer a versioned route to a non-versioned route for the same path. Advanced version ranges and pre-releases currently are not supported. -*Be aware that using this feature will cause a degradation of the overall -performances of the router.* +> **Note:** using this feature can degrade the router’s performance. ```js fastify.route({ From 70b14e92c0b55e8201f5530ba2e6bab4e928c784 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 5 Sep 2025 10:21:50 +0200 Subject: [PATCH 1142/1295] Bumped v5.6.0 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index e97762b3dbb..515e0c60251 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.5.0' +const VERSION = '5.6.0' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 3d23ad31cfc..a409eb61f0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.5.0", + "version": "5.6.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 39f35f9a0596537326b960183b1aa64aca796632 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 9 Sep 2025 23:34:30 +0200 Subject: [PATCH 1143/1295] fix: fix typo of deprecation warning FSTDEP022 (#6313) --- lib/warnings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/warnings.js b/lib/warnings.js index f79f342e48e..1a5092e90a5 100644 --- a/lib/warnings.js +++ b/lib/warnings.js @@ -43,7 +43,7 @@ const FSTSEC001 = createWarning({ const FSTDEP022 = createWarning({ name: 'FastifyWarning', - code: 'FSTDPE022', + code: 'FSTDEP022', message: 'The router options for %s property access is deprecated. Please use "options.routerOptions" instead for accessing router options. The router options will be removed in `fastify@6`.', unlimited: true }) From ebf8730ec5dad1347d5566075cd056c711cf9ea5 Mon Sep 17 00:00:00 2001 From: Emanuel Covelli Date: Mon, 15 Sep 2025 12:37:22 +0200 Subject: [PATCH 1144/1295] docs(decorators): fix TypeScript inconsistency (#6224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: fix TypeScript inconsistency in Decorators.md - Remove TypeScript generic syntax from getDecorator and setDecorator sections - Convert all TypeScript examples to JavaScript for consistency - Simplify complex TypeScript-specific concepts - Maintain JavaScript style used throughout the rest of the document Fixes #6222 * docs: revise approach based on maintainer feedback - Move TypeScript-specific decorator content to TypeScript.md instead of deleting - Keep JavaScript-focused documentation in Decorators.md consistent with rest of document - Add comprehensive TypeScript decorators section with advanced typing examples - Preserve valuable information while addressing the original inconsistency issue Addresses feedback from jean-michelet and jsumners on PR #6224 * fix: resolve markdown linting issues - Fix line length violations in docs/Reference/Decorators.md - Fix line length violations in docs/Reference/TypeScript.md - All lines now comply with 80 character limit - Addresses linting feedback from PR review * fix: address PR feedback from jean-michelet - Remove 'Plugin encapsulation' bullet point from Decorators.md - Consolidate overlapping bullet points into single 'Dependency validation' point - Move TypeScript Decorators section under Plugins section as requested - Change heading level from ### to #### for proper nesting Addresses feedback from PR review comments * fix: address PR feedback from Fdawgs - Remove second-person 'you' usage from TypeScript decorators section - Change **Note**: format to ℹ Note: format - Remove redundant 'Use Cases' sections and consolidate content - Addresses feedback from PR review comments * Update docs/Reference/TypeScript.md Signed-off-by: Frazer Smith * docs: update TypeScript decorators section for enhanced type safety Refactor the documentation for Fastify's `getDecorator` and `setDecorator` methods to emphasize their support for generic type parameters, improving type safety and TypeScript integration. This change clarifies the usage of decorators in Fastify and links to the relevant Decorators reference. * fix: address final review feedback from jean-michelet - Simplify setDecorator description in TypeScript.md - Simplify getDecorator description in TypeScript.md - Streamline setDecorator note in Decorators.md Resolves all unresolved review comments to unblock PR merge. * fix linting --------- Signed-off-by: Frazer Smith Co-authored-by: Frazer Smith Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> Co-authored-by: Aras Abbasi --- docs/Reference/Decorators.md | 205 ++++++----------------------------- docs/Reference/TypeScript.md | 136 +++++++++++++++++++++++ 2 files changed, 172 insertions(+), 169 deletions(-) diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index ee2e2f2b406..4324658db2a 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -366,201 +366,68 @@ Will define the `foo` property on the Fastify instance: console.log(fastify.foo) // 'a getter' ``` -### `getDecorator` API +#### `getDecorator(name)` + -Fastify's `getDecorator` API retrieves an existing decorator from the -Fastify instance, `Request`, or `Reply`. If the decorator is not defined, an -`FST_ERR_DEC_UNDECLARED` error is thrown. +Used to retrieve an existing decorator from the Fastify instance, `Request`, or `Reply`. +If the decorator is not defined, an `FST_ERR_DEC_UNDECLARED` error is thrown. -#### Use cases +```js +// Get a decorator from the Fastify instance +const utility = fastify.getDecorator('utility') -**Early Plugin Dependency Validation** +// Get a decorator from the request object +const user = request.getDecorator('user') -`getDecorator` on Fastify instance verifies that required decorators are -available at registration time. +// Get a decorator from the reply object +const helper = reply.getDecorator('helper') +``` -For example: +The `getDecorator` method is useful for dependency validation - it can be used to +check for required decorators at registration time. If any are missing, it fails +at boot, ensuring dependencies are available during the request lifecycle. ```js fastify.register(async function (fastify) { + // Verify the decorator exists before using it const usersRepository = fastify.getDecorator('usersRepository') fastify.get('/users', async function (request, reply) { - // We are sure `usersRepository` exists at runtime return usersRepository.findAll() }) }) ``` -**Handling Missing Decorators** - -Directly accessing a decorator may lead to unexpected behavior if it is not declared: - -```ts -const user = request.user; -if (user && user.isAdmin) { - // Execute admin tasks. -} -``` - -If `request.user` doesn't exist, then `user` will be set to `undefined`. -This makes it unclear whether the user is unauthenticated or the decorator is missing. - -Using `getDecorator` enforces runtime safety: - -```ts -// If the decorator is missing, an explicit `FST_ERR_DEC_UNDECLARED` -// error is thrown immediately. -const user = request.getDecorator('user'); -if (user && user.isAdmin) { - // Execute admin tasks. -} -``` - -**Alternative to Module Augmentation** - -Decorators are typically typed via module augmentation: - -```ts -declare module 'fastify' { - interface FastifyInstance { - usersRepository: IUsersRepository - } - interface FastifyRequest { - session: ISession - } - interface FastifyReply { - sendSuccess: SendSuccessFn - } -} -``` - -This approach modifies the Fastify instance globally, which may lead to -conflicts and inconsistent behavior in multi-server setups or with plugin -encapsulation. - -Using `getDecorator` allows to limit types scope: - -```ts -serverOne.register(async function (fastify) { - const usersRepository = fastify.getDecorator( - 'usersRepository' - ) - - fastify.decorateRequest('session', null) - fastify.addHook('onRequest', async (req, reply) => { - // Yes, the request object has a setDecorator method. - // More information will be provided soon. - req.setDecorator('session', { user: 'Jean' }) - }) - - fastify.get('/me', (request, reply) => { - const session = request.getDecorator('session') - reply.send(session) - }) -}) - -serverTwo.register(async function (fastify) { - const usersRepository = fastify.getDecorator( - 'usersRepository' - ) - - fastify.decorateReply('sendSuccess', function (data) { - return this.send({ success: true }) - }) - - fastify.get('/success', async (request, reply) => { - const sendSuccess = reply.getDecorator('sendSuccess') - await sendSuccess() - }) -}) -``` +> ℹ️ Note: For TypeScript users, `getDecorator` supports generic type parameters. +> See the [TypeScript documentation](/docs/latest/Reference/TypeScript/) for +> advanced typing examples. -#### Bound functions inference +#### `setDecorator(name, value)` + -To save time, it's common to infer function types instead of -writing them manually: +Used to safely update the value of a `Request` decorator. +If the decorator does not exist, a `FST_ERR_DEC_UNDECLARED` error is thrown. -```ts -function sendSuccess (this: FastifyReply) { - return this.send({ success: true }) -} - -export type SendSuccess = typeof sendSuccess -``` - -However, `getDecorator` returns functions with the `this` -context already **bound**, meaning the `this` parameter disappears -from the function signature. - -To correctly type it, you should use `OmitThisParameter` utility: - -```ts -function sendSuccess (this: FastifyReply) { - return this.send({ success: true }) -} - -type BoundSendSuccess = OmitThisParameter - -fastify.decorateReply('sendSuccess', sendSuccess) -fastify.get('/success', async (request, reply) => { - const sendSuccess = reply.getDecorator('sendSuccess') - await sendSuccess() -}) -``` - -### `Request.setDecorator` Method - -The `setDecorator` method provides a safe and convenient way to -update the value of a `Request` decorator. -If the decorator does not exist, a `FST_ERR_DEC_UNDECLARED` error -is thrown. - -#### Use Cases - -**Runtime Safety** - -A typical way to set a `Request` decorator looks like this: - -```ts -fastify.decorateRequest('user', '') -fastify.addHook('preHandler', async (req, reply) => { - req.user = 'Bob Dylan' -}) -``` - -However, there is no guarantee that the decorator actually exists -unless you manually check beforehand. -Additionally, typos are common, e.g. `account`, `acount`, or `accout`. - -By using `setDecorator`, you are always sure that the decorator exists: +```js +fastify.decorateRequest('user', null) -```ts -fastify.decorateRequest('user', '') fastify.addHook('preHandler', async (req, reply) => { - // Throws FST_ERR_DEC_UNDECLARED if the decorator does not exist - req.setDecorator('user-with-typo', 'Bob Dylan') + // Safely set the decorator value + req.setDecorator('user', 'Bob Dylan') }) ``` ---- - -**Type Safety** +The `setDecorator` method provides runtime safety by ensuring the decorator exists +before setting its value, preventing errors from typos in decorator names. -If the `FastifyRequest` interface does not declare the decorator, you -would typically need to use type assertions: - -```ts +```js +fastify.decorateRequest('account', null) fastify.addHook('preHandler', async (req, reply) => { - (req as typeof req & { user: string }).user = 'Bob Dylan' + // This will throw FST_ERR_DEC_UNDECLARED due to typo in decorator name + req.setDecorator('acount', { id: 123 }) }) ``` -The `setDecorator` method eliminates the need for explicit type -assertions while allowing type safety: - -```ts -fastify.addHook('preHandler', async (req, reply) => { - req.setDecorator('user', 'Bob Dylan') -}) -``` +> ℹ️ Note: For TypeScript users, see the +> [TypeScript documentation](/docs/latest/Reference/TypeScript/) for advanced +> typing examples using `setDecorator`. diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 96409fcf202..e647a860759 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -687,6 +687,142 @@ Or even explicit config on tsconfig } ``` +#### `getDecorator` + +Fastify's `getDecorator` method retrieves decorators with enhanced type safety. + +The `getDecorator` method supports generic type parameters for enhanced type safety: + +```typescript +// Type-safe decorator retrieval +const usersRepository = fastify.getDecorator('usersRepository') +const session = request.getDecorator('session') +const sendSuccess = reply.getDecorator('sendSuccess') +``` + +**Alternative to Module Augmentation** + +Decorators are typically typed via module augmentation: + +```typescript +declare module 'fastify' { + interface FastifyInstance { + usersRepository: IUsersRepository + } + interface FastifyRequest { + session: ISession + } + interface FastifyReply { + sendSuccess: SendSuccessFn + } +} +``` + +This approach modifies the Fastify instance globally, which may lead to conflicts +and inconsistent behavior in multi-server setups or with plugin encapsulation. + +Using `getDecorator` allows limiting types scope: + +```typescript +serverOne.register(async function (fastify) { + const usersRepository = fastify.getDecorator( + 'usersRepository' + ) + + fastify.decorateRequest('session', null) + fastify.addHook('onRequest', async (req, reply) => { + req.setDecorator('session', { user: 'Jean' }) + }) + + fastify.get('/me', (request, reply) => { + const session = request.getDecorator('session') + reply.send(session) + }) +}) + +serverTwo.register(async function (fastify) { + const usersRepository = fastify.getDecorator( + 'usersRepository' + ) + + fastify.decorateReply('sendSuccess', function (data) { + return this.send({ success: true }) + }) + + fastify.get('/success', async (request, reply) => { + const sendSuccess = reply.getDecorator('sendSuccess') + await sendSuccess() + }) +}) +``` + +**Bound Functions Inference** + +To save time, it is common to infer function types instead of writing them manually: + +```typescript +function sendSuccess (this: FastifyReply) { + return this.send({ success: true }) +} + +export type SendSuccess = typeof sendSuccess +``` + +However, `getDecorator` returns functions with the `this` context already **bound**, +meaning the `this` parameter disappears from the function signature. + +To correctly type it, use the `OmitThisParameter` utility: + +```typescript +function sendSuccess (this: FastifyReply) { + return this.send({ success: true }) +} + +type BoundSendSuccess = OmitThisParameter + +fastify.decorateReply('sendSuccess', sendSuccess) +fastify.get('/success', async (request, reply) => { + const sendSuccess = reply.getDecorator('sendSuccess') + await sendSuccess() +}) +``` + +#### `setDecorator` + +Fastify's `setDecorator` method provides enhanced type safety for updating request +decorators. + +The `setDecorator` method provides enhanced type safety for updating request +decorators: + +```typescript +fastify.decorateRequest('user', '') +fastify.addHook('preHandler', async (req, reply) => { + // Type-safe decorator setting + req.setDecorator('user', 'Bob Dylan') +}) +``` + +**Type Safety Benefits** + +If the `FastifyRequest` interface does not declare the decorator, type assertions +are typically needed: + +```typescript +fastify.addHook('preHandler', async (req, reply) => { + (req as typeof req & { user: string }).user = 'Bob Dylan' +}) +``` + +The `setDecorator` method eliminates the need for explicit type assertions +while providing type safety: + +```typescript +fastify.addHook('preHandler', async (req, reply) => { + req.setDecorator('user', 'Bob Dylan') +}) +``` + ## Code Completion In Vanilla JavaScript Vanilla JavaScript can use the published types to provide code completion (e.g. From d1af93411c488b34c61b6d4a7fe8f4d351ab611b Mon Sep 17 00:00:00 2001 From: Andreas Deininger Date: Wed, 17 Sep 2025 12:47:43 +0200 Subject: [PATCH 1145/1295] chore: fix typos (#6316) --- docs/Reference/Reply.md | 2 +- docs/Reference/Server.md | 2 +- test/decorator-namespace.test._js_ | 2 +- test/set-error-handler.test.js | 2 +- test/skip-reply-send.test.js | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index c456b60011a..679480e4fc1 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -369,7 +369,7 @@ charset must be set explicitly. By calling this function using a provided `schema` or `httpStatus`, -and the optional `contentType`, it will return a `serialzation` function +and the optional `contentType`, it will return a `serialization` function that can be used to serialize diverse inputs. It returns `undefined` if no serialization function was found using either of the provided inputs. diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 73bd761fb9f..528d84bcf79 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -966,7 +966,7 @@ Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which suppor separating the path and query string with a `;` character (code 59), e.g. `/dev;foo=bar`. This decision originated from [delvedor/find-my-way#76] (https://github.com/delvedor/find-my-way/issues/76). Thus, this option will support -backwards compatiblilty for the need to split on `;`. To enable support for splitting +backwards compatibility for the need to split on `;`. To enable support for splitting on `;` set `useSemicolonDelimiter` to `true`. ```js diff --git a/test/decorator-namespace.test._js_ b/test/decorator-namespace.test._js_ index d23783c2549..ed35e7b90a5 100644 --- a/test/decorator-namespace.test._js_ +++ b/test/decorator-namespace.test._js_ @@ -20,7 +20,7 @@ test('plugin namespace', async t => { // ! the plugin does not know about the namespace reply.send({ utility: app.utility() }) }) - }, { namepace: 'foo' }) + }, { namespace: 'foo' }) // ! but outside the plugin the decorator would be app.foo.utility() t.assert.ok(app.foo.utility) diff --git a/test/set-error-handler.test.js b/test/set-error-handler.test.js index 1d6b5f62e93..9fbe9b27626 100644 --- a/test/set-error-handler.test.js +++ b/test/set-error-handler.test.js @@ -24,7 +24,7 @@ test('setErrorHandler can be set independently in parent and child scopes', asyn }) }) -test('setErrorHandler can be overriden if allowErrorHandlerOverride is set to true', async t => { +test('setErrorHandler can be overridden if allowErrorHandlerOverride is set to true', async t => { t.plan(2) const fastify = Fastify() diff --git a/test/skip-reply-send.test.js b/test/skip-reply-send.test.js index a919aa6a87c..202a758f006 100644 --- a/test/skip-reply-send.test.js +++ b/test/skip-reply-send.test.js @@ -244,12 +244,12 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { if (hookOrHandler === 'handler') { app.get('/', (req, reply) => { reply.hijack() - throw new Error('This wil be skipped') + throw new Error('This will be skipped') }) } else { app.addHook(hookOrHandler, async (req, reply) => { reply.hijack() - throw new Error('This wil be skipped') + throw new Error('This will be skipped') }) app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) } From a4fe041500e62de1839401dbad134ccbb3150c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulises=20Gasc=C3=B3n?= Date: Thu, 18 Sep 2025 10:40:15 +0200 Subject: [PATCH 1146/1295] docs(security): add security escalation policy (#6315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add security escalation policy Signed-off-by: Ulises Gascón * Update SECURITY.md Co-authored-by: Aras Abbasi Signed-off-by: Ulises Gascón * chore: relocate text * Update SECURITY.md Co-authored-by: Frazer Smith Signed-off-by: Ulises Gascón * chore: lint files --------- Signed-off-by: Ulises Gascón Co-authored-by: Aras Abbasi Co-authored-by: Frazer Smith --- SECURITY.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index 48b2f20dc81..75699ac1064 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -110,6 +110,18 @@ Within HackerOne, this is handled through a "public disclosure request". Reference: [HackerOne: Disclosure](https://docs.hackerone.com/hackers/disclosure.html) +### Secondary Contact + +If you do not receive an acknowledgment of your report within 6 business days, +or if you cannot find a private security contact for the project, you may +contact the OpenJS Foundation CNA at `security@lists.openjsf.org` for +assistance. + +The CNA can help ensure your report is properly acknowledged, assist with +coordinating disclosure timelines, and assign CVEs when necessary. This is a +support mechanism to ensure security reports are handled appropriately across +all OpenJS Foundation projects. + ## The Fastify Security team The core team is responsible for the management of the security program and From 39d2e10048a5fd25d0f85b025815e7fe31df6142 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 18 Sep 2025 18:13:57 +0200 Subject: [PATCH 1147/1295] chore(gha): remove benchmark github workflows (#6322) * chore(gha): remove benchmark workflows Signed-off-by: Manuel Spigolon * chore(gha): remove benchmark workflows Signed-off-by: Manuel Spigolon --------- Signed-off-by: Manuel Spigolon --- .github/workflows/benchmark-parser.yml | 96 --------------------- .github/workflows/benchmark.yml | 115 ------------------------- 2 files changed, 211 deletions(-) delete mode 100644 .github/workflows/benchmark-parser.yml delete mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark-parser.yml b/.github/workflows/benchmark-parser.yml deleted file mode 100644 index e6a079f3e8f..00000000000 --- a/.github/workflows/benchmark-parser.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Benchmark Parser - -on: - pull_request_target: - types: [labeled] - -permissions: - contents: read - -jobs: - benchmark: - if: ${{ github.event.label.name == 'benchmark' }} - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} - PR-BENCH-22: ${{ steps.benchmark-pr.outputs.BENCH_RESULT22 }} - PR-BENCH-24: ${{ steps.benchmark-pr.outputs.BENCH_RESULT24 }} - MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} - MAIN-BENCH-22: ${{ steps.benchmark-main.outputs.BENCH_RESULT22 }} - MAIN-BENCH-24: ${{ steps.benchmark-main.outputs.BENCH_RESULT24 }} - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - ref: ${{github.event.pull_request.head.sha}} - repository: ${{github.event.pull_request.head.repo.full_name}} - - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - check-latest: true - - - name: Install - run: | - npm install --ignore-scripts - - - name: Run benchmark - id: benchmark-pr - run: | - npm run --silent benchmark:parser > ./bench-result.md - result=$(awk '/requests in/' ./bench-result.md) - echo 'BENCH_RESULT${{matrix.node-version}}<> $GITHUB_OUTPUT - echo "$result" >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - # main benchmark - - uses: actions/checkout@v4 - with: - persist-credentials: false - ref: 'main' - - - name: Install - run: | - npm install --ignore-scripts - - - name: Run benchmark - id: benchmark-main - run: | - npm run --silent benchmark:parser > ./bench-result.md - result=$(awk '/requests in/' ./bench-result.md) - echo 'BENCH_RESULT${{matrix.node-version}}<> $GITHUB_OUTPUT - echo "$result" >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - output-benchmark: - needs: - - benchmark - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Comment PR - uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - message: | - **Node**: 20 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} - - --- - - **Node**: 22 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-22 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-22 }} - - --- - - **Node**: 24 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-24 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-24 }} \ No newline at end of file diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index d8dbd7c142b..00000000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Benchmark - -on: - pull_request_target: - types: [labeled] - -permissions: - contents: read - -jobs: - benchmark: - if: ${{ github.event.label.name == 'benchmark' }} - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - PR-BENCH-20: ${{ steps.benchmark-pr.outputs.BENCH_RESULT20 }} - PR-BENCH-22: ${{ steps.benchmark-pr.outputs.BENCH_RESULT22 }} - PR-BENCH-24: ${{ steps.benchmark-pr.outputs.BENCH_RESULT24 }} - MAIN-BENCH-20: ${{ steps.benchmark-main.outputs.BENCH_RESULT20 }} - MAIN-BENCH-22: ${{ steps.benchmark-main.outputs.BENCH_RESULT22 }} - MAIN-BENCH-24: ${{ steps.benchmark-main.outputs.BENCH_RESULT24 }} - strategy: - matrix: - node-version: [20, 22, 24] - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - ref: ${{github.event.pull_request.head.sha}} - repository: ${{github.event.pull_request.head.repo.full_name}} - - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - check-latest: true - - - name: Install - run: | - npm install --ignore-scripts - - - name: Run benchmark - id: benchmark-pr - run: | - npm run --silent benchmark > ./bench-result.md - result=$(awk '/requests in/' ./bench-result.md) - echo 'BENCH_RESULT${{matrix.node-version}}<> $GITHUB_OUTPUT - echo "$result" >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - # main benchmark - - uses: actions/checkout@v4 - with: - persist-credentials: false - ref: 'main' - - - name: Install - run: | - npm install --ignore-scripts - - - name: Run benchmark - id: benchmark-main - run: | - npm run --silent benchmark > ./bench-result.md - result=$(awk '/requests in/' ./bench-result.md) - echo 'BENCH_RESULT${{matrix.node-version}}<> $GITHUB_OUTPUT - echo "$result" >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - output-benchmark: - needs: - - benchmark - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Comment PR - uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - message: | - **Node**: 20 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-20 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-20 }} - - --- - - **Node**: 22 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-22 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-22 }} - - --- - - **Node**: 24 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-24 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-24 }} - remove-label: - if: ${{ github.event.label.name == 'benchmark' }} - needs: - - benchmark - - output-benchmark - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Remove benchmark label - uses: octokit/request-action@v2.x - id: remove-label - with: - route: DELETE /repos/{repo}/issues/{issue_number}/labels/{name} - repo: ${{ github.repository }} - issue_number: ${{ github.event.pull_request.number }} - name: benchmark - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 9a67d3a4875e2c078f9f710035a7625819853677 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 21 Sep 2025 10:45:26 +0200 Subject: [PATCH 1148/1295] chore(sponsor): add lambdatest (#6324) --- SPONSORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SPONSORS.md b/SPONSORS.md index dad01462b5f..a215ead99e4 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -17,6 +17,7 @@ _Be the first!_ - [Val Town, Inc.](https://opencollective.com/valtown) - [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) - [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship) +- [Lambdatest](https://www.lambdatest.com/) ## Tier 2 From 80ef70c2541cac5cacf7e288c2db35e3763e0c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Mon, 22 Sep 2025 14:18:04 +0200 Subject: [PATCH 1149/1295] fix: (types) allow FastifySchemaValidationError[] as an error (#6326) --- test/types/schema.test-d.ts | 21 +++++++++++++++++++++ types/schema.d.ts | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/test/types/schema.test-d.ts b/test/types/schema.test-d.ts index 07091abac86..5d3934d6fe2 100644 --- a/test/types/schema.test-d.ts +++ b/test/types/schema.test-d.ts @@ -72,6 +72,27 @@ expectAssignable(server.post('/test', { } }, async req => req.body)) +expectAssignable(server.post('/test', { + validatorCompiler: ({ schema }) => { + return data => { + if (!data || data.constructor !== Object) { + return { + error: [ + { + keyword: 'type', + instancePath: '', + schemaPath: '#/type', + params: { type: 'object' }, + message: 'value is not an object' + } + ] + } + } + return { value: data } + } + } +}, async req => req.body)) + expectAssignable(server.setValidatorCompiler }>( function ({ schema }) { return new Ajv().compile(schema) diff --git a/types/schema.d.ts b/types/schema.d.ts index ebed58500a1..40a8dd1cb06 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -33,7 +33,7 @@ export interface FastifySchemaValidationError { } export interface FastifyValidationResult { - (data: any): boolean | SafePromiseLike | { error?: Error, value?: any } + (data: any): boolean | SafePromiseLike | { error?: Error | FastifySchemaValidationError[], value?: any } errors?: FastifySchemaValidationError[] | null; } From 4e5a3631ec731fa9b82689e140b399f21a8b96fd Mon Sep 17 00:00:00 2001 From: David van der Sluijs Date: Mon, 22 Sep 2025 14:18:35 +0200 Subject: [PATCH 1150/1295] fix: correct session.close() context in http2 timeout handler (#6327) Replace function declaration with arrow function to properly reference the session object when closing timed-out HTTP/2 sessions. Signed-off-by: David van der Sluijs --- lib/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/server.js b/lib/server.js index 486de0bf072..205ea1e2beb 100644 --- a/lib/server.js +++ b/lib/server.js @@ -283,8 +283,8 @@ function getServerInstance (options, httpHandler) { if (options.http2) { const server = typeof httpsOptions === 'object' ? http2.createSecureServer(httpsOptions, httpHandler) : http2.createServer(options.http, httpHandler) - server.on('session', (session) => session.setTimeout(options.http2SessionTimeout, function closeSession () { - this.close() + server.on('session', (session) => session.setTimeout(options.http2SessionTimeout, () => { + session.close() })) server.setTimeout(options.connectionTimeout) From 36498f85a91cebc107282fe8128e08e48567bde0 Mon Sep 17 00:00:00 2001 From: Konstantin Korotchenkov <72752001+kostyak127@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:19:05 +0500 Subject: [PATCH 1151/1295] fix: close http2 sessions on close server (#6137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: close http2 sessions on close server * fix * add some session events handlers + add to docs info about closing sessions * save sessions in server[kHttp2ServerSessions] and add tests on sessions set --------- Co-authored-by: Aras Abbasi Co-authored-by: Carlos Fuentes Co-authored-by: Gürgün Dayıoğlu --- docs/Reference/Server.md | 6 +++ fastify.js | 5 +++ lib/server.js | 55 +++++++++++++++++++++++- lib/symbols.js | 1 + test/http2/closing.test.js | 88 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 528d84bcf79..599cbad6d9f 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -170,6 +170,12 @@ When set to `true`, upon [`close`](#close) the server will iterate the current persistent connections and [destroy their sockets](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketdestroyerror). +When used with HTTP/2 server, it will also close all active HTTP/2 sessions. + +> ℹ️ Note: +> Since Node.js v24 active sessions are closed by default + + > ⚠ Warning: > Connections are not inspected to determine if requests have > been completed. diff --git a/fastify.js b/fastify.js index 515e0c60251..680cb2ec7d3 100644 --- a/fastify.js +++ b/fastify.js @@ -194,6 +194,7 @@ function fastify (options) { const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function' const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function' + const serverHasCloseHttp2Sessions = typeof server.closeHttp2Sessions === 'function' let forceCloseConnections = options.forceCloseConnections if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) { @@ -491,6 +492,10 @@ function fastify (options) { } } + if (serverHasCloseHttp2Sessions) { + instance.server.closeHttp2Sessions() + } + // No new TCP connections are accepted. // We must call close on the server even if we are not listening // otherwise memory will be leaked. diff --git a/lib/server.js b/lib/server.js index 205ea1e2beb..282c3289922 100644 --- a/lib/server.js +++ b/lib/server.js @@ -6,7 +6,7 @@ const http2 = require('node:http2') const dns = require('node:dns') const os = require('node:os') -const { kState, kOptions, kServerBindings } = require('./symbols') +const { kState, kOptions, kServerBindings, kHttp2ServerSessions } = require('./symbols') const { FSTWRN003 } = require('./warnings') const { onListenHookRunner } = require('./hooks') const { @@ -175,6 +175,9 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections === true) { secondaryServer.closeAllConnections() } + if (typeof secondaryServer.closeHttp2Sessions === 'function') { + secondaryServer.closeHttp2Sessions() + } } secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade')) @@ -287,6 +290,12 @@ function getServerInstance (options, httpHandler) { session.close() })) + // This is only needed for Node.js versions < 24.0.0 since Node.js added native + // closeAllSessions() on server.close() support for HTTP/2 servers in v24.0.0 + if (options.forceCloseConnections === true) { + server.closeHttp2Sessions = createCloseHttp2SessionsByHttp2Server(server) + } + server.setTimeout(options.connectionTimeout) return server @@ -353,3 +362,47 @@ function logServerAddress (server, listenTextResolver) { } return addresses[0] } + +/** + * @param {http2.Http2Server} http2Server + * @returns {() => void} + */ +function createCloseHttp2SessionsByHttp2Server (http2Server) { + /** + * @type {Set} + */ + http2Server[kHttp2ServerSessions] = new Set() + + http2Server.on('session', function (session) { + session.once('connect', function () { + http2Server[kHttp2ServerSessions].add(session) + }) + + session.once('close', function () { + http2Server[kHttp2ServerSessions].delete(session) + }) + + session.once('frameError', function (type, code, streamId) { + if (streamId === 0) { + // The stream ID is 0, which means that the error is related to the session itself. + // If the event is not associated with a stream, the Http2Session will be shut down immediately + http2Server[kHttp2ServerSessions].delete(session) + } + }) + + session.once('goaway', function () { + // The Http2Session instance will be shut down automatically when the 'goaway' event is emitted. + http2Server[kHttp2ServerSessions].delete(session) + }) + }) + + return function closeHttp2Sessions () { + if (http2Server[kHttp2ServerSessions].size === 0) { + return + } + + for (const session of http2Server[kHttp2ServerSessions]) { + session.close() + } + } +} diff --git a/lib/symbols.js b/lib/symbols.js index cd7da0fb68f..510ac39c7c1 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -17,6 +17,7 @@ const keys = { kPluginNameChain: Symbol('fastify.pluginNameChain'), kRouteContext: Symbol('fastify.context'), kGenReqId: Symbol('fastify.genReqId'), + kHttp2ServerSessions: Symbol('fastify.http2ServerSessions'), // Schema kSchemaController: Symbol('fastify.schemaController'), kSchemaHeaders: Symbol('headers-schema'), diff --git a/test/http2/closing.test.js b/test/http2/closing.test.js index 2d14090d6df..8253b59fedc 100644 --- a/test/http2/closing.test.js +++ b/test/http2/closing.test.js @@ -8,6 +8,7 @@ const connect = promisify(http2.connect) const { once } = require('node:events') const { buildCertificate } = require('../build-certificate') const { getServerUrl } = require('../helper') +const { kHttp2ServerSessions } = require('../../lib/symbols') test.before(buildCertificate) @@ -180,3 +181,90 @@ test('http/2 server side session emits a timeout event', async t => { await p await fastify.close() }) + +test('http/2 sessions closed after closing server', async t => { + t.plan(1) + const fastify = Fastify({ + http2: true, + http2SessionTimeout: 100 + }) + await fastify.listen() + const url = getServerUrl(fastify) + const waitSessionConnect = once(fastify.server, 'session') + const session = http2.connect(url) + await once(session, 'connect') + await waitSessionConnect + const waitSessionClosed = once(session, 'close') + await fastify.close() + await waitSessionClosed + t.assert.strictEqual(session.closed, true) +}) + +test('http/2 sessions should be closed when setting forceClosedConnections to true', async t => { + t.plan(2) + const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true }) + fastify.get('/', () => 'hello world') + await fastify.listen() + const client = await connect(getServerUrl(fastify)) + const req = client.request({ + [http2.HTTP2_HEADER_PATH]: '/', + [http2.HTTP2_HEADER_METHOD]: 'GET' + }) + await once(req, 'response') + fastify.close() + const r2 = client.request({ + [http2.HTTP2_HEADER_PATH]: '/', + [http2.TTP2_HEADER_METHOD]: 'GET' + }) + r2.on('error', (err) => { + t.assert.strictEqual(err.toString(), 'Error [ERR_HTTP2_STREAM_ERROR]: Stream closed with error code NGHTTP2_REFUSED_STREAM') + }) + await once(r2, 'error') + r2.end() + t.assert.strictEqual(client.closed, true) + client.destroy() +}) + +test('http/2 sessions should be removed from server[kHttp2ServerSessions] Set on goaway', async t => { + t.plan(2) + const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true }) + await fastify.listen() + const waitSession = once(fastify.server, 'session') + const client = http2.connect(getServerUrl(fastify)) + const [session] = await waitSession + const waitGoaway = once(session, 'goaway') + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1) + client.goaway() + await waitGoaway + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 0) + client.destroy() + await fastify.close() +}) + +test('http/2 sessions should be removed from server[kHttp2ServerSessions] Set on frameError', async t => { + t.plan(2) + const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true }) + await fastify.listen() + const waitSession = once(fastify.server, 'session') + const client = http2.connect(getServerUrl(fastify)) + const [session] = await waitSession + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1) + session.emit('frameError', 0, 0, 0) + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 0) + client.destroy() + await fastify.close() +}) + +test('http/2 sessions should not be removed from server[kHttp2ServerSessions] from Set if stream id passed on frameError', async t => { + t.plan(2) + const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true }) + await fastify.listen() + const waitSession = once(fastify.server, 'session') + const client = http2.connect(getServerUrl(fastify)) + const [session] = await waitSession + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1) + session.emit('frameError', 0, 0, 1) + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1) + client.destroy() + await fastify.close() +}) From 91414fecaa0ad383c7f97c91f86bee87bf4f06c0 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 22 Sep 2025 18:00:40 +0200 Subject: [PATCH 1152/1295] Bumped v5.6.1 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 680cb2ec7d3..a51de41acb7 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.6.0' +const VERSION = '5.6.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index a409eb61f0b..519cf287b0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.6.0", + "version": "5.6.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 5bcee1d7ab3bd7109488df50054a185c27fcf486 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:19:04 +0200 Subject: [PATCH 1153/1295] refactor: rename source file names with kebab-case (#6331) --- build/build-validation.js | 2 +- eslint.config.js | 2 +- fastify.d.ts | 2 +- fastify.js | 12 ++++++------ lib/{configValidator.js => config-validator.js} | 0 lib/{contentTypeParser.js => content-type-parser.js} | 0 lib/error-handler.js | 2 +- lib/{fourOhFour.js => four-oh-four.js} | 2 +- lib/{handleRequest.js => handle-request.js} | 2 +- lib/{headRoute.js => head-route.js} | 0 ...figValidation.js => initial-config-validation.js} | 2 +- lib/{pluginOverride.js => plugin-override.js} | 4 ++-- lib/{pluginUtils.js => plugin-utils.js} | 4 ++-- lib/reply.js | 2 +- lib/{reqIdGenFactory.js => req-id-gen-factory.js} | 0 lib/route.js | 6 +++--- lib/{wrapThenable.js => wrap-thenable.js} | 0 package.json | 2 +- .../diagnostics-channel/error-before-handler.test.js | 2 +- test/internals/content-type-parser.test.js | 4 ++-- test/internals/handle-request.test.js | 4 ++-- test/internals/initial-config.test.js | 2 +- test/internals/plugin.test.js | 4 ++-- test/internals/req-id-gen-factory.test.js | 2 +- test/stream.5.test.js | 6 +++--- test/wrap-thenable.test.js | 2 +- types/{serverFactory.d.ts => server-factory.d.ts} | 0 27 files changed, 35 insertions(+), 35 deletions(-) rename lib/{configValidator.js => config-validator.js} (100%) rename lib/{contentTypeParser.js => content-type-parser.js} (100%) rename lib/{fourOhFour.js => four-oh-four.js} (99%) rename lib/{handleRequest.js => handle-request.js} (98%) rename lib/{headRoute.js => head-route.js} (100%) rename lib/{initialConfigValidation.js => initial-config-validation.js} (95%) rename lib/{pluginOverride.js => plugin-override.js} (96%) rename lib/{pluginUtils.js => plugin-utils.js} (97%) rename lib/{reqIdGenFactory.js => req-id-gen-factory.js} (100%) rename lib/{wrapThenable.js => wrap-thenable.js} (100%) rename types/{serverFactory.d.ts => server-factory.d.ts} (100%) diff --git a/build/build-validation.js b/build/build-validation.js index ae3a881d862..0b6d4c8b427 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -16,7 +16,7 @@ module.exports.defaultInitOptions = ${JSON.stringify(defaultInitOptions)} /* c8 ignore stop */ ` - const file = path.join(__dirname, '..', 'lib', 'configValidator.js') + const file = path.join(__dirname, '..', 'lib', 'config-validator.js') fs.writeFileSync(file, moduleCode) console.log(`Saved ${file} file successfully`) } diff --git a/eslint.config.js b/eslint.config.js index cc4570e2960..2d0202b08d6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,7 +4,7 @@ const neostandard = require('neostandard') module.exports = [ ...neostandard({ ignores: [ - 'lib/configValidator.js', + 'lib/config-validator.js', 'lib/error-serializer.js', 'test/same-shape.test.js', 'test/types/import.js' diff --git a/fastify.d.ts b/fastify.d.ts index 4c0fe0ca181..65891df118e 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -29,7 +29,7 @@ import { FastifyReply } from './types/reply' import { FastifyRequest, RequestGenericInterface } from './types/request' import { RouteGenericInterface, RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route' import { FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema' -import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory' +import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/server-factory' import { FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike } from './types/type-provider' import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './types/utils' diff --git a/fastify.js b/fastify.js index a51de41acb7..d798feae54a 100644 --- a/fastify.js +++ b/fastify.js @@ -40,16 +40,16 @@ const Reply = require('./lib/reply') const Request = require('./lib/request') const Context = require('./lib/context.js') const decorator = require('./lib/decorate') -const ContentTypeParser = require('./lib/contentTypeParser') +const ContentTypeParser = require('./lib/content-type-parser.js') const SchemaController = require('./lib/schema-controller') const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks') const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory') -const pluginUtils = require('./lib/pluginUtils') -const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory') +const pluginUtils = require('./lib/plugin-utils.js') +const { getGenReqId, reqIdGenFactory } = require('./lib/req-id-gen-factory.js') const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route') -const build404 = require('./lib/fourOhFour') -const getSecuredInitialConfig = require('./lib/initialConfigValidation') -const override = require('./lib/pluginOverride') +const build404 = require('./lib/four-oh-four') +const getSecuredInitialConfig = require('./lib/initial-config-validation.js') +const override = require('./lib/plugin-override') const noopSet = require('./lib/noop-set') const { appendStackTrace, diff --git a/lib/configValidator.js b/lib/config-validator.js similarity index 100% rename from lib/configValidator.js rename to lib/config-validator.js diff --git a/lib/contentTypeParser.js b/lib/content-type-parser.js similarity index 100% rename from lib/contentTypeParser.js rename to lib/content-type-parser.js diff --git a/lib/error-handler.js b/lib/error-handler.js index c5ebf629a2f..25bb90cb97f 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -1,7 +1,7 @@ 'use strict' const statusCodes = require('node:http').STATUS_CODES -const wrapThenable = require('./wrapThenable') +const wrapThenable = require('./wrap-thenable.js') const { kReplyHeaders, kReplyNextErrorHandler, diff --git a/lib/fourOhFour.js b/lib/four-oh-four.js similarity index 99% rename from lib/fourOhFour.js rename to lib/four-oh-four.js index 41e77dfeeef..917bb8e59a0 100644 --- a/lib/fourOhFour.js +++ b/lib/four-oh-four.js @@ -19,7 +19,7 @@ const { FST_ERR_NOT_FOUND } = require('./errors') const { createChildLogger } = require('./logger-factory') -const { getGenReqId } = require('./reqIdGenFactory.js') +const { getGenReqId } = require('./req-id-gen-factory.js') /** * Each fastify instance have a: diff --git a/lib/handleRequest.js b/lib/handle-request.js similarity index 98% rename from lib/handleRequest.js rename to lib/handle-request.js index be8b43eacd7..fe0977f95cc 100644 --- a/lib/handleRequest.js +++ b/lib/handle-request.js @@ -3,7 +3,7 @@ const diagnostics = require('node:diagnostics_channel') const { validate: validateSchema } = require('./validation') const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') -const wrapThenable = require('./wrapThenable') +const wrapThenable = require('./wrap-thenable') const { kReplyIsError, kRouteContext, diff --git a/lib/headRoute.js b/lib/head-route.js similarity index 100% rename from lib/headRoute.js rename to lib/head-route.js diff --git a/lib/initialConfigValidation.js b/lib/initial-config-validation.js similarity index 95% rename from lib/initialConfigValidation.js rename to lib/initial-config-validation.js index c56d8c54e34..d8f3d6f1018 100644 --- a/lib/initialConfigValidation.js +++ b/lib/initial-config-validation.js @@ -1,6 +1,6 @@ 'use strict' -const validate = require('./configValidator') +const validate = require('./config-validator') const deepClone = require('rfdc')({ circles: true, proto: false }) const { FST_ERR_INIT_OPTS_INVALID } = require('./errors') diff --git a/lib/pluginOverride.js b/lib/plugin-override.js similarity index 96% rename from lib/pluginOverride.js rename to lib/plugin-override.js index 3a98e352c83..4eef32577ee 100644 --- a/lib/pluginOverride.js +++ b/lib/plugin-override.js @@ -19,9 +19,9 @@ const { const Reply = require('./reply') const Request = require('./request') const SchemaController = require('./schema-controller') -const ContentTypeParser = require('./contentTypeParser') +const ContentTypeParser = require('./content-type-parser.js') const { buildHooks } = require('./hooks') -const pluginUtils = require('./pluginUtils') +const pluginUtils = require('./plugin-utils.js') // Function that runs the encapsulation magic. // Everything that need to be encapsulated must be handled in this function. diff --git a/lib/pluginUtils.js b/lib/plugin-utils.js similarity index 97% rename from lib/pluginUtils.js rename to lib/plugin-utils.js index 577296904fb..d18d94e58a5 100644 --- a/lib/pluginUtils.js +++ b/lib/plugin-utils.js @@ -6,12 +6,12 @@ const kRegisteredPlugins = Symbol.for('registered-plugin') const { kTestInternals } = require('./symbols.js') -const { exist, existReply, existRequest } = require('./decorate') +const { exist, existReply, existRequest } = require('./decorate.js') const { FST_ERR_PLUGIN_VERSION_MISMATCH, FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE, FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER -} = require('./errors') +} = require('./errors.js') const rcRegex = /-(?:rc|pre|alpha).+$/u diff --git a/lib/reply.js b/lib/reply.js index 3bc7c722cf5..0d057acf6a9 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -31,7 +31,7 @@ const { preSerializationHookRunner } = require('./hooks') -const internals = require('./handleRequest')[Symbol.for('internals')] +const internals = require('./handle-request.js')[Symbol.for('internals')] const loggerUtils = require('./logger-factory') const now = loggerUtils.now const { handleError } = require('./error-handler') diff --git a/lib/reqIdGenFactory.js b/lib/req-id-gen-factory.js similarity index 100% rename from lib/reqIdGenFactory.js rename to lib/req-id-gen-factory.js diff --git a/lib/route.js b/lib/route.js index d5e5d832a81..83813220f94 100644 --- a/lib/route.js +++ b/lib/route.js @@ -2,10 +2,10 @@ const FindMyWay = require('find-my-way') const Context = require('./context') -const handleRequest = require('./handleRequest') +const handleRequest = require('./handle-request.js') const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks') const { normalizeSchema } = require('./schemas') -const { parseHeadOnSendHandlers } = require('./headRoute') +const { parseHeadOnSendHandlers } = require('./head-route.js') const { compileSchemasForValidation, @@ -52,7 +52,7 @@ const { } = require('./symbols.js') const { buildErrorHandler } = require('./error-handler') const { createChildLogger } = require('./logger-factory.js') -const { getGenReqId } = require('./reqIdGenFactory.js') +const { getGenReqId } = require('./req-id-gen-factory.js') const routerKeys = [ 'allowUnsafeRegex', diff --git a/lib/wrapThenable.js b/lib/wrap-thenable.js similarity index 100% rename from lib/wrapThenable.js rename to lib/wrap-thenable.js diff --git a/package.json b/package.json index 519cf287b0a..5d3b74b0850 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run unit && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", - "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", + "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/config-validator.js", "test:typescript": "tsc test/types/import.ts --target es2022 --moduleResolution node16 --module node16 --noEmit && tsd", "test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse", "unit": "borp", diff --git a/test/diagnostics-channel/error-before-handler.test.js b/test/diagnostics-channel/error-before-handler.test.js index 4d15c3dfdfd..edac466b706 100644 --- a/test/diagnostics-channel/error-before-handler.test.js +++ b/test/diagnostics-channel/error-before-handler.test.js @@ -6,7 +6,7 @@ require('../../lib/hooks').onSendHookRunner = function Stub () {} const Request = require('../../lib/request') const Reply = require('../../lib/reply') const symbols = require('../../lib/symbols.js') -const { preHandlerCallback } = require('../../lib/handleRequest')[Symbol.for('internals')] +const { preHandlerCallback } = require('../../lib/handle-request')[Symbol.for('internals')] test('diagnostics channel handles an error before calling context handler', t => { t.plan(3) diff --git a/test/internals/content-type-parser.test.js b/test/internals/content-type-parser.test.js index 75bfd3f79c9..aadff2dedc1 100644 --- a/test/internals/content-type-parser.test.js +++ b/test/internals/content-type-parser.test.js @@ -10,7 +10,7 @@ const Reply = require('../../lib/reply') test('rawBody function', t => { t.plan(2) - const internals = require('../../lib/contentTypeParser')[kTestInternals] + const internals = require('../../lib/content-type-parser')[kTestInternals] const body = Buffer.from('你好 世界') const parser = { asString: true, @@ -60,7 +60,7 @@ test('rawBody function', t => { test('Should support Webpack and faux modules', t => { t.plan(2) - const internals = proxyquire('../../lib/contentTypeParser', { + const internals = proxyquire('../../lib/content-type-parser', { 'toad-cache': { default: () => { } } })[kTestInternals] diff --git a/test/internals/handle-request.test.js b/test/internals/handle-request.test.js index a1752123adb..2a6962d7b24 100644 --- a/test/internals/handle-request.test.js +++ b/test/internals/handle-request.test.js @@ -1,8 +1,8 @@ 'use strict' const { test } = require('node:test') -const handleRequest = require('../../lib/handleRequest') -const internals = require('../../lib/handleRequest')[Symbol.for('internals')] +const handleRequest = require('../../lib/handle-request') +const internals = require('../../lib/handle-request')[Symbol.for('internals')] const Request = require('../../lib/request') const Reply = require('../../lib/reply') const { kRouteContext } = require('../../lib/symbols') diff --git a/test/internals/initial-config.test.js b/test/internals/initial-config.test.js index 20a1c146a9f..c513c0035bd 100644 --- a/test/internals/initial-config.test.js +++ b/test/internals/initial-config.test.js @@ -7,7 +7,7 @@ const http = require('node:http') const pino = require('pino') const split = require('split2') const deepClone = require('rfdc')({ circles: true, proto: false }) -const { deepFreezeObject } = require('../../lib/initialConfigValidation').utils +const { deepFreezeObject } = require('../../lib/initial-config-validation').utils const { buildCertificate } = require('../build-certificate') diff --git a/test/internals/plugin.test.js b/test/internals/plugin.test.js index 8dc3357121a..d3bd3415b24 100644 --- a/test/internals/plugin.test.js +++ b/test/internals/plugin.test.js @@ -2,9 +2,9 @@ const { test } = require('node:test') -const pluginUtilsPublic = require('../../lib/pluginUtils.js') +const pluginUtilsPublic = require('../../lib/plugin-utils.js') const symbols = require('../../lib/symbols.js') -const pluginUtils = require('../../lib/pluginUtils')[symbols.kTestInternals] +const pluginUtils = require('../../lib/plugin-utils')[symbols.kTestInternals] test("shouldSkipOverride should check the 'skip-override' symbol", t => { t.plan(2) diff --git a/test/internals/req-id-gen-factory.test.js b/test/internals/req-id-gen-factory.test.js index 567a3cc27a0..558841d565d 100644 --- a/test/internals/req-id-gen-factory.test.js +++ b/test/internals/req-id-gen-factory.test.js @@ -1,7 +1,7 @@ 'use strict' const { test } = require('node:test') -const { reqIdGenFactory } = require('../../lib/reqIdGenFactory') +const { reqIdGenFactory } = require('../../lib/req-id-gen-factory') test('should create incremental ids deterministically', t => { t.plan(1) diff --git a/test/stream.5.test.js b/test/stream.5.test.js index 77165f60868..4580462d7ed 100644 --- a/test/stream.5.test.js +++ b/test/stream.5.test.js @@ -35,8 +35,8 @@ test('should mark reply as sent before pumping the payload stream into response t.plan(2) t.after(() => fastify.close()) - const handleRequest = proxyquire('../lib/handleRequest', { - './wrapThenable': (thenable, reply) => { + const handleRequest = proxyquire('../lib/handle-request', { + './wrap-thenable': (thenable, reply) => { thenable.then(function (payload) { t.assert.strictEqual(reply.sent, true) }) @@ -44,7 +44,7 @@ test('should mark reply as sent before pumping the payload stream into response }) const route = proxyquire('../lib/route', { - './handleRequest': handleRequest + './handle-request': handleRequest }) const Fastify = proxyquire('..', { diff --git a/test/wrap-thenable.test.js b/test/wrap-thenable.test.js index 1e543fd9519..b906acea369 100644 --- a/test/wrap-thenable.test.js +++ b/test/wrap-thenable.test.js @@ -2,7 +2,7 @@ const { test } = require('node:test') const { kReplyHijacked } = require('../lib/symbols') -const wrapThenable = require('../lib/wrapThenable') +const wrapThenable = require('../lib/wrap-thenable') const Reply = require('../lib/reply') test('should resolve immediately when reply[kReplyHijacked] is true', async t => { diff --git a/types/serverFactory.d.ts b/types/server-factory.d.ts similarity index 100% rename from types/serverFactory.d.ts rename to types/server-factory.d.ts From 79cbc96aef2a29f58b6ceff3405e744709cef004 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 24 Sep 2025 09:19:53 +0100 Subject: [PATCH 1154/1295] ci(ci): check dependabot prs come from repo (#6330) --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98255ce5ed7..7e7a8f19347 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -241,6 +241,7 @@ jobs: automerge: if: > github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.user.login == 'dependabot[bot]' needs: - test-typescript From 6a428d94f6bf42792d4b91dd7643f74f5e2960eb Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:43:13 +0200 Subject: [PATCH 1155/1295] fix: accept htab ows (#6303) --- lib/content-type-parser.js | 3 ++- test/content-parser.test.js | 3 ++- test/schema-validation.test.js | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/content-type-parser.js b/lib/content-type-parser.js index 35dcfb031c8..483e93703d4 100644 --- a/lib/content-type-parser.js +++ b/lib/content-type-parser.js @@ -121,7 +121,8 @@ ContentTypeParser.prototype.getParser = function (contentType) { ( caseInsensitiveContentType.length === parserListItem.length || caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ || - caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */ + caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */ || + caseInsensitiveContentType.charCodeAt(parserListItem.length) === 9 /* `\t` */ ) ) { parser = this.customParsers.get(parserListItem) diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 83a757765ac..0f2319b6111 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -48,7 +48,7 @@ test('hasContentTypeParser', async t => { test('getParser', async t => { await t.test('should return matching parser', t => { - t.plan(6) + t.plan(7) const fastify = Fastify() @@ -61,6 +61,7 @@ test('getParser', async t => { t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, third) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html; charset=utf-8').fn, third) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ; charset=utf-8').fn, third) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html\t; charset=utf-8').fn, third) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/htmlINVALID')?.fn, undefined) }) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index 9dd377158db..f2faa40e015 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -1426,6 +1426,17 @@ test('Schema validation will not be bypass by different content type', async t = t.assert.strictEqual(correct2.status, 200) await correct2.bytes() + const correct3 = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json\t; charset=utf-8' + }, + body: JSON.stringify({ foo: 'string' }) + }) + t.assert.strictEqual(correct2.status, 200) + await correct3.bytes() + const invalid1 = await fetch(address, { method: 'POST', url: '/', @@ -1489,8 +1500,8 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid6.status, 415) - t.assert.strictEqual((await invalid6.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + t.assert.strictEqual(invalid6.status, 400) + t.assert.strictEqual((await invalid6.json()).code, 'FST_ERR_VALIDATION') const invalid7 = await fetch(address, { method: 'POST', From b03d0783f24602748ca5042cc30dfadefdd2c53a Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 26 Sep 2025 00:12:11 +0200 Subject: [PATCH 1156/1295] fix: handle non FastifyErrors in custom handler properly, set type of error-parameter for setErrorHandler and errorHandler to unknown, but configurable via generic TError (#6308) * types: loosen setErrorHandler and errorHandler types * set TError to unknown add generic to errorHandler * fix * remove obsolte type tst * add unit test, fix potential issue --------- Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- lib/error-handler.js | 2 +- test/500s.test.js | 191 ++++++++++++++++++++++++++++++++++ test/types/instance.test-d.ts | 10 +- types/instance.d.ts | 4 +- 4 files changed, 196 insertions(+), 11 deletions(-) diff --git a/lib/error-handler.js b/lib/error-handler.js index 25bb90cb97f..03d5cab36ee 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -82,7 +82,7 @@ function handleError (reply, error, cb) { function defaultErrorHandler (error, request, reply) { setErrorHeaders(error, reply) if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) { - const statusCode = error.statusCode || error.status + const statusCode = error && (error.statusCode || error.status) reply.code(statusCode >= 400 ? statusCode : 500) } if (reply.statusCode < 500) { diff --git a/test/500s.test.js b/test/500s.test.js index e6ee7e631d0..846eefbab9b 100644 --- a/test/500s.test.js +++ b/test/500s.test.js @@ -3,6 +3,7 @@ const { test } = require('node:test') const Fastify = require('..') const symbols = require('../lib/symbols.js') +const { FastifyError } = require('@fastify/error') test('default 500', (t, done) => { t.plan(4) @@ -30,6 +31,94 @@ test('default 500', (t, done) => { }) }) +test('default 500 with non-error string', (t, done) => { + t.plan(4) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + throw 'kaboom' // eslint-disable-line no-throw-literal + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.deepStrictEqual(res.payload, 'kaboom') + done() + }) +}) + +test('default 500 with non-error symbol', (t, done) => { + t.plan(4) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + throw Symbol('error') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.payload, '') + done() + }) +}) + +test('default 500 with non-error false', (t, done) => { + t.plan(4) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + throw false // eslint-disable-line no-throw-literal + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.payload, 'false') + done() + }) +}) + +test('default 500 with non-error null', (t, done) => { + t.plan(4) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + throw null // eslint-disable-line no-throw-literal + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.payload, 'null') + done() + }) +}) + test('custom 500', (t, done) => { t.plan(6) @@ -229,3 +318,105 @@ test('catch synchronous errors', (t, done) => { done() }) }) + +test('custom 500 with non-error and custom errorHandler', (t, done) => { + t.plan(6) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + throw 'kaboom' // eslint-disable-line no-throw-literal + }) + + fastify.setErrorHandler(function (err, request, reply) { + t.assert.ok(typeof request === 'object') + t.assert.ok(request instanceof fastify[symbols.kRequest].parent) + reply + .code(500) + .type('text/plain') + .send('an error happened: ' + err.message) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(res.payload, 'an error happened: undefined') + done() + }) +}) + +test('custom 500 with FastifyError detection', (t, done) => { + t.plan(18) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/string', function (req, reply) { + throw 'kaboom' // eslint-disable-line no-throw-literal + }) + + fastify.get('/native-error', function (req, reply) { + throw new Error('kaboom') + }) + + fastify.get('/fastify-error', function (req, reply) { + throw new FastifyError('kaboom') + }) + + fastify.setErrorHandler(function (err, request, reply) { + t.assert.ok(typeof request === 'object') + t.assert.ok(request instanceof fastify[symbols.kRequest].parent) + if (err instanceof FastifyError) { + reply + .code(500) + .type('text/plain') + .send('FastifyError thrown') + } else if (err instanceof Error) { + reply + .code(500) + .type('text/plain') + .send('Error thrown') + } else { + reply + .code(500) + .type('text/plain') + .send('Primitive thrown') + } + }) + + fastify.inject({ + method: 'GET', + url: '/string' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(res.payload, 'Primitive thrown') + + fastify.inject({ + method: 'GET', + url: '/native-error' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(res.payload, 'Error thrown') + + fastify.inject({ + method: 'GET', + url: '/fastify-error' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual(res.payload, 'FastifyError thrown') + done() + }) + }) + }) +}) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 3780ebd2ba1..e59c8d00d62 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -42,7 +42,7 @@ expectType(server.supportedMethods) expectAssignable( server.setErrorHandler(function (error, request, reply) { - expectType(error) + expectType(error) expectAssignable(this) }) ) @@ -150,12 +150,6 @@ server.setNotFoundHandler(function (_, reply) { return reply.send('') }) -function invalidErrorHandler (error: number) { - if (error) throw error -} - -expectError(server.setErrorHandler(invalidErrorHandler)) - server.setSchemaController({ bucket: (parentSchemas: unknown) => { return { @@ -253,7 +247,7 @@ expectAssignable(server.routing({} as RawRequestDefaultExpression, {} as R expectType(fastify().get('/', { handler: () => {}, errorHandler: (error, request, reply) => { - expectAssignable(error) + expectAssignable(error) expectAssignable(request) expectAssignable<{ contextKey: string }>(request.routeOptions.config) expectAssignable(reply) diff --git a/types/instance.d.ts b/types/instance.d.ts index 0173dff9ade..f940ad10509 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -464,12 +464,12 @@ export interface FastifyInstance< /** * Fastify default error handler */ - errorHandler: (error: FastifyError, request: FastifyRequest, reply: FastifyReply) => void; + errorHandler: (error: TError, request: FastifyRequest, reply: FastifyReply) => void; /** * Set a function that will be invoked whenever an exception is thrown during the request lifecycle. */ - setErrorHandler( + setErrorHandler( handler: (this: FastifyInstance, error: TError, request: FastifyRequest, reply: FastifyReply) => any | Promise ): FastifyInstance; From da642ab1e44e63f6c940ca499ebc25511179d992 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 25 Sep 2025 23:25:07 +0100 Subject: [PATCH 1157/1295] build(deps-dev): remove @fastify/pre-commit (#6319) `@fastify/pre-commit` isn't configured or used in this repo as there is no `pre-commit` object in package.json. It won't run regardless as scripts are disabled. Signed-off-by: Frazer Smith Co-authored-by: Aras Abbasi --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 5d3b74b0850..2328dae227d 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,6 @@ } ], "devDependencies": { - "@fastify/pre-commit": "^2.1.0", "@jsumners/line-reporter": "^1.0.1", "@sinclair/typebox": "^0.34.13", "@sinonjs/fake-timers": "^11.2.2", From 1ab11bbcd087c4adbdd3258621f350276d0464ed Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 26 Sep 2025 00:52:33 +0200 Subject: [PATCH 1158/1295] ci: improve citgm workflows (#6334) * ci: improve citgm workflows * fix remark --- .github/workflows/citgm-package.yml | 14 +++++++++++--- .github/workflows/citgm.yml | 7 +++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index 66b700a6319..b92b669119a 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -7,11 +7,19 @@ on: description: 'Package to test' required: true type: string + node-version: description: 'Node version to test' required: true - type: string - default: '20' + type: choice + options: + - '20' + - '22' + - '24' + - 'lts/*' + - 'nightly' + - 'current' + default: '24' os: description: 'Operating System' required: false @@ -63,7 +71,7 @@ jobs: - name: Install Dependencies for Fastify run: | - npm install --ignore-scripts + npm install --production --ignore-scripts - name: Npm Link Fastify run: | npm link diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml index 7b97c59b8f7..60f08a288ce 100644 --- a/.github/workflows/citgm.yml +++ b/.github/workflows/citgm.yml @@ -3,6 +3,8 @@ name: CITGM on: pull_request: types: [labeled] + + workflow_dispatch: permissions: contents: read @@ -10,7 +12,7 @@ permissions: jobs: core-plugins: name: CITGM - if: ${{ github.event.label.name == 'citgm-core-plugins' }} + if: ${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'citgm-core-plugins' }} permissions: contents: read strategy: @@ -85,7 +87,7 @@ jobs: node-version: ${{ matrix.node-version }} remove-label: - if: ${{ github.event.label.name == 'citgm-core-plugins' }} + if: ${{ always() && (github.event_name == 'workflow_dispatch' || github.event.label.name == 'citgm-core-plugins') }} needs: - core-plugins continue-on-error: true @@ -104,5 +106,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: "echo Successfully removed label" + if: ${{ success() }} - run: "echo Could not remove label" if: ${{ failure() }} From 34fea68924d1b8bec34b18b5317c71f9006790f1 Mon Sep 17 00:00:00 2001 From: Denys Otrishko Date: Fri, 26 Sep 2025 11:38:11 -0700 Subject: [PATCH 1159/1295] docs: explain stream error handling (#5746) * docs: explain stream error handling * Update docs/Reference/Server.md Co-authored-by: Matteo Collina Signed-off-by: James Sumners <321201+jsumners@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Frazer Smith Signed-off-by: Denys Otrishko * docs: explain stream error handling * fix test --------- Signed-off-by: James Sumners <321201+jsumners@users.noreply.github.com> Signed-off-by: Denys Otrishko Signed-off-by: Aras Abbasi Co-authored-by: James Sumners <321201+jsumners@users.noreply.github.com> Co-authored-by: Matteo Collina Co-authored-by: Frazer Smith Co-authored-by: Aras Abbasi --- docs/Reference/Reply.md | 3 + docs/Reference/Server.md | 30 ++++++++++ test/request-error.test.js | 116 +++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 679480e4fc1..cc1eac7611a 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -688,6 +688,9 @@ If you are sending a stream and you have not set a `'Content-Type'` header, As noted above, streams are considered to be pre-serialized, so they will be sent unmodified without response validation. +See special note about error handling for streams in +[`setErrorHandler`](./Server.md#seterrorhandler). + ```js const fs = require('node:fs') diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 599cbad6d9f..a3f2400c29e 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1693,6 +1693,9 @@ set it to 500 before calling the error handler. sent to the client. Use the `onSend` hook instead. - not found (404) errors. Use [`setNotFoundHandler`](#set-not-found-handler) instead. +- Stream errors thrown during piping into the response socket, as + headers/response were already sent to the client. + Use custom in-stream data to signal such errors. ```js fastify.setErrorHandler(function (error, request, reply) { @@ -1722,6 +1725,33 @@ if (statusCode >= 500) { > Avoid calling setErrorHandler multiple times in the same scope. > See [`allowErrorHandlerOverride`](#allowerrorhandleroverride). +##### Custom error handler for stream replies + + +If `Content-Type` differs between the endpoint and error handler, explicitly +define it in both. For example, if the endpoint returns an `application/text` +stream and the error handler responds with `application/json`, the error handler +must explicitly set `Content-Type`. Otherwise, it will fail serialization with +a `500` status code. Alternatively, always respond with serialized data in the +error handler by manually calling a serialization method (e.g., +`JSON.stringify`). + +```js +fastify.setErrorHandler((err, req, reply) => { + reply + .code(400) + .type('application/json') + .send({ error: err.message }) +}) +``` + +```js +fastify.setErrorHandler((err, req, reply) => { + reply + .code(400) + .send(JSON.stringify({ error: err.message })) +}) +``` #### setChildLoggerFactory diff --git a/test/request-error.test.js b/test/request-error.test.js index 2dde9b1ca99..fd5a5cdd5d9 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -4,6 +4,9 @@ const { connect } = require('node:net') const { test } = require('node:test') const Fastify = require('..') const { kRequest } = require('../lib/symbols.js') +const split = require('split2') +const { Readable } = require('node:stream') +const { getServerUrl } = require('./helper') test('default 400 on request error', (t, done) => { t.plan(4) @@ -465,3 +468,116 @@ test('test request.routeOptions.version', async t => { t.assert.ok(result2.ok) t.assert.strictEqual(result2.status, 200) }) + +test('customErrorHandler should throw for json err and stream response', async (t) => { + t.plan(5) + + const logStream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + t.after(() => fastify.close()) + + fastify.get('/', async (req, reply) => { + const stream = new Readable({ + read () { + this.push('hello') + } + }) + process.nextTick(() => stream.destroy(new Error('stream error'))) + + reply.type('application/text') + await reply.send(stream) + }) + + fastify.setErrorHandler((err, req, reply) => { + t.assert.strictEqual(err.message, 'stream error') + reply.code(400) + reply.send({ error: err.message }) + }) + + logStream.once('data', line => { + t.assert.strictEqual(line.msg, 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.') + t.assert.strictEqual(line.level, 50) + }) + + await fastify.listen({ port: 0 }) + + const response = await fetch(getServerUrl(fastify) + '/') + + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(await response.json(), { statusCode: 500, code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE', error: 'Internal Server Error', message: "Attempted to send payload of invalid type 'object'. Expected a string or Buffer." }) +}) + +test('customErrorHandler should not throw for json err and stream response with content-type defined', async (t) => { + t.plan(4) + + const logStream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + + t.after(() => fastify.close()) + + fastify.get('/', async (req, reply) => { + const stream = new Readable({ + read () { + this.push('hello') + } + }) + process.nextTick(() => stream.destroy(new Error('stream error'))) + + reply.type('application/text') + await reply.send(stream) + }) + + fastify.setErrorHandler((err, req, reply) => { + t.assert.strictEqual(err.message, 'stream error') + reply + .code(400) + .type('application/json') + .send({ error: err.message }) + }) + + await fastify.listen({ port: 0 }) + + const response = await fetch(getServerUrl(fastify) + '/') + + t.assert.strictEqual(response.status, 400) + t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(await response.json(), { error: 'stream error' }) +}) + +test('customErrorHandler should not call handler for in-stream error', async (t) => { + t.plan(1) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', async (req, reply) => { + const stream = new Readable({ + read () { + this.push('hello') + stream.destroy(new Error('stream error')) + } + }) + + reply.type('application/text') + await reply.send(stream) + }) + + fastify.setErrorHandler(() => { + t.assert.fail('must not be called') + }) + await fastify.listen({ port: 0 }) + + await t.assert.rejects(fetch(getServerUrl(fastify) + '/'), { + message: 'fetch failed' + }) +}) From 329e40f09f23910ed0193a9636b474844a7fbe79 Mon Sep 17 00:00:00 2001 From: "Juan L." Date: Fri, 26 Sep 2025 16:56:39 -0300 Subject: [PATCH 1160/1295] fix: error throwing in reply (#6299) * fix: instantiate readable stream error * Create reply-web-stream-locked.test.js Signed-off-by: Juan L. Signed-off-by: Juan Letamendia * fix test --------- Signed-off-by: Juan L. Signed-off-by: Juan Letamendia Co-authored-by: Aras Abbasi --- lib/reply.js | 2 +- test/reply-web-stream-locked.test.js | 37 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 test/reply-web-stream-locked.test.js diff --git a/lib/reply.js b/lib/reply.js index 0d057acf6a9..59f16b0b794 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -681,7 +681,7 @@ function logStreamError (logger, err, res) { function sendWebStream (payload, res, reply) { if (payload.locked) { - throw FST_ERR_REP_READABLE_STREAM_LOCKED() + throw new FST_ERR_REP_READABLE_STREAM_LOCKED() } const nodeStream = Readable.fromWeb(payload) sendStream(nodeStream, res, reply) diff --git a/test/reply-web-stream-locked.test.js b/test/reply-web-stream-locked.test.js new file mode 100644 index 00000000000..21262a05906 --- /dev/null +++ b/test/reply-web-stream-locked.test.js @@ -0,0 +1,37 @@ +'use strict' + +const { ReadableStream } = require('node:stream/web') +const { test, after } = require('node:test') +const Fastify = require('..') + +test('reply.send(web ReadableStream) throws if locked', async t => { + t.plan(3) + + const app = Fastify() + after(() => app.close()) + + app.get('/', (req, reply) => { + const rs = new ReadableStream({ + start (controller) { controller.enqueue(new TextEncoder().encode('hi')); controller.close() } + }) + // lock the stream + const reader = rs.getReader() + t.assert.strictEqual(rs.locked, true, 'stream is locked') + + // sending a locked stream should trigger the Fastify error + reply.send(rs) + reader.releaseLock() + }) + + const res = await app.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual( + JSON.parse(res.body), + { + statusCode: 500, + code: 'FST_ERR_REP_READABLE_STREAM_LOCKED', + error: 'Internal Server Error', + message: 'ReadableStream was locked. You should call releaseLock() method on reader before sending.' + } + ) +}) From a38ebb49dbe32245639d8757d3acb70ff6a298e9 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Sat, 27 Sep 2025 10:08:26 +0200 Subject: [PATCH 1161/1295] refactor: delegate options processing to a dedicated function (#6333) * refactor: delegate configuration to builder a function * refactor: processOptions * fix: unused exported variables --- fastify.js | 323 +++++++++++++++++++++++++------------------------- lib/route.js | 2 +- lib/server.js | 20 +++- 3 files changed, 183 insertions(+), 162 deletions(-) diff --git a/fastify.js b/fastify.js index d798feae54a..ee87fdec3e2 100644 --- a/fastify.js +++ b/fastify.js @@ -50,7 +50,6 @@ const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('. const build404 = require('./lib/four-oh-four') const getSecuredInitialConfig = require('./lib/initial-config-validation.js') const override = require('./lib/plugin-override') -const noopSet = require('./lib/noop-set') const { appendStackTrace, AVVIO_ERRORS_MAP, @@ -63,7 +62,6 @@ const { defaultInitOptions } = getSecuredInitialConfig const { FST_ERR_ASYNC_CONSTRAINT, FST_ERR_BAD_URL, - FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE, FST_ERR_OPTIONS_NOT_OBJ, FST_ERR_QSP_NOT_FN, FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN, @@ -83,99 +81,17 @@ const { FSTWRN004 } = require('./lib/warnings.js') const initChannel = diagnostics.channel('fastify.initialization') -function defaultBuildPrettyMeta (route) { - // return a shallow copy of route's sanitized context - - const cleanKeys = {} - const allowedProps = ['errorHandler', 'logLevel', 'logSerializers'] - - allowedProps.concat(supportedHooks).forEach(k => { - cleanKeys[k] = route.store[k] - }) - - return Object.assign({}, cleanKeys) -} - /** * @param {import('./fastify.js').FastifyServerOptions} options */ -function fastify (options) { - // Options validations - if (options && typeof options !== 'object') { - throw new FST_ERR_OPTIONS_NOT_OBJ() - } else { - // Shallow copy options object to prevent mutations outside of this function - options = Object.assign({}, options) - } - - if ( - (options.querystringParser && typeof options.querystringParser !== 'function') || - ( - options.routerOptions?.querystringParser && - typeof options.routerOptions.querystringParser !== 'function' - ) - ) { - throw new FST_ERR_QSP_NOT_FN(typeof (options.querystringParser ?? options.routerOptions.querystringParser)) - } - - if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') { - throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket) - } - - validateBodyLimitOption(options.bodyLimit) - - const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id') - const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId) - const requestIdLogLabel = options.requestIdLogLabel || 'reqId' - const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit - const disableRequestLogging = options.disableRequestLogging || false - - const ajvOptions = Object.assign({ - customOptions: {}, - plugins: [] - }, options.ajv) - const frameworkErrors = options.frameworkErrors - - // Ajv options - if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') { - throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions) - } - if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) { - throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins) - } - - // Instance Fastify components - - const { logger, hasLogger } = createLogger(options) - - // Update the options with the fixed values - options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout - options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout - options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket - options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout - options.logger = logger - options.requestIdHeader = requestIdHeader - options.requestIdLogLabel = requestIdLogLabel - options.disableRequestLogging = disableRequestLogging - options.ajv = ajvOptions - options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler - options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride - - const initialConfig = getSecuredInitialConfig(options) - - // exposeHeadRoutes have its default set from the validator - options.exposeHeadRoutes = initialConfig.exposeHeadRoutes - - options.routerOptions = buildRouterOptions(options, { - defaultRoute, - onBadUrl, - ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash, - ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes, - maxParamLength: defaultInitOptions.maxParamLength, - allowUnsafeRegex: defaultInitOptions.allowUnsafeRegex, - buildPrettyMeta: defaultBuildPrettyMeta, - useSemicolonDelimiter: defaultInitOptions.useSemicolonDelimiter - }) +function fastify (serverOptions) { + const { + options, + genReqId, + disableRequestLogging, + hasLogger, + initialConfig + } = processOptions(serverOptions, defaultRoute, onBadUrl) // Default router const router = buildRouting({ @@ -188,23 +104,14 @@ function fastify (options) { // HTTP server and its handler const httpHandler = wrapRouting(router, options) - // we need to set this before calling createServer - options.http2SessionTimeout = initialConfig.http2SessionTimeout - const { server, listen } = createServer(options, httpHandler) - - const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function' - const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function' - const serverHasCloseHttp2Sessions = typeof server.closeHttp2Sessions === 'function' - - let forceCloseConnections = options.forceCloseConnections - if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) { - throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE() - } else if (typeof forceCloseConnections !== 'boolean') { - /* istanbul ignore next: only one branch can be valid in a given Node.js version */ - forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false - } - - const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet() + const { + server, + listen, + forceCloseConnections, + serverHasCloseAllConnections, + serverHasCloseHttp2Sessions, + keepAliveConnections + } = createServer(options, httpHandler) const setupResponseListeners = Reply.setupResponseListeners const schemaController = SchemaController.buildSchemaController(null, options.schemaController) @@ -241,7 +148,7 @@ function fastify (options) { [kOptions]: options, [kChildren]: [], [kServerBindings]: [], - [kBodyLimit]: bodyLimit, + [kBodyLimit]: options.bodyLimit, [kRoutePrefix]: '', [kLogLevel]: '', [kLogSerializers]: null, @@ -253,7 +160,7 @@ function fastify (options) { [kChildLoggerFactory]: defaultChildLoggerFactory, [kReplySerializerDefault]: null, [kContentTypeParser]: new ContentTypeParser( - bodyLimit, + options.bodyLimit, (options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning), (options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning) ), @@ -307,7 +214,7 @@ function fastify (options) { return router.findRoute(options) }, // expose logger instance - log: logger, + log: options.logger, // type provider withTypeProvider, // hooks @@ -529,7 +436,6 @@ function fastify (options) { router.setup(options, { avvio, fourOhFour, - logger, hasLogger, setupResponseListeners, throwIfAlreadyStarted, @@ -716,46 +622,6 @@ function fastify (options) { return this } - function defaultClientErrorHandler (err, socket) { - // In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done. - // https://nodejs.org/api/http.html#http_event_clienterror - if (err.code === 'ECONNRESET' || socket.destroyed) { - return - } - - let body, errorCode, errorStatus, errorLabel - - if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') { - errorCode = '408' - errorStatus = http.STATUS_CODES[errorCode] - body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}` - errorLabel = 'timeout' - } else if (err.code === 'HPE_HEADER_OVERFLOW') { - errorCode = '431' - errorStatus = http.STATUS_CODES[errorCode] - body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}` - errorLabel = 'header_overflow' - } else { - errorCode = '400' - errorStatus = http.STATUS_CODES[errorCode] - body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}` - errorLabel = 'error' - } - - // Most devs do not know what to do with this error. - // In the vast majority of cases, it's a network error and/or some - // config issue on the load balancer side. - this.log.trace({ err }, `client ${errorLabel}`) - // Copying standard node behavior - // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666 - - // If the socket is not writable, there is no reason to try to send data. - if (socket.writable) { - socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`) - } - socket.destroy(err) - } - // If the router does not match any route, every request will land here // req and res are Node.js core objects function defaultRoute (req, res) { @@ -770,9 +636,9 @@ function fastify (options) { } function onBadUrl (path, req, res) { - if (frameworkErrors) { + if (options.frameworkErrors) { const id = getGenReqId(onBadUrlContext.server, req) - const childLogger = createChildLogger(onBadUrlContext, logger, req, id) + const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id) const request = new Request(id, null, req, null, childLogger, onBadUrlContext) const reply = new Reply(res, request, childLogger) @@ -781,7 +647,7 @@ function fastify (options) { childLogger.info({ req: request }, 'incoming request') } - return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply) + return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply) } const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}` res.writeHead(400, { @@ -795,9 +661,9 @@ function fastify (options) { if (isAsync === false) return undefined return function onAsyncConstraintError (err) { if (err) { - if (frameworkErrors) { + if (options.frameworkErrors) { const id = getGenReqId(onBadUrlContext.server, req) - const childLogger = createChildLogger(onBadUrlContext, logger, req, id) + const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id) const request = new Request(id, null, req, null, childLogger, onBadUrlContext) const reply = new Reply(res, request, childLogger) @@ -806,7 +672,7 @@ function fastify (options) { childLogger.info({ req: request }, 'incoming request') } - return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply) + return options.frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply) } const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}' res.writeHead(500, { @@ -943,6 +809,145 @@ function fastify (options) { } } +function processOptions (options, defaultRoute, onBadUrl) { + // Options validations + if (options && typeof options !== 'object') { + throw new FST_ERR_OPTIONS_NOT_OBJ() + } else { + // Shallow copy options object to prevent mutations outside of this function + options = Object.assign({}, options) + } + + if ( + (options.querystringParser && typeof options.querystringParser !== 'function') || + ( + options.routerOptions?.querystringParser && + typeof options.routerOptions.querystringParser !== 'function' + ) + ) { + throw new FST_ERR_QSP_NOT_FN(typeof (options.querystringParser ?? options.routerOptions.querystringParser)) + } + + if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') { + throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket) + } + + validateBodyLimitOption(options.bodyLimit) + + const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id') + const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId) + const requestIdLogLabel = options.requestIdLogLabel || 'reqId' + options.bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit + const disableRequestLogging = options.disableRequestLogging || false + + const ajvOptions = Object.assign({ + customOptions: {}, + plugins: [] + }, options.ajv) + + if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') { + throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions) + } + if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) { + throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins) + } + + const { logger, hasLogger } = createLogger(options) + + // Update the options with the fixed values + options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout + options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout + options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket + options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout + options.logger = logger + options.requestIdHeader = requestIdHeader + options.requestIdLogLabel = requestIdLogLabel + options.disableRequestLogging = disableRequestLogging + options.ajv = ajvOptions + options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler + options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride + + const initialConfig = getSecuredInitialConfig(options) + + // exposeHeadRoutes have its default set from the validator + options.exposeHeadRoutes = initialConfig.exposeHeadRoutes + + // we need to set this before calling createServer + options.http2SessionTimeout = initialConfig.http2SessionTimeout + + options.routerOptions = buildRouterOptions(options, { + defaultRoute, + onBadUrl, + ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash, + ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes, + maxParamLength: defaultInitOptions.maxParamLength, + allowUnsafeRegex: defaultInitOptions.allowUnsafeRegex, + buildPrettyMeta: defaultBuildPrettyMeta, + useSemicolonDelimiter: defaultInitOptions.useSemicolonDelimiter + }) + + return { + options, + genReqId, + disableRequestLogging, + hasLogger, + initialConfig + } +} + +function defaultBuildPrettyMeta (route) { + // return a shallow copy of route's sanitized context + + const cleanKeys = {} + const allowedProps = ['errorHandler', 'logLevel', 'logSerializers'] + + allowedProps.concat(supportedHooks).forEach(k => { + cleanKeys[k] = route.store[k] + }) + + return Object.assign({}, cleanKeys) +} + +function defaultClientErrorHandler (err, socket) { + // In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done. + // https://nodejs.org/api/http.html#http_event_clienterror + if (err.code === 'ECONNRESET' || socket.destroyed) { + return + } + + let body, errorCode, errorStatus, errorLabel + + if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') { + errorCode = '408' + errorStatus = http.STATUS_CODES[errorCode] + body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}` + errorLabel = 'timeout' + } else if (err.code === 'HPE_HEADER_OVERFLOW') { + errorCode = '431' + errorStatus = http.STATUS_CODES[errorCode] + body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}` + errorLabel = 'header_overflow' + } else { + errorCode = '400' + errorStatus = http.STATUS_CODES[errorCode] + body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}` + errorLabel = 'error' + } + + // Most devs do not know what to do with this error. + // In the vast majority of cases, it's a network error and/or some + // config issue on the load balancer side. + this.log.trace({ err }, `client ${errorLabel}`) + // Copying standard node behavior + // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666 + + // If the socket is not writable, there is no reason to try to send data. + if (socket.writable) { + socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`) + } + socket.destroy(err) +} + function validateSchemaErrorFormatter (schemaErrorFormatter) { if (typeof schemaErrorFormatter !== 'function') { throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN(typeof schemaErrorFormatter) diff --git a/lib/route.js b/lib/route.js index 83813220f94..fce6f633830 100644 --- a/lib/route.js +++ b/lib/route.js @@ -94,11 +94,11 @@ function buildRouting (options) { setup (options, fastifyArgs) { avvio = fastifyArgs.avvio fourOhFour = fastifyArgs.fourOhFour - logger = fastifyArgs.logger hasLogger = fastifyArgs.hasLogger setupResponseListeners = fastifyArgs.setupResponseListeners throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted + logger = options.logger globalExposeHeadRoutes = options.exposeHeadRoutes disableRequestLogging = options.disableRequestLogging ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash diff --git a/lib/server.js b/lib/server.js index 282c3289922..ed483faa62d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -12,8 +12,10 @@ const { onListenHookRunner } = require('./hooks') const { FST_ERR_REOPENED_CLOSE_SERVER, FST_ERR_REOPENED_SERVER, - FST_ERR_LISTEN_OPTIONS_INVALID + FST_ERR_LISTEN_OPTIONS_INVALID, + FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE } = require('./errors') +const noopSet = require('./noop-set') const PonyPromise = require('./promise') module.exports.createServer = createServer @@ -122,7 +124,21 @@ function createServer (options, httpHandler) { this.ready(listenCallback.call(this, server, listenOptions)) } - return { server, listen } + const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function' + const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function' + const serverHasCloseHttp2Sessions = typeof server.closeHttp2Sessions === 'function' + + let forceCloseConnections = options.forceCloseConnections + if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) { + throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE() + } else if (typeof forceCloseConnections !== 'boolean') { + /* istanbul ignore next: only one branch can be valid in a given Node.js version */ + forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false + } + + const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet() + + return { server, listen, forceCloseConnections, serverHasCloseAllConnections, serverHasCloseHttp2Sessions, keepAliveConnections } } function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, onListen) { From d6d408a3d9c7292f7dfef090bcde422a9c374e8c Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sat, 27 Sep 2025 13:28:15 +0200 Subject: [PATCH 1162/1295] ci: remove label of citgm only on pull_request.labeled, add options for workflow_dispatch (#6335) * ci (citgm): remove label of citgm only on pull_request.labeled, add options for workflow_dispatch The previous PR for citgm improved the usability, but it had a bug on when to try to remove the label. * remove diagnostics-channel plugin because it is archived * add fallbacks --- .github/workflows/citgm.yml | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml index 60f08a288ce..90ba18d8220 100644 --- a/.github/workflows/citgm.yml +++ b/.github/workflows/citgm.yml @@ -5,6 +5,29 @@ on: types: [labeled] workflow_dispatch: + inputs: + node-version: + description: 'Node version' + required: true + type: choice + options: + - '20' + - '22' + - '24' + - 'lts/*' + - 'nightly' + - 'current' + - 'latest' + default: '24' + os: + description: 'Operating System' + required: false + type: choice + default: 'ubuntu-latest' + options: + - 'ubuntu-latest' + - 'windows-latest' + - 'macos-latest' permissions: contents: read @@ -33,7 +56,6 @@ jobs: - '@fastify/cookie' - '@fastify/cors' - '@fastify/csrf-protection' - - '@fastify/diagnostics-channel' # - '@fastify/elasticsearch' - '@fastify/env' - '@fastify/etag' @@ -78,16 +100,14 @@ jobs: # - '@fastify/vite' - '@fastify/websocket' - '@fastify/zipkin' - node-version: ['20'] - os: [ubuntu-latest] uses: './.github/workflows/citgm-package.yml' with: - os: ${{ matrix.os }} + os: ${{ github.event_name == 'workflow_dispatch' && inputs.os || 'ubuntu-latest' }} package: ${{ matrix.package }} - node-version: ${{ matrix.node-version }} + node-version: ${{ github.event_name == 'workflow_dispatch' && inputs.node-version || '24' }} remove-label: - if: ${{ always() && (github.event_name == 'workflow_dispatch' || github.event.label.name == 'citgm-core-plugins') }} + if: ${{ always() && github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'citgm-core-plugins' }} needs: - core-plugins continue-on-error: true From 66e87d01b66d25d8a2e12964a999aa7080d6a250 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:19:54 +0000 Subject: [PATCH 1163/1295] chore: Bump actions/checkout from 4 to 5 (#6343) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-alternative-runtime.yml | 6 +++--- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/citgm-package.yml | 4 ++-- .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- .../workflows/integration-alternative-runtimes.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/links-check.yml | 2 +- .github/workflows/lint-ecosystem-order.yml | 2 +- .github/workflows/md-lint.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index d8955abfe74..05e94fc9236 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -37,7 +37,7 @@ jobs: node-version: 20 nsolid-version: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -61,7 +61,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -88,7 +88,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e7a8f19347..8e24ac84dcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: contents: read steps: - name: Check out repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false @@ -50,7 +50,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -76,7 +76,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -125,7 +125,7 @@ jobs: os: [macos-latest, ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -153,7 +153,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -183,7 +183,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -212,7 +212,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - name: Use Node.js diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index b92b669119a..14bb02cc927 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -57,7 +57,7 @@ jobs: contents: read steps: - name: Check out Fastify - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false @@ -87,7 +87,7 @@ jobs: const result = repositoryUrl.match( /.*\/([a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+)\.git/)[1] return result - name: Check out ${{inputs.package}} - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: ${{ steps.repository-url.outputs.result }} path: package diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index fc1de26d458..94d9192375c 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 869ed3ab53d..f5cecc67f72 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index e58c66fb83c..44fab8b6692 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -34,7 +34,7 @@ jobs: node-version: 20 runtime: nsolid steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 2af82678e90..785aeeb15f5 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -30,7 +30,7 @@ jobs: pnpm-version: [8] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 16bcfd07334..b0bf3a20da2 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -16,7 +16,7 @@ jobs: contents: read steps: - name: Check out repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index 405fffd94f3..6abab216b8d 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index a3493edad5e..36dfcf9f954 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 6356a5aefeb..b5334e22c51 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -22,7 +22,7 @@ jobs: pnpm-version: [8] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false From 820496792e4828b25a28c39fcba1fb30b40a8f2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:33:29 +0000 Subject: [PATCH 1164/1295] chore: Bump joi from 17.13.3 to 18.0.1 (#6347) Bumps [joi](https://github.com/hapijs/joi) from 17.13.3 to 18.0.1. - [Commits](https://github.com/hapijs/joi/compare/v17.13.3...v18.0.1) --- updated-dependencies: - dependency-name: joi dependency-version: 18.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2328dae227d..c3982a015e9 100644 --- a/package.json +++ b/package.json @@ -187,7 +187,7 @@ "fluent-json-schema": "^6.0.0", "h2url": "^0.2.0", "http-errors": "^2.0.0", - "joi": "^17.12.3", + "joi": "^18.0.1", "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", "markdownlint-cli2": "^0.18.1", From 2a98b9c57ebf98cf9918dec6a82ce218a8594742 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:20:07 +0200 Subject: [PATCH 1165/1295] chore: Bump lycheeverse/lychee-action from 2.4.1 to 2.6.1 (#6345) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.4.1 to 2.6.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/82202e5e9c2f4ef1a55a3d02563e1cb6041e5332...885c65f3dc543b57c898c8099f4e08c8afd178a2) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index b0bf3a20da2..2ad713716f9 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -24,7 +24,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1 + uses: lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2 # v2.6.1 with: fail: true # As external links behavior is not predictable, we check only internal links From d78d3a849390ed14320f5775304be565397c5468 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:20:25 +0200 Subject: [PATCH 1166/1295] chore: Bump actions/dependency-review-action from 4.7.1 to 4.8.0 (#6344) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.7.1 to 4.8.0. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/da24556b548a50705dd671f47852072ea4c105d9...56339e523c0409420f6c2c9a2f4292bbb3c07dd3) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-version: 4.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e24ac84dcd..47474995817 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Dependency review - uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 + uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0 check-licenses: name: Check licenses From 48f77d93b6185f7a89be7c48c9ce44eb0ff4b455 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:21:32 +0200 Subject: [PATCH 1167/1295] chore: Bump actions/labeler from 5 to 6 (#6341) Bumps [actions/labeler](https://github.com/actions/labeler) from 5 to 6. - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/labeler dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 9c82959696d..6f8e7a924f6 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -11,4 +11,4 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@v6 From a1869376f24bb4b0d8000d5f3fa8f4e487fcd9a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:22:07 +0200 Subject: [PATCH 1168/1295] chore: Bump actions/setup-node from 4 to 5 (#6342) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/citgm-package.yml | 2 +- .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/md-lint.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47474995817..eb046d4f6b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 'lts/*' cache: 'npm' @@ -81,7 +81,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 'lts/*' cache: 'npm' @@ -130,7 +130,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -158,7 +158,7 @@ jobs: persist-credentials: false - name: Use Custom Node.js Version - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ github.event.inputs.nodejs-version }} cache: 'npm' @@ -187,7 +187,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: '20' cache: 'npm' @@ -216,7 +216,7 @@ jobs: with: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index 14bb02cc927..40158e49f42 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -62,7 +62,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ inputs.node-version }} cache: 'npm' diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 94d9192375c..91ed99181ea 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -16,7 +16,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index f5cecc67f72..940526f0863 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -16,7 +16,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 785aeeb15f5..ff522ae0f7d 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -35,7 +35,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} check-latest: true diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index 36dfcf9f954..423da287e1f 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 'lts/*' check-latest: true diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index b5334e22c51..294d4666cf3 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -27,7 +27,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} check-latest: true @@ -62,7 +62,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} check-latest: true From 30043bed7dc7768db14c1fcf91047a9c945ab404 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:22:59 +0200 Subject: [PATCH 1169/1295] chore: Bump actions/github-script from 7 to 8 (#6340) Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/citgm-package.yml | 2 +- .github/workflows/lint-ecosystem-order.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index 40158e49f42..925b7c7f665 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -76,7 +76,7 @@ jobs: run: | npm link - name: Determine repository URL of ${{inputs.package}} - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: repository-url with: result-encoding: string diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index 6abab216b8d..0932cd0d6e9 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Lint Doc - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From 0c7bc2c723aca8e757ce850b42bdf07c65a38848 Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Sun, 5 Oct 2025 15:49:24 +0200 Subject: [PATCH 1170/1295] docs: mention that addHttpMethod override existing methods (#6350) --- docs/Reference/Server.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index a3f2400c29e..926bd46a4e4 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1442,6 +1442,8 @@ fastify.mkcol('/', (req, reply) => { }) ``` +> ⚠ Warning: +> `addHttpMethod` overrides existing methods. #### addSchema From fa5b4792c70924693dcdd18df7052c510242d32b Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Sun, 5 Oct 2025 21:12:56 +0200 Subject: [PATCH 1171/1295] chore: remove reference to simple-get (#6353) --- test/promises.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/promises.test.js b/test/promises.test.js index f1f34b1ec81..68b828844cc 100644 --- a/test/promises.test.js +++ b/test/promises.test.js @@ -64,7 +64,7 @@ fastify.get('/return-reply', opts, function (req, reply) { fastify.listen({ port: 0 }, (err, fastifyServer) => { assert.ifError(err) - test('shorthand - sget return promise es6 get', async t => { + test('shorthand - fetch return promise es6 get', async t => { t.plan(4) const result = await fetch(`${fastifyServer}/return`) @@ -75,7 +75,7 @@ fastify.listen({ port: 0 }, (err, fastifyServer) => { t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) }) - test('shorthand - sget promise es6 get return error', async t => { + test('shorthand - fetch promise es6 get return error', async t => { t.plan(2) const result = await fetch(`${fastifyServer}/return-error`) @@ -83,7 +83,7 @@ fastify.listen({ port: 0 }, (err, fastifyServer) => { t.assert.strictEqual(result.status, 500) }) - test('sget promise double send', async t => { + test('fetch promise double send', async t => { t.plan(3) const result = await fetch(`${fastifyServer}/double`) From 387b168997acbffb82ea9b10377a43b21aceb259 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Mon, 6 Oct 2025 11:27:01 +0200 Subject: [PATCH 1172/1295] chore: remove commented tests (#6352) * chore: remove commented tests * chore: Delete test/check.test.js --- test/check.test.js | 219 --------------------------------------------- 1 file changed, 219 deletions(-) delete mode 100644 test/check.test.js diff --git a/test/check.test.js b/test/check.test.js deleted file mode 100644 index 941152f24f7..00000000000 --- a/test/check.test.js +++ /dev/null @@ -1,219 +0,0 @@ -'use strict' - -const { test } = require('node:test') -const { S } = require('fluent-json-schema') -const Fastify = require('..') - -const BadRequestSchema = S.object() - .prop('statusCode', S.number()) - .prop('error', S.string()) - .prop('message', S.string()) - -const InternalServerErrorSchema = S.object() - .prop('statusCode', S.number()) - .prop('error', S.string()) - .prop('message', S.string()) - -const NotFoundSchema = S.object() - .prop('statusCode', S.number()) - .prop('error', S.string()) - .prop('message', S.string()) - -const options = { - schema: { - body: { - type: 'object', - properties: { - id: { type: 'string' } - } - }, - response: { - 200: { - type: 'object', - properties: { - id: { type: 'string' } - } - }, - 400: { - description: 'Bad Request', - content: { - 'application/json': { - schema: BadRequestSchema.valueOf() - } - } - }, - 404: { - description: 'Resource not found', - content: { - 'application/json': { - schema: NotFoundSchema.valueOf(), - example: { - statusCode: 404, - error: 'Not Found', - message: 'Not Found' - } - } - } - }, - 500: { - description: 'Internal Server Error', - content: { - 'application/json': { - schema: InternalServerErrorSchema.valueOf(), - example: { - message: 'Internal Server Error' - } - } - } - } - } - } -} - -const handler = (request, reply) => { - if (request.body.id === '400') { - return reply.status(400).send({ - statusCode: 400, - error: 'Bad Request', - message: 'Custom message', - extra: 'This should not be in the response' - }) - } - - if (request.body.id === '404') { - return reply.status(404).send({ - statusCode: 404, - error: 'Not Found', - message: 'Custom Not Found', - extra: 'This should not be in the response' - }) - } - - if (request.body.id === '500') { - reply.status(500).send({ - statusCode: 500, - error: 'Internal Server Error', - message: 'Custom Internal Server Error', - extra: 'This should not be in the response' - }) - } - - reply.send({ - id: request.body.id, - extra: 'This should not be in the response' - }) -} - -test('serialize the response for a Bad Request error, as defined on the schema', async t => { - const fastify = Fastify({}) - - t.after(() => fastify.close()) - - fastify.post('/', options, handler) - - const fastifyServer = await fastify.listen({ port: 0 }) - - const result = await fetch(fastifyServer, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: '12' - }) - - t.assert.ok(!result.ok) - t.assert.strictEqual(result.status, 400) - t.assert.deepStrictEqual(await result.json(), { - statusCode: 400, - error: 'Bad Request', - message: 'body must be object' - }) -}) - -// test('serialize the response for a Not Found error, as defined on the schema', t => { -// const fastify = Fastify({}) - -// t.teardown(fastify.close.bind(fastify)) - -// fastify.post('/', options, handler) - -// fastify.listen({ port: 0 }, err => { -// t.error(err) - -// const url = `http://localhost:${fastify.server.address().port}/` - -// sget({ -// method: 'POST', -// url, -// json: true, -// body: { id: '404' } -// }, (err, response, body) => { -// t.error(err) -// t.equal(response.statusCode, 404) -// t.same(body, { -// statusCode: 404, -// error: 'Not Found', -// message: 'Custom Not Found' -// }) -// t.end() -// }) -// }) -// }) - -// test('serialize the response for a Internal Server Error error, as defined on the schema', t => { -// const fastify = Fastify({}) - -// t.teardown(fastify.close.bind(fastify)) - -// fastify.post('/', options, handler) - -// fastify.listen({ port: 0 }, err => { -// t.error(err) - -// const url = `http://localhost:${fastify.server.address().port}/` - -// sget({ -// method: 'POST', -// url, -// json: true, -// body: { id: '500' } -// }, (err, response, body) => { -// t.error(err) -// t.equal(response.statusCode, 500) -// t.same(body, { -// statusCode: 500, -// error: 'Internal Server Error', -// message: 'Custom Internal Server Error' -// }) -// t.end() -// }) -// }) -// }) - -// test('serialize the success response, as defined on the schema', t => { -// const fastify = Fastify({}) - -// t.teardown(fastify.close.bind(fastify)) - -// fastify.post('/', options, handler) - -// fastify.listen({ port: 0 }, err => { -// t.error(err) - -// const url = `http://localhost:${fastify.server.address().port}/` - -// sget({ -// method: 'POST', -// url, -// json: true, -// body: { id: 'test' } -// }, (err, response, body) => { -// t.error(err) -// t.equal(response.statusCode, 200) -// t.same(body, { -// id: 'test' -// }) -// t.end() -// }) -// }) -// }) From e1f58aba76e0510cd69e4e69b507598253b70070 Mon Sep 17 00:00:00 2001 From: Scott Talbot Date: Tue, 7 Oct 2025 21:49:55 +1100 Subject: [PATCH 1173/1295] fix: respect child logger factory in fastify options (#6349) Co-authored-by: Carlos Fuentes --- fastify.js | 2 +- test/child-logger-factory.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fastify.js b/fastify.js index ee87fdec3e2..d39dfc647de 100644 --- a/fastify.js +++ b/fastify.js @@ -157,7 +157,7 @@ function fastify (serverOptions) { [kSchemaErrorFormatter]: null, [kErrorHandler]: buildErrorHandler(), [kErrorHandlerAlreadySet]: false, - [kChildLoggerFactory]: defaultChildLoggerFactory, + [kChildLoggerFactory]: options.childLoggerFactory || defaultChildLoggerFactory, [kReplySerializerDefault]: null, [kContentTypeParser]: new ContentTypeParser( options.bodyLimit, diff --git a/test/child-logger-factory.test.js b/test/child-logger-factory.test.js index 3dd5f4f417b..f458757bae5 100644 --- a/test/child-logger-factory.test.js +++ b/test/child-logger-factory.test.js @@ -34,12 +34,12 @@ test('Should accept a custom childLoggerFactory function', (t, done) => { }) test('Should accept a custom childLoggerFactory function as option', (t, done) => { - t.plan(2) + t.plan(4) const fastify = Fastify({ childLoggerFactory: function (logger, bindings, opts) { - t.ok(bindings.reqId) - t.ok(opts) + t.assert.ok(bindings.reqId) + t.assert.ok(opts) this.log.debug(bindings, 'created child logger') return logger.child(bindings, opts) } From f225328294aa598d8ccb329d01ea67ab8b1f1be3 Mon Sep 17 00:00:00 2001 From: Abdullah Ali <37246509+attaryz@users.noreply.github.com> Date: Sat, 11 Oct 2025 13:38:15 +0300 Subject: [PATCH 1174/1295] docs(ecosystem): adding attaryz/fastify-devtools to community plugins (#6339) * docs(ecosystem): adding fastify-devtools to community plugins Signed-off-by: Abdullah Ali <37246509+attaryz@users.noreply.github.com> * update Ecosystem.md according to linter Signed-off-by: Abdullah Ali <37246509+attaryz@users.noreply.github.com> * fix indent Signed-off-by: Abdullah Ali <37246509+attaryz@users.noreply.github.com> --------- Signed-off-by: Abdullah Ali <37246509+attaryz@users.noreply.github.com> --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 852198e11e1..0e1dd4d63bd 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -165,6 +165,9 @@ section. A simple way to add a crud in your fastify project. - [`@applicazza/fastify-nextjs`](https://github.com/applicazza/fastify-nextjs) Alternate Fastify and Next.js integration. +- [`@attaryz/fastify-devtools`](https://github.com/attaryz/fastify-devtools) + Development tools plugin for Fastify with live request dashboard, replay + capabilities, and metrics tracking. - [`@blastorg/fastify-aws-dynamodb-cache`](https://github.com/blastorg/fastify-aws-dynamodb-cache) A plugin to help with caching API responses using AWS DynamoDB. - [`@clerk/fastify`](https://github.com/clerkinc/javascript/tree/main/packages/fastify) From c94e4a1b729c880b1a7a9f9aaf0b43387fa25aaf Mon Sep 17 00:00:00 2001 From: Udoh Jeremiah Date: Sat, 11 Oct 2025 13:19:32 +0100 Subject: [PATCH 1175/1295] Update Fluent-Schema.md (#6360) Add **:** to remove ambiguity from what **NB** means. Signed-off-by: Udoh Jeremiah --- docs/Guides/Fluent-Schema.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Fluent-Schema.md b/docs/Guides/Fluent-Schema.md index 653f6e3df8d..a38037e01cb 100644 --- a/docs/Guides/Fluent-Schema.md +++ b/docs/Guides/Fluent-Schema.md @@ -122,5 +122,5 @@ const schema = { body: bodyJsonSchema } fastify.post('/the/url', { schema }, handler) ``` -NB You can mix up the `$ref-way` and the `replace-way` when using +NB: You can mix up the `$ref-way` and the `replace-way` when using `fastify.addSchema`. From 18710e33251eab138e59c947e48c7d7f38afc2f8 Mon Sep 17 00:00:00 2001 From: Manshu Saini <149303743+manshusainishab@users.noreply.github.com> Date: Mon, 13 Oct 2025 03:33:16 +0530 Subject: [PATCH 1176/1295] chore: add `@fastify/sse` as fastify core plugin to documentation and citgm (#6364) * updated the docs for @fastify/sse * addressing the lint limit * updated citgm.yml * Update docs/Guides/Ecosystem.md Co-authored-by: Aras Abbasi Signed-off-by: Manshu Saini <149303743+manshusainishab@users.noreply.github.com> --------- Signed-off-by: Manshu Saini <149303743+manshusainishab@users.noreply.github.com> Co-authored-by: Aras Abbasi --- .github/workflows/citgm.yml | 1 + docs/Guides/Ecosystem.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml index 90ba18d8220..38bf72f212f 100644 --- a/.github/workflows/citgm.yml +++ b/.github/workflows/citgm.yml @@ -88,6 +88,7 @@ jobs: - '@fastify/secure-session' - '@fastify/sensible' - '@fastify/session' + - '@fastify/sse' - '@fastify/static' - '@fastify/swagger' - '@fastify/swagger-ui' diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 0e1dd4d63bd..33bfca4c752 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -119,6 +119,8 @@ section. HTTP errors and assertions, but also more request and reply methods. - [`@fastify/session`](https://github.com/fastify/session) a session plugin for Fastify. +- [`@fastify/sse`](https://github.com/fastify/sse) Plugin for Server-Sent Events + (SSE) support in Fastify. - [`@fastify/static`](https://github.com/fastify/fastify-static) Plugin for serving static files as fast as possible. - [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) Plugin for From 0b97a6283698cc37aaf80fd1e2cd86e05359843f Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 16 Oct 2025 10:26:51 +0100 Subject: [PATCH 1177/1295] docs(guides/fluent-schema): replace last `nb` usage (#6365) Not everyone understands an acronym for a dead language. Signed-off-by: Frazer Smith --- docs/Guides/Fluent-Schema.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Guides/Fluent-Schema.md b/docs/Guides/Fluent-Schema.md index a38037e01cb..954e7242af8 100644 --- a/docs/Guides/Fluent-Schema.md +++ b/docs/Guides/Fluent-Schema.md @@ -122,5 +122,5 @@ const schema = { body: bodyJsonSchema } fastify.post('/the/url', { schema }, handler) ``` -NB: You can mix up the `$ref-way` and the `replace-way` when using -`fastify.addSchema`. +> ℹ️ Note: You can mix up the `$ref-way` and the `replace-way` +> when using `fastify.addSchema`. From 02ac9e083ea993f3dc446466020387c254fa6249 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 16 Oct 2025 17:42:33 +0100 Subject: [PATCH 1178/1295] style(ci): remove whitespace from concurrency group (#6366) Signed-off-by: Frazer Smith --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb046d4f6b0..c3493905879 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ on: # This allows a subsequently queued workflow run to interrupt previous runs concurrency: - group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" cancel-in-progress: true permissions: From 347b7df29863ba61ba5827c63818302aaaa1d435 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Sun, 19 Oct 2025 16:10:55 +0200 Subject: [PATCH 1179/1295] docs: Fix broken link to TypeBox doc website wrt AJV setup (#6367) Signed-off-by: Melroy van den Berg --- docs/Reference/Type-Providers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index 9406ba8d5af..fbc6e6dfc7c 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -88,8 +88,8 @@ server.get('/route', { ``` See the [TypeBox -documentation](https://github.com/sinclairzx81/typebox#validation) -for setting up AJV to work with TypeBox. +documentation](https://sinclairzx81.github.io/typebox/#/docs/overview/2_setup) +for setting-up AJV to work with TypeBox. ### Zod From 1564559c355b4aa1f00f7371f71f896ebe94f862 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Tue, 21 Oct 2025 15:06:27 +0100 Subject: [PATCH 1180/1295] docs(reference/plugins): mention async plugins (#6357) * docs(reference/plugins): mention async plugins * Apply suggestion from @Fdawgs Signed-off-by: Frazer Smith --------- Signed-off-by: Frazer Smith --- docs/Reference/Encapsulation.md | 6 +++++- docs/Reference/Plugins.md | 13 +++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Encapsulation.md b/docs/Reference/Encapsulation.md index 195a53ab824..0ea5cc6bdfe 100644 --- a/docs/Reference/Encapsulation.md +++ b/docs/Reference/Encapsulation.md @@ -27,7 +27,11 @@ registered within its _grandchild context_. Given that everything in Fastify is a [plugin](./Plugins.md) except for the _root context_, every "context" and "plugin" in this example is a plugin -that can consist of decorators, hooks, plugins, and routes. To put this +that can consist of decorators, hooks, plugins, and routes. As plugins, they +must still signal completion either by returning a Promise (e.g., using `async` functions) +or by calling the `done` function if using the callback style. + +To put this example into concrete terms, consider a basic scenario of a REST API server with three routes: the first route (`/one`) requires authentication, the second route (`/two`) does not, and the third route (`/three`) has access to diff --git a/docs/Reference/Plugins.md b/docs/Reference/Plugins.md index 1f51d0db1e4..6e98a470e0e 100644 --- a/docs/Reference/Plugins.md +++ b/docs/Reference/Plugins.md @@ -171,18 +171,27 @@ export default plugin Creating a plugin is easy. Create a function that takes three parameters: the -`fastify` instance, an `options` object, and the `done` callback. +`fastify` instance, an `options` object, and the `done` callback. Alternatively, +use an `async` function and omit the `done` callback. Example: ```js -module.exports = function (fastify, opts, done) { +module.exports = function callbackPlugin (fastify, opts, done) { fastify.decorate('utility', function () {}) fastify.get('/', handler) done() } + +// Or using async +module.exports = async function asyncPlugin (fastify, opts) { + fastify.decorate('utility', function () {}) + + fastify.get('/', handler) +} ``` + `register` can also be used inside another `register`: ```js module.exports = function (fastify, opts, done) { From 543f3410ba1db398e63a6603119eee971b9b6fc2 Mon Sep 17 00:00:00 2001 From: Emanuel Covelli Date: Thu, 30 Oct 2025 16:59:46 +0100 Subject: [PATCH 1181/1295] chore: add max-len ESLint rule with 120 character limit (#6221) --- eslint.config.js | 18 ++++- fastify.js | 9 ++- lib/four-oh-four.js | 4 +- lib/head-route.js | 4 +- lib/reply.js | 4 +- lib/route.js | 21 +++-- lib/server.js | 25 +++++- test/internals/reply.test.js | 25 +++++- test/schema-validation.test.js | 16 +++- test/types/fastify.test-d.ts | 88 +++++++++++++++----- test/types/hooks.test-d.ts | 7 +- test/types/instance.test-d.ts | 40 ++++++++-- test/types/logger.test-d.ts | 24 ++++-- test/types/plugin.test-d.ts | 30 +++++-- test/types/register.test-d.ts | 141 +++++++++++++++++++++++++-------- test/types/reply.test-d.ts | 29 +++++-- test/types/request.test-d.ts | 31 ++++++-- test/types/route.test-d.ts | 11 ++- 18 files changed, 423 insertions(+), 104 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 2d0202b08d6..e498cc5af4c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -13,7 +13,23 @@ module.exports = [ }), { rules: { - 'comma-dangle': ['error', 'never'] + 'comma-dangle': ['error', 'never'], + 'max-len': ['error', { + code: 120, + tabWidth: 2, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true, + ignoreComments: true, + ignoreTrailingComments: true + }] + } + }, + { + files: ['**/*.d.ts'], + rules: { + 'max-len': 'off' } } ] diff --git a/fastify.js b/fastify.js index d39dfc647de..2f3e54cc5a4 100644 --- a/fastify.js +++ b/fastify.js @@ -713,7 +713,10 @@ function fastify (serverOptions) { function setSchemaController (schemaControllerOpts) { throwIfAlreadyStarted('Cannot call "setSchemaController"!') const old = this[kSchemaController] - const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts)) + const schemaController = SchemaController.buildSchemaController( + old, + Object.assign({}, old.opts, schemaControllerOpts) + ) this[kSchemaController] = schemaController this.getSchema = schemaController.getSchema.bind(schemaController) this.getSchemas = schemaController.getSchemas.bind(schemaController) @@ -755,7 +758,9 @@ function fastify (serverOptions) { function printRoutes (opts = {}) { // includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks - opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta + opts.includeMeta = opts.includeHooks + ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks + : opts.includeMeta return router.printRoutes(opts) } diff --git a/lib/four-oh-four.js b/lib/four-oh-four.js index 917bb8e59a0..ac6dfa6d370 100644 --- a/lib/four-oh-four.js +++ b/lib/four-oh-four.js @@ -150,7 +150,9 @@ function fourOhFour (options) { .map(h => h.bind(this)) context[hook] = toSet.length ? toSet : null } - context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler] + context.errorHandler = opts.errorHandler + ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) + : this[kErrorHandler] }) if (this[kFourOhFourContext] !== null && prefix === '/') { diff --git a/lib/head-route.js b/lib/head-route.js index 0cc4e5a4a56..c7496e23c5a 100644 --- a/lib/head-route.js +++ b/lib/head-route.js @@ -25,7 +25,9 @@ function headRouteOnSendHandler (req, reply, payload, done) { function parseHeadOnSendHandlers (onSendHandlers) { if (onSendHandlers == null) return headRouteOnSendHandler - return Array.isArray(onSendHandlers) ? [...onSendHandlers, headRouteOnSendHandler] : [onSendHandlers, headRouteOnSendHandler] + return Array.isArray(onSendHandlers) + ? [...onSendHandlers, headRouteOnSendHandler] + : [onSendHandlers, headRouteOnSendHandler] } module.exports = { diff --git a/lib/reply.js b/lib/reply.js index 59f16b0b794..5d4ddb636d0 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -166,7 +166,9 @@ Reply.prototype.send = function (payload) { if (!hasContentType) { this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET } - const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength) + const payloadToSend = Buffer.isBuffer(payload) + ? payload + : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength) onSendHook(this, payloadToSend) return this } diff --git a/lib/route.js b/lib/route.js index fce6f633830..3f49c64b3a6 100644 --- a/lib/route.js +++ b/lib/route.js @@ -365,13 +365,17 @@ function buildRouting (options) { this.after((notHandledErr, done) => { // Send context async - context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler] + context.errorHandler = opts.errorHandler + ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) + : this[kErrorHandler] context._parserOptions.limit = opts.bodyLimit || null context.logLevel = opts.logLevel context.logSerializers = opts.logSerializers context.attachValidation = opts.attachValidation context[kReplySerializerDefault] = this[kReplySerializerDefault] - context.schemaErrorFormatter = opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter + context.schemaErrorFormatter = opts.schemaErrorFormatter || + this[kSchemaErrorFormatter] || + context.schemaErrorFormatter // Run hooks and more avvio.once('preReady', () => { @@ -398,12 +402,19 @@ function buildRouting (options) { context.schema = normalizeSchema(context.schema, this.initialConfig) const schemaController = this[kSchemaController] - if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) { + if (!opts.validatorCompiler && ( + opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params + )) { schemaController.setupValidator(this[kOptions]) } try { - const isCustom = typeof opts?.validatorCompiler === 'function' || schemaController.isCustomValidatorCompiler - compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler, isCustom) + const isCustom = typeof opts?.validatorCompiler === 'function' || + schemaController.isCustomValidatorCompiler + compileSchemasForValidation( + context, + opts.validatorCompiler || schemaController.validatorCompiler, + isCustom + ) } catch (error) { throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message) } diff --git a/lib/server.js b/lib/server.js index ed483faa62d..2b8b93591f2 100644 --- a/lib/server.js +++ b/lib/server.js @@ -138,7 +138,14 @@ function createServer (options, httpHandler) { const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet() - return { server, listen, forceCloseConnections, serverHasCloseAllConnections, serverHasCloseHttp2Sessions, keepAliveConnections } + return { + server, + listen, + forceCloseConnections, + serverHasCloseAllConnections, + serverHasCloseHttp2Sessions, + keepAliveConnections + } } function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, onListen) { @@ -227,7 +234,11 @@ function listenCallback (server, listenOptions) { server.removeListener('error', wrap) server.removeListener('listening', wrap) if (!err) { - const address = logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText) + const address = logServerAddress.call( + this, + server, + listenOptions.listenTextResolver || defaultResolveServerListeningText + ) listenOptions.cb(null, address) } else { this[kState].listening = false @@ -276,7 +287,11 @@ function listenPromise (server, listenOptions) { const listeningEventHandler = () => { cleanup() this[kState].listening = true - resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)) + resolve(logServerAddress.call( + this, + server, + listenOptions.listenTextResolver || defaultResolveServerListeningText + )) } function cleanup () { server.removeListener('error', errEventHandler) @@ -318,7 +333,9 @@ function getServerInstance (options, httpHandler) { } // HTTP1 server instance - const server = httpsOptions ? https.createServer(httpsOptions, httpHandler) : http.createServer(options.http, httpHandler) + const server = httpsOptions + ? https.createServer(httpsOptions, httpHandler) + : http.createServer(options.http, httpHandler) server.keepAliveTimeout = options.keepAliveTimeout server.requestTimeout = options.requestTimeout server.setTimeout(options.connectionTimeout) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 0eaced9ea55..eeb61c5e3c4 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -34,7 +34,12 @@ const doGet = async function (url) { test('Once called, Reply should return an object with methods', t => { t.plan(15) const response = { res: 'res' } - const context = { config: { onSend: [] }, schema: {}, _parserOptions: {}, server: { hasConstraintStrategy: () => false, initialConfig: {} } } + const context = { + config: { onSend: [] }, + schema: {}, + _parserOptions: {}, + server: { hasConstraintStrategy: () => false, initialConfig: {} } + } const request = new Request(null, null, null, null, null, context) const reply = new Reply(response, request) t.assert.strictEqual(typeof reply, 'object') @@ -465,7 +470,14 @@ test('Uint16Array without content type should send a application/octet-stream an }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') - t.assert.deepStrictEqual(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(50).fill(0xffffffff)) + t.assert.deepStrictEqual( + new Uint16Array( + res.rawPayload.buffer, + res.rawPayload.byteOffset, + res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT + ), + new Uint16Array(50).fill(0xffffffff) + ) done() }) }) @@ -490,7 +502,14 @@ test('TypedArray with content type should not send application/octet-stream', (t }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-type'], 'text/plain') - t.assert.deepStrictEqual(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(1024).fill(0xffffffff)) + t.assert.deepStrictEqual( + new Uint16Array( + res.rawPayload.buffer, + res.rawPayload.byteOffset, + res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT + ), + new Uint16Array(1024).fill(0xffffffff) + ) done() }) }) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index f2faa40e015..cde8063e111 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -1241,7 +1241,13 @@ test('Custom validator builder override by custom validator compiler', async t = } const ajv1 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_one', type: 'object', validator: () => true }) const ajv2 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_two', type: 'object', validator: () => true }) - const fastify = Fastify({ schemaController: { compilersFactory: { buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) } } }) + const fastify = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) + } + } + }) fastify.setValidatorCompiler((routeSchemaDef) => ajv2.compile(routeSchemaDef.schema)) @@ -1281,7 +1287,13 @@ test('Custom validator builder override by custom validator compiler in child in } const ajv1 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_one', type: 'object', validator: () => true }) const ajv2 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_two', type: 'object', validator: () => true }) - const fastify = Fastify({ schemaController: { compilersFactory: { buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) } } }) + const fastify = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) + } + } + }) fastify.register((embedded, _opts, done) => { embedded.setValidatorCompiler((routeSchemaDef) => ajv2.compile(routeSchemaDef.schema)) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 9ebef411a7e..9a175c40258 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -25,20 +25,53 @@ import { Bindings, ChildLoggerOptions } from '../../types/logger' // FastifyInstance // http server -expectError & Promise>>(fastify()) -expectAssignable & PromiseLike>>(fastify()) -expectType & SafePromiseLike>>(fastify()) -expectType & SafePromiseLike>>(fastify({})) -expectType & SafePromiseLike>>(fastify({ http: {} })) +expectError< + FastifyInstance & + Promise> +>(fastify()) +expectAssignable< + FastifyInstance & + PromiseLike> +>(fastify()) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify()) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({})) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ http: {} })) // https server -expectType & SafePromiseLike>>(fastify({ https: {} })) -expectType & SafePromiseLike>>(fastify({ https: null })) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ https: {} })) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ https: null })) // http2 server -expectType & SafePromiseLike>>(fastify({ http2: true, http2SessionTimeout: 1000 })) -expectType & SafePromiseLike>>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 })) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ http2: true, http2SessionTimeout: 1000 })) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 })) expectType(fastify({ http2: true, https: {} }).inject()) -expectType & SafePromiseLike>>(fastify({ schemaController: {} })) -expectType & SafePromiseLike>>( +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ schemaController: {} })) +expectType< + FastifyInstance & + SafePromiseLike> +>( fastify({ schemaController: { compilersFactory: {} @@ -61,13 +94,18 @@ expectAssignable({ query: '' }) fastify({ http2: true, https: {} }).inject().then((resp) => { expectAssignable(resp) }) -const lightMyRequestCallback: LightMyRequestCallback = (err: Error | undefined, response: LightMyRequestResponse | undefined) => { +const lightMyRequestCallback: LightMyRequestCallback = ( + err: Error | undefined, + response: LightMyRequestResponse | undefined +) => { if (err) throw err } fastify({ http2: true, https: {} }).inject({}, lightMyRequestCallback) // server options -expectAssignable>(fastify({ http2: true })) +expectAssignable< + FastifyInstance +>(fastify({ http2: true })) expectAssignable(fastify({ ignoreTrailingSlash: true })) expectAssignable(fastify({ ignoreDuplicateSlashes: true })) expectAssignable(fastify({ connectionTimeout: 1000 })) @@ -81,11 +119,17 @@ expectAssignable(fastify({ requestIdLogLabel: 'request-id' })) expectAssignable(fastify({ onProtoPoisoning: 'error' })) expectAssignable(fastify({ onConstructorPoisoning: 'error' })) expectAssignable(fastify({ serializerOpts: { rounding: 'ceil' } })) -expectAssignable(fastify({ serializerOpts: { ajv: { missingRefs: 'ignore' } } })) +expectAssignable( + fastify({ serializerOpts: { ajv: { missingRefs: 'ignore' } } }) +) expectAssignable(fastify({ serializerOpts: { schema: {} } })) expectAssignable(fastify({ serializerOpts: { otherProp: {} } })) -expectAssignable>(fastify({ logger: true })) -expectAssignable>(fastify({ logger: true })) +expectAssignable< + FastifyInstance +>(fastify({ logger: true })) +expectAssignable< + FastifyInstance +>(fastify({ logger: true })) expectAssignable>(fastify({ logger: { level: 'info', @@ -126,7 +170,9 @@ const customLogger = { debug: () => { }, child: () => customLogger } -expectAssignable>(fastify({ logger: customLogger })) +expectAssignable< + FastifyInstance +>(fastify({ logger: customLogger })) expectAssignable(fastify({ serverFactory: () => http.createServer() })) expectAssignable(fastify({ caseSensitive: true })) expectAssignable(fastify({ requestIdHeader: 'request-id' })) @@ -221,7 +267,13 @@ expectAssignable(fastify({ })) expectAssignable(fastify({ - childLoggerFactory: function (this: FastifyInstance, logger: FastifyBaseLogger, bindings: Bindings, opts: ChildLoggerOptions, req: RawRequestDefaultExpression) { + childLoggerFactory: function ( + this: FastifyInstance, + logger: FastifyBaseLogger, + bindings: Bindings, + opts: ChildLoggerOptions, + req: RawRequestDefaultExpression + ) { expectType(logger) expectType(bindings) expectType(opts) diff --git a/test/types/hooks.test-d.ts b/test/types/hooks.test-d.ts index 72491fb4cc0..312150eb9cf 100644 --- a/test/types/hooks.test-d.ts +++ b/test/types/hooks.test-d.ts @@ -416,7 +416,12 @@ server.route({ expectType(request) expectType(reply) expectType(payload) - expectType<(err?: TError | null | undefined, res?: RequestPayload | undefined) => void>(done) + expectType< + ( + err?: TError | null | undefined, + res?: RequestPayload | undefined + ) => void + >(done) }, preValidation: (request, reply, done) => { expectType(request) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index e59c8d00d62..b975249fec8 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -127,24 +127,44 @@ server.setErrorHandler(async (error, request, reply) function notFoundHandler (request: FastifyRequest, reply: FastifyReply) {} async function notFoundAsyncHandler (request: FastifyRequest, reply: FastifyReply) {} -function notFoundpreHandlerHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) { done() } -async function notFoundpreHandlerAsyncHandler (request: FastifyRequest, reply: FastifyReply) {} -function notFoundpreValidationHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) { done() } -async function notFoundpreValidationAsyncHandler (request: FastifyRequest, reply: FastifyReply) {} +function notFoundpreHandlerHandler ( + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction +) { done() } +async function notFoundpreHandlerAsyncHandler ( + request: FastifyRequest, + reply: FastifyReply +) {} +function notFoundpreValidationHandler ( + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction +) { done() } +async function notFoundpreValidationAsyncHandler ( + request: FastifyRequest, + reply: FastifyReply +) {} server.setNotFoundHandler(notFoundHandler) server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler }, notFoundHandler) server.setNotFoundHandler({ preHandler: notFoundpreHandlerAsyncHandler }, notFoundHandler) server.setNotFoundHandler({ preValidation: notFoundpreValidationHandler }, notFoundHandler) server.setNotFoundHandler({ preValidation: notFoundpreValidationAsyncHandler }, notFoundHandler) -server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler }, notFoundHandler) +server.setNotFoundHandler( + { preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler }, + notFoundHandler +) server.setNotFoundHandler(notFoundAsyncHandler) server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler }, notFoundAsyncHandler) server.setNotFoundHandler({ preHandler: notFoundpreHandlerAsyncHandler }, notFoundAsyncHandler) server.setNotFoundHandler({ preValidation: notFoundpreValidationHandler }, notFoundAsyncHandler) server.setNotFoundHandler({ preValidation: notFoundpreValidationAsyncHandler }, notFoundAsyncHandler) -server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler }, notFoundAsyncHandler) +server.setNotFoundHandler( + { preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler }, + notFoundAsyncHandler +) server.setNotFoundHandler(function (_, reply) { return reply.send('') @@ -280,7 +300,13 @@ expectAssignable( }) ) -function childLoggerFactory (this: FastifyInstance, logger: FastifyBaseLogger, bindings: Bindings, opts: ChildLoggerOptions, req: RawRequestDefaultExpression) { +function childLoggerFactory ( + this: FastifyInstance, + logger: FastifyBaseLogger, + bindings: Bindings, + opts: ChildLoggerOptions, + req: RawRequestDefaultExpression +) { return logger.child(bindings, opts) } server.setChildLoggerFactory(childLoggerFactory) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index a5e2203b4d0..e084a46e9d1 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -17,12 +17,24 @@ expectType(fastify().log) class Foo {} ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(logLevel => { - expectType(fastify().log[logLevel as LogLevel]) - expectType(fastify().log[logLevel as LogLevel]('')) - expectType(fastify().log[logLevel as LogLevel]({})) - expectType(fastify().log[logLevel as LogLevel]({ foo: 'bar' })) - expectType(fastify().log[logLevel as LogLevel](new Error())) - expectType(fastify().log[logLevel as LogLevel](new Foo())) + expectType( + fastify().log[logLevel as LogLevel] + ) + expectType( + fastify().log[logLevel as LogLevel]('') + ) + expectType( + fastify().log[logLevel as LogLevel]({}) + ) + expectType( + fastify().log[logLevel as LogLevel]({ foo: 'bar' }) + ) + expectType( + fastify().log[logLevel as LogLevel](new Error()) + ) + expectType( + fastify().log[logLevel as LogLevel](new Foo()) + ) }) interface CustomLogger extends FastifyBaseLogger { diff --git a/test/types/plugin.test-d.ts b/test/types/plugin.test-d.ts index 30358ff50ab..f5e48217b3a 100644 --- a/test/types/plugin.test-d.ts +++ b/test/types/plugin.test-d.ts @@ -21,8 +21,15 @@ const testPluginOptsAsync: FastifyPluginAsync = async function (ins expectType(opts) } -const testPluginOptsWithType = (instance: FastifyInstance, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { } -const testPluginOptsWithTypeAsync = async (instance: FastifyInstance, opts: FastifyPluginOptions) => { } +const testPluginOptsWithType = ( + instance: FastifyInstance, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginOptsWithTypeAsync = async ( + instance: FastifyInstance, + opts: FastifyPluginOptions +) => { } expectError(fastify().register(testPluginOpts, {})) // error because missing required options from generic declaration expectError(fastify().register(testPluginOptsAsync, {})) // error because missing required options from generic declaration @@ -43,16 +50,27 @@ expectAssignable(fastify().register(testPluginCallback, {})) const testPluginAsync: FastifyPluginAsync = async function (instance, opts) { } expectAssignable(fastify().register(testPluginAsync, {})) -expectAssignable(fastify().register(function (instance, opts): Promise { return Promise.resolve() })) +expectAssignable( + fastify().register(function (instance, opts): Promise { return Promise.resolve() }) +) expectAssignable(fastify().register(async function (instance, opts) { }, () => { })) expectAssignable(fastify().register(async function (instance, opts) { }, { logLevel: 'info', prefix: 'foobar' })) expectError(fastify().register(function (instance, opts, done) { }, { ...testOptions, logLevel: '' })) // must use a valid logLevel const httpsServer = fastify({ https: {} }) -expectError & Promise>>(httpsServer) -expectAssignable & PromiseLike>>(httpsServer) -expectType & SafePromiseLike>>(httpsServer) +expectError< + FastifyInstance & + Promise> +>(httpsServer) +expectAssignable< + FastifyInstance & + PromiseLike> +>(httpsServer) +expectType< + FastifyInstance & + SafePromiseLike> +>(httpsServer) // Chainable httpsServer diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index 3822763bfac..0bcf5307a5e 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -9,7 +9,11 @@ const testPluginAsync: FastifyPluginAsync = async function (instance, opts) { } const testPluginOpts: FastifyPluginCallback = function (instance, opts, done) { } const testPluginOptsAsync: FastifyPluginAsync = async function (instance, opts) { } -const testPluginOptsWithType = (instance: FastifyInstance, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { } +const testPluginOptsWithType = ( + instance: FastifyInstance, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } const testPluginOptsWithTypeAsync = async (instance: FastifyInstance, opts: FastifyPluginOptions) => { } interface TestOptions extends FastifyPluginOptions { @@ -46,8 +50,15 @@ const serverWithHttp2 = fastify({ http2: true }) type ServerWithHttp2 = FastifyInstance const testPluginWithHttp2: FastifyPluginCallback = function (instance, opts, done) { } const testPluginWithHttp2Async: FastifyPluginAsync = async function (instance, opts) { } -const testPluginWithHttp2WithType = (instance: ServerWithHttp2, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { } -const testPluginWithHttp2WithTypeAsync = async (instance: ServerWithHttp2, opts: FastifyPluginOptions) => { } +const testPluginWithHttp2WithType = ( + instance: ServerWithHttp2, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginWithHttp2WithTypeAsync = async ( + instance: ServerWithHttp2, + opts: FastifyPluginOptions +) => { } const testOptions: TestOptions = { option1: 'a', option2: false @@ -82,11 +93,32 @@ expectAssignable(serverWithHttp2.register(async (instance: Serv // With Type Provider type TestTypeProvider = { schema: 'test', validator: 'test', serializer: 'test' } const serverWithTypeProvider = fastify().withTypeProvider() -type ServerWithTypeProvider = FastifyInstance -const testPluginWithTypeProvider: FastifyPluginCallback = function (instance, opts, done) { } -const testPluginWithTypeProviderAsync: FastifyPluginAsync = async function (instance, opts) { } -const testPluginWithTypeProviderWithType = (instance: ServerWithTypeProvider, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { } -const testPluginWithTypeProviderWithTypeAsync = async (instance: ServerWithTypeProvider, opts: FastifyPluginOptions) => { } +type ServerWithTypeProvider = FastifyInstance< + Server, + IncomingMessage, + ServerResponse, + FastifyLoggerInstance, + TestTypeProvider +> +const testPluginWithTypeProvider: FastifyPluginCallback< + TestOptions, + RawServerDefault, + TestTypeProvider +> = function (instance, opts, done) { } +const testPluginWithTypeProviderAsync: FastifyPluginAsync< + TestOptions, + RawServerDefault, + TestTypeProvider +> = async function (instance, opts) { } +const testPluginWithTypeProviderWithType = ( + instance: ServerWithTypeProvider, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginWithTypeProviderWithTypeAsync = async ( + instance: ServerWithTypeProvider, + opts: FastifyPluginOptions +) => { } expectAssignable(serverWithTypeProvider.register(testPluginCallback)) expectAssignable(serverWithTypeProvider.register(testPluginAsync)) expectAssignable(serverWithTypeProvider.register(testPluginOpts)) @@ -129,34 +161,77 @@ const customLogger = { const serverWithTypeProviderAndLogger = fastify({ loggerInstance: customLogger }).withTypeProvider() -type ServerWithTypeProviderAndLogger = FastifyInstance -const testPluginWithTypeProviderAndLogger: FastifyPluginCallback = function (instance, opts, done) { } -const testPluginWithTypeProviderAndLoggerAsync: FastifyPluginAsync = async function (instance, opts) { } -const testPluginWithTypeProviderAndLoggerWithType = (instance: ServerWithTypeProviderAndLogger, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { } -const testPluginWithTypeProviderAndLoggerWithTypeAsync = async (instance: ServerWithTypeProviderAndLogger, opts: FastifyPluginOptions) => { } +type ServerWithTypeProviderAndLogger = FastifyInstance< + Server, + IncomingMessage, + ServerResponse, + typeof customLogger, + TestTypeProvider +> +const testPluginWithTypeProviderAndLogger: FastifyPluginCallback< + TestOptions, + RawServerDefault, + TestTypeProvider, + typeof customLogger +> = function (instance, opts, done) { } +const testPluginWithTypeProviderAndLoggerAsync: FastifyPluginAsync< + TestOptions, + RawServerDefault, + TestTypeProvider, + typeof customLogger +> = async function (instance, opts) { } +const testPluginWithTypeProviderAndLoggerWithType = ( + instance: ServerWithTypeProviderAndLogger, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginWithTypeProviderAndLoggerWithTypeAsync = async ( + instance: ServerWithTypeProviderAndLogger, + opts: FastifyPluginOptions +) => { } expectAssignable(serverWithTypeProviderAndLogger.register(testPluginCallback)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginAsync)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOpts)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsAsync)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsWithType)) expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsWithTypeAsync)) -// @ts-expect-error -expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger)) -expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger, testOptions)) -// @ts-expect-error -expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync)) -expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync, testOptions)) -expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithType)) -expectAssignable(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithTypeAsync)) -expectAssignable(serverWithTypeProviderAndLogger.register((instance) => { - expectAssignable(instance) -})) -expectAssignable(serverWithTypeProviderAndLogger.register((instance: ServerWithTypeProviderAndLogger) => { - expectAssignable(instance) -})) -expectAssignable(serverWithTypeProviderAndLogger.register(async (instance) => { - expectAssignable(instance) -})) -expectAssignable(serverWithTypeProviderAndLogger.register(async (instance: ServerWithTypeProviderAndLogger) => { - expectAssignable(instance) -})) +expectAssignable( + // @ts-expect-error + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger, testOptions) +) +expectAssignable( + // @ts-expect-error + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync, testOptions) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithType) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithTypeAsync) +) +expectAssignable( + serverWithTypeProviderAndLogger.register((instance) => { + expectAssignable(instance) + }) +) +expectAssignable( + serverWithTypeProviderAndLogger.register((instance: ServerWithTypeProviderAndLogger) => { + expectAssignable(instance) + }) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(async (instance) => { + expectAssignable(instance) + }) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(async (instance: ServerWithTypeProviderAndLogger) => { + expectAssignable(instance) + }) +) diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index e58fa064a43..872a129827d 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -20,7 +20,9 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType(reply.elapsedTime) expectType(reply.statusCode) expectType(reply.sent) - expectType<(hints: Record, callback?: (() => void) | undefined) => void>(reply.writeEarlyHints) + expectType< + (hints: Record, callback?: (() => void) | undefined) => void + >(reply.writeEarlyHints) expectType<((payload?: unknown) => FastifyReply)>(reply.send) expectAssignable<(key: string, value: any) => FastifyReply>(reply.header) expectAssignable<(values: { [key: string]: any }) => FastifyReply>(reply.headers) @@ -35,14 +37,29 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType<(fn: (payload: any) => string) => FastifyReply>(reply.serializer) expectType<(payload: any) => string | ArrayBuffer | Buffer>(reply.serialize) expectType<(fulfilled: () => void, rejected: (err: Error) => void) => void>(reply.then) - expectType<(key: string, fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise) | ((reply: FastifyReply, payload: string | Buffer | null, done: (err: Error | null, value?: string) => void) => void)) => FastifyReply>(reply.trailer) + expectType< + ( + key: string, + fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise) | + ((reply: FastifyReply, payload: string | Buffer | null, + done: (err: Error | null, value?: string) => void) => void) + ) => FastifyReply + >(reply.trailer) expectType<(key: string) => boolean>(reply.hasTrailer) expectType<(key: string) => FastifyReply>(reply.removeTrailer) expectType(reply.server) - expectAssignable<((httpStatus: string) => DefaultSerializationFunction | undefined)>(reply.getSerializationFunction) - expectAssignable<((schema: { [key: string]: unknown }) => DefaultSerializationFunction | undefined)>(reply.getSerializationFunction) - expectAssignable<((schema: { [key: string]: unknown }, httpStatus?: string) => DefaultSerializationFunction)>(reply.compileSerializationSchema) - expectAssignable<((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown)>(reply.serializeInput) + expectAssignable< + ((httpStatus: string) => DefaultSerializationFunction | undefined) + >(reply.getSerializationFunction) + expectAssignable< + ((schema: { [key: string]: unknown }) => DefaultSerializationFunction | undefined) + >(reply.getSerializationFunction) + expectAssignable< + ((schema: { [key: string]: unknown }, httpStatus?: string) => DefaultSerializationFunction) + >(reply.compileSerializationSchema) + expectAssignable< + ((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown) + >(reply.serializeInput) expectAssignable<((input: { [key: string]: unknown }, httpStatus: string) => unknown)>(reply.serializeInput) expectType(reply.routeOptions.config) expectType(reply.getDecorator('foo')) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 7dd58fdc5ce..437112ca8c7 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -91,7 +91,9 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.server) expectAssignable<(httpPart: HTTPRequestPart) => ExpectedGetValidationFunction>(request.getValidationFunction) expectAssignable<(schema: { [key: string]: unknown }) => ExpectedGetValidationFunction>(request.getValidationFunction) - expectAssignable<(input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) + expectAssignable< + (input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean + >(request.validateInput) expectAssignable<(input: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) expectType(request.getDecorator('foo')) expectType(request.setDecorator('foo', 'hello')) @@ -99,14 +101,25 @@ const getHandler: RouteHandler = function (request, _reply) { expectError(request.setDecorator('foo', true)) } -const getHandlerWithCustomLogger: RouteHandlerMethod = function (request, _reply) { +const getHandlerWithCustomLogger: RouteHandlerMethod< + RawServerDefault, + RawRequestDefaultExpression, + RawReplyDefaultExpression, + RouteGenericInterface, + ContextConfigDefault, + FastifySchema, + FastifyTypeProviderDefault, + CustomLoggerInterface +> = function (request, _reply) { expectType(request.log) } const postHandler: Handler = function (request) { expectType(request.body) expectType(request.params) - expectType(request.headers) + expectType( + request.headers + ) expectType(request.query) expectType(request.body.content) expectType(request.query.from) @@ -154,15 +167,21 @@ const customLogger: CustomLoggerInterface = { const serverWithCustomLogger = fastify({ loggerInstance: customLogger }) expectError< FastifyInstance -& Promise> +& Promise< + FastifyInstance +> >(serverWithCustomLogger) expectAssignable< FastifyInstance -& PromiseLike> +& PromiseLike< + FastifyInstance +> >(serverWithCustomLogger) expectType< FastifyInstance -& SafePromiseLike> +& SafePromiseLike< + FastifyInstance +> >(serverWithCustomLogger) serverWithCustomLogger.get('/get', getHandlerWithCustomLogger) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index bc9f58d31e6..e60dfb9ac23 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -23,7 +23,16 @@ declare module '../../fastify' { } /* eslint-disable @typescript-eslint/no-unused-vars */ - interface FastifyRequest { + interface FastifyRequest< + RouteGeneric, + RawServer, + RawRequest, + SchemaCompiler, + TypeProvider, + ContextConfig, + Logger, + RequestType + > { message: ContextConfig extends { includeMessage: true } ? string : null; From b124d6fb45cb322c4224550f3d9a7c80049d25eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:12:39 +0000 Subject: [PATCH 1182/1295] chore: Bump actions/dependency-review-action from 4.8.0 to 4.8.1 (#6374) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.8.0 to 4.8.1. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/56339e523c0409420f6c2c9a2f4292bbb3c07dd3...40c09b7dc99638e5ddb0bfd91c1673effc064d8a) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-version: 4.8.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3493905879..f156772229a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Dependency review - uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0 + uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1 check-licenses: name: Check licenses From 55653d6aa052607bd6968b660627905e70a4c2b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:13:13 +0000 Subject: [PATCH 1183/1295] chore: Bump pino from 9.14.0 to 10.1.0 in the dependencies-major group (#6378) Bumps the dependencies-major group with 1 update: [pino](https://github.com/pinojs/pino). Updates `pino` from 9.14.0 to 10.1.0 - [Release notes](https://github.com/pinojs/pino/releases) - [Commits](https://github.com/pinojs/pino/compare/v9.14.0...v10.1.0) --- updated-dependencies: - dependency-name: pino dependency-version: 10.1.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c3982a015e9..55cc885f6d2 100644 --- a/package.json +++ b/package.json @@ -211,7 +211,7 @@ "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", - "pino": "^9.0.0", + "pino": "^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", From 106bb6bf6a6d272dc36ca990478e16efca60c83a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:14:18 +0000 Subject: [PATCH 1184/1295] chore: Bump pnpm/action-setup from 4.1.0 to 4.2.0 (#6375) Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/a7487c7e89a18df4991f7f222e4898a00d66ddda...41ff72655975bd51cab0327fa583b6e92b6d3061) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/integration-alternative-runtimes.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/package-manager-ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index 44fab8b6692..3c6e1c222c0 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -45,7 +45,7 @@ jobs: nsolid-version: ${{ matrix.nsolid-version }} - name: Install Pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 with: version: ${{ matrix.pnpm-version }} diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ff522ae0f7d..f51d0c3956b 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -41,7 +41,7 @@ jobs: check-latest: true - name: Install Pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 with: version: ${{ matrix.pnpm-version }} diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 294d4666cf3..8d136eeeac8 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -33,7 +33,7 @@ jobs: check-latest: true - name: Install with pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 with: version: ${{ matrix.pnpm-version }} From 5ebe3277a2acf097cbaf1db97067d8d9bcba29ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:14:31 +0000 Subject: [PATCH 1185/1295] chore: Bump tsd in the dev-dependencies-typescript group (#6346) Bumps the dev-dependencies-typescript group with 1 update: [tsd](https://github.com/tsdjs/tsd). Updates `tsd` from 0.32.0 to 0.33.0 - [Release notes](https://github.com/tsdjs/tsd/releases) - [Commits](https://github.com/tsdjs/tsd/compare/v0.32.0...v0.33.0) --- updated-dependencies: - dependency-name: tsd dependency-version: 0.33.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies-typescript ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 55cc885f6d2..12c99979769 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,7 @@ "node-forge": "^1.3.1", "proxyquire": "^2.1.3", "split2": "^4.2.0", - "tsd": "^0.32.0", + "tsd": "^0.33.0", "typescript": "~5.9.2", "undici": "^7.11.0", "vary": "^1.1.2", From 810e3d548eeceb32c948a0fbfdd90d6d1e6098ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:14:46 +0000 Subject: [PATCH 1186/1295] chore: Bump lycheeverse/lychee-action from 2.6.1 to 2.7.0 (#6377) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.6.1 to 2.7.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/885c65f3dc543b57c898c8099f4e08c8afd178a2...a8c4c7cb88f0c7386610c35eb25108e448569cb0) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-version: 2.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 2ad713716f9..8db783dc60c 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -24,7 +24,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2 # v2.6.1 + uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2.7.0 with: fail: true # As external links behavior is not predictable, we check only internal links From dd02e428ddbfe407a2d114d431e60a6a10335ea0 Mon Sep 17 00:00:00 2001 From: "JH.Lee" Date: Mon, 3 Nov 2025 19:42:10 +0900 Subject: [PATCH 1187/1295] fix: handle web stream payload in HEAD route (#6372) * fix: handle web stream in HEAD route * test: add web stream test for HEAD route * test: add web stream cancel error test for HEAD route * feat: provide reason for stream cancellation in HEAD route --------- Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- lib/head-route.js | 10 +++++++++ test/route.6.test.js | 21 ++++++++++++++++++- test/route.7.test.js | 49 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/lib/head-route.js b/lib/head-route.js index c7496e23c5a..63f3519c1e0 100644 --- a/lib/head-route.js +++ b/lib/head-route.js @@ -7,6 +7,7 @@ function headRouteOnSendHandler (req, reply, payload, done) { return } + // node:stream if (typeof payload.resume === 'function') { payload.on('error', (err) => { reply.log.error({ err }, 'Error on Stream found for HEAD route') @@ -16,6 +17,15 @@ function headRouteOnSendHandler (req, reply, payload, done) { return } + // node:stream/web + if (typeof payload.getReader === 'function') { + payload.cancel('Stream cancelled by HEAD route').catch((err) => { + reply.log.error({ err }, 'Error on Stream found for HEAD route') + }) + done(null, null) + return + } + const size = '' + Buffer.byteLength(payload) reply.header('content-length', size) diff --git a/test/route.6.test.js b/test/route.6.test.js index 32858e88672..07ea4a03654 100644 --- a/test/route.6.test.js +++ b/test/route.6.test.js @@ -1,6 +1,7 @@ 'use strict' const stream = require('node:stream') +const { ReadableStream } = require('node:stream/web') const { test } = require('node:test') const Fastify = require('..') @@ -90,13 +91,14 @@ test('Will not create a HEAD route that is not GET', async t => { }) test('HEAD route should handle properly each response type', async t => { - t.plan(20) + t.plan(24) const fastify = Fastify({ exposeHeadRoutes: true }) const resString = 'Found me!' const resJSON = { here: 'is Johnny' } const resBuffer = Buffer.from('I am a buffer!') const resStream = stream.Readable.from('I am a stream!') + const resWebStream = ReadableStream.from('I am a web stream!') fastify.route({ method: 'GET', @@ -139,6 +141,14 @@ test('HEAD route should handle properly each response type', async t => { } }) + fastify.route({ + method: 'GET', + path: '/web-stream', + handler: (req, reply) => { + return resWebStream + } + }) + let res = await fastify.inject({ method: 'HEAD', url: '/json' @@ -183,6 +193,15 @@ test('HEAD route should handle properly each response type', async t => { t.assert.strictEqual(res.headers['content-type'], undefined) t.assert.strictEqual(res.headers['content-length'], undefined) t.assert.strictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/web-stream' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + t.assert.strictEqual(res.headers['content-length'], undefined) + t.assert.strictEqual(res.body, '') }) test('HEAD route should respect custom onSend handlers', async t => { diff --git a/test/route.7.test.js b/test/route.7.test.js index 333f4d8e4a2..44e7a091556 100644 --- a/test/route.7.test.js +++ b/test/route.7.test.js @@ -1,6 +1,7 @@ 'use strict' const stream = require('node:stream') +const { ReadableStream } = require('node:stream/web') const split = require('split2') const { test } = require('node:test') const Fastify = require('..') @@ -47,6 +48,54 @@ test("HEAD route should handle stream.on('error')", (t, done) => { }) }) +test('HEAD route should handle ReadableStream.cancel() error', (t, done) => { + t.plan(7) + + const logStream = split(JSON.parse) + const expectedError = new Error('Cancel error!') + const fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + + fastify.route({ + method: 'GET', + path: '/web-stream', + exposeHeadRoute: true, + handler: (req, reply) => { + const webStream = new ReadableStream({ + start (controller) { + controller.enqueue('Hello from web stream!') + }, + cancel (reason) { + t.assert.strictEqual(reason, 'Stream cancelled by HEAD route') + throw expectedError + } + }) + return webStream + } + }) + + logStream.once('data', line => { + const { message, stack } = expectedError + t.assert.deepStrictEqual(line.err, { type: 'Error', message, stack }) + t.assert.strictEqual(line.msg, 'Error on Stream found for HEAD route') + t.assert.strictEqual(line.level, 50) + }) + + fastify.inject({ + method: 'HEAD', + url: '/web-stream' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + done() + }) +}) + test('HEAD route should be exposed by default', async t => { t.plan(5) From 3120cde1a8c0628ebb6e8b2120883e32f2cd209a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 05:06:52 +0100 Subject: [PATCH 1188/1295] chore: Bump actions/setup-node from 5 to 6 (#6376) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/citgm-package.yml | 2 +- .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/md-lint.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f156772229a..014d061076a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: 'lts/*' cache: 'npm' @@ -81,7 +81,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: 'lts/*' cache: 'npm' @@ -130,7 +130,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -158,7 +158,7 @@ jobs: persist-credentials: false - name: Use Custom Node.js Version - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ github.event.inputs.nodejs-version }} cache: 'npm' @@ -187,7 +187,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version: '20' cache: 'npm' @@ -216,7 +216,7 @@ jobs: with: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index 925b7c7f665..2e36b77537a 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -62,7 +62,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} cache: 'npm' diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 91ed99181ea..54e489c58d6 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -16,7 +16,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index 940526f0863..cdbb92ed663 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -16,7 +16,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version: 'lts/*' cache: 'npm' diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index f51d0c3956b..b58d1bdb23b 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -35,7 +35,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} check-latest: true diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index 423da287e1f..fbadd1d4833 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: Setup Node - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: 'lts/*' check-latest: true diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index 8d136eeeac8..d3c2fbe6d9c 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -27,7 +27,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} check-latest: true @@ -62,7 +62,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} check-latest: true From b5958b3b8ed6fce850ebaabbc4a8a3e5f42b389b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 04:15:09 +0000 Subject: [PATCH 1189/1295] chore: Bump borp from 0.20.2 to 0.21.0 (#6379) Bumps [borp](https://github.com/mcollina/borp) from 0.20.2 to 0.21.0. - [Release notes](https://github.com/mcollina/borp/releases) - [Commits](https://github.com/mcollina/borp/commits) --- updated-dependencies: - dependency-name: borp dependency-version: 0.21.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12c99979769..4539d145bdf 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "autocannon": "^8.0.0", - "borp": "^0.20.0", + "borp": "^0.21.0", "branch-comparer": "^1.1.0", "concurrently": "^9.1.2", "cross-env": "^10.0.0", From e1aee4b872aaeabbd4f7074794c1015a518f8d9a Mon Sep 17 00:00:00 2001 From: Jean <110341611+jean-michelet@users.noreply.github.com> Date: Wed, 5 Nov 2025 08:24:31 +0100 Subject: [PATCH 1190/1295] fix: parse ipv6 hostname (#6373) --- lib/request.js | 5 +++++ test/server.test.js | 26 ++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/request.js b/lib/request.js index 66704d42745..c9640fdcc47 100644 --- a/lib/request.js +++ b/lib/request.js @@ -226,6 +226,11 @@ Object.defineProperties(Request.prototype, { }, hostname: { get () { + // Check for IPV6 Host + if (this.host[0] === '[') { + return this.host.slice(0, this.host.indexOf(']') + 1) + } + return this.host.split(':', 1)[0] } }, diff --git a/test/server.test.js b/test/server.test.js index 6e11adc87ec..7fa2b4ad9b4 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -67,13 +67,13 @@ test('listen should reject string port', async (t) => { }) test('Test for hostname and port', async (t) => { + t.plan(3) const app = Fastify() t.after(() => app.close()) app.get('/host', (req, res) => { - const host = 'localhost:8000' - t.assert.strictEqual(req.host, host) - t.assert.strictEqual(req.hostname, req.host.split(':')[0]) - t.assert.strictEqual(req.port, Number(req.host.split(':')[1])) + t.assert.strictEqual(req.host, 'localhost:8000') + t.assert.strictEqual(req.hostname, 'localhost') + t.assert.strictEqual(req.port, 8000) res.send('ok') }) @@ -81,6 +81,24 @@ test('Test for hostname and port', async (t) => { await fetch('http://localhost:8000/host') }) +test('Test for IPV6 port', async (t) => { + t.plan(3) + const app = Fastify() + t.after(() => app.close()) + app.get('/host', (req, res) => { + t.assert.strictEqual(req.host, '[::1]:3040') + t.assert.strictEqual(req.hostname, '[::1]') + t.assert.strictEqual(req.port, 3040) + res.send('ok') + }) + + await app.listen({ + port: 3040, + host: '::1' + }) + await fetch('http://[::1]:3040/host') +}) + test('abort signal', async t => { await t.test('should close server when aborted after', (t, end) => { t.plan(2) From d338dca5ab24a4ce0175b4333efe46859ceaffab Mon Sep 17 00:00:00 2001 From: Emanuel Covelli Date: Sat, 8 Nov 2025 15:16:10 +0100 Subject: [PATCH 1191/1295] fix: consistent error handling for custom validators in async validation contexts (#6247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: consistent error handling for custom validators in async validation contexts - Wrap validatorFunction call in try-catch to handle synchronous throws - Ensures thrown errors are caught consistently in both sync and async validation contexts - Fixes issue where custom validators throwing errors in async preValidation hooks would cause unhandled promise rejections instead of triggering error handlers - Add test case to verify error handling consistency Fixes #6214 * fix: address review suggestions for PR #6247 * fix: address review suggestions for PR #6247 - Add err.statusCode = 500 inside catch block of validateParam() - Update test expectation from 500 to 200 status code - Ensures thrown errors are properly handled as internal errors * fix: update error handling in async preValidation tests - Change response status code from 200 to 500 when custom validation fails - Ensure error messages are sent correctly in the response * chore: improve test * Apply suggestion from @jean-michelet Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> Signed-off-by: Manuel Spigolon * feedback --------- Signed-off-by: Manuel Spigolon Co-authored-by: Gürgün Dayıoğlu Co-authored-by: Manuel Spigolon Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> Co-authored-by: Manuel Spigolon --- lib/validation.js | 10 +++- test/validation-error-handling.test.js | 69 +++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/lib/validation.js b/lib/validation.js index 047d1c8a812..a7ac2aeadea 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -117,7 +117,15 @@ function compileSchemasForValidation (context, compile, isCustom) { function validateParam (validatorFunction, request, paramName) { const isUndefined = request[paramName] === undefined - const ret = validatorFunction && validatorFunction(isUndefined ? null : request[paramName]) + let ret + + try { + ret = validatorFunction?.(isUndefined ? null : request[paramName]) + } catch (err) { + // If validator throws synchronously, ensure it propagates as an internal error + err.statusCode = 500 + return err + } if (ret && typeof ret.then === 'function') { return ret diff --git a/test/validation-error-handling.test.js b/test/validation-error-handling.test.js index 6177332dd71..f8933f3e4d4 100644 --- a/test/validation-error-handling.test.js +++ b/test/validation-error-handling.test.js @@ -1,6 +1,6 @@ 'use strict' -const { test } = require('node:test') +const { describe, test } = require('node:test') const Joi = require('joi') const Fastify = require('..') @@ -831,3 +831,70 @@ test('plugin override', async (t) => { }) t.assert.strictEqual(response5.statusCode, 400) }) + +describe('sync and async must work in the same way', () => { + // Route with custom validator that throws + const throwingRouteValidator = { + schema: { + body: { + type: 'object', + properties: { name: { type: 'string' } } + } + }, + validatorCompiler: () => { + return function (inputData) { + // This custom validator throws a sync error instead of returning `{ error }` + throw new Error('Custom validation failed') + } + }, + handler (request, reply) { reply.send({ success: true }) } + } + + test('async preValidation with custom validator should trigger error handler when validator throws', async (t) => { + t.plan(4) + + const fastify = Fastify() + fastify.setErrorHandler((error, request, reply) => { + t.assert.ok(error instanceof Error, 'error should be an Error instance') + t.assert.strictEqual(error.message, 'Custom validation failed') + reply.status(500).send({ error: error.message }) + }) + + // Add async preValidation hook + fastify.addHook('preValidation', async (request, reply) => { + await Promise.resolve('ok') + }) + fastify.post('/async', throwingRouteValidator) + + const response = await fastify.inject({ + method: 'POST', + url: '/async', + payload: { name: 'test' } + }) + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(response.json(), { error: 'Custom validation failed' }) + }) + + test('sync preValidation with custom validator should trigger error handler when validator throws', async (t) => { + t.plan(4) + + const fastify = Fastify() + fastify.setErrorHandler((error, request, reply) => { + t.assert.ok(error instanceof Error, 'error should be an Error instance') + t.assert.strictEqual(error.message, 'Custom validation failed') + reply.status(500).send({ error: error.message }) + }) + + // Add sync preValidation hook + fastify.addHook('preValidation', (request, reply, next) => { next() }) + fastify.post('/sync', throwingRouteValidator) + + const response = await fastify.inject({ + method: 'POST', + url: '/sync', + payload: { name: 'test' } + }) + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(response.json(), { error: 'Custom validation failed' }) + }) +}) From f15d4eaba03d86786bec2de7aed1236718c813f9 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 9 Nov 2025 09:47:54 +0100 Subject: [PATCH 1192/1295] Bumped v5.6.2 --- fastify.js | 2 +- package.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index 2f3e54cc5a4..3a116b6436b 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.6.1' +const VERSION = '5.6.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 4539d145bdf..227c02f64ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.6.1", + "version": "5.6.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", @@ -11,13 +11,14 @@ "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"", "benchmark:parser:error": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -H \"content-length:123\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", + "build:sync-version": "node build/sync-version.js", "coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100 ", "coverage:ci-check-coverage": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100", "lint": "npm run lint:eslint", "lint:fix": "eslint --fix", "lint:markdown": "markdownlint-cli2", "lint:eslint": "eslint", - "prepublishOnly": "cross-env PREPUBLISH=true borp --reporter=@jsumners/line-reporter && npm run test:validator:integrity", + "prepublishOnly": "cross-env PREPUBLISH=true borp --reporter=@jsumners/line-reporter && npm run test:validator:integrity && npm run build:sync-version", "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run unit && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", From 664e5aff06658e2126a09668486aa5f4191988fe Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Sun, 9 Nov 2025 10:33:01 +0100 Subject: [PATCH 1193/1295] docs: Improved firebase serverless guide about process remaining stuck (#6380) --- docs/Guides/Serverless.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index c675152e104..fb055a6e083 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -303,10 +303,10 @@ const fastifyApp = async (request, reply) => { ### Add Custom `contentTypeParser` to Fastify instance and define endpoints -Firebase Function's HTTP layer already parses the request -and makes a JSON payload available. It also provides access -to the raw body, unparsed, which is useful for calculating -request signatures to validate HTTP webhooks. +Firebase Function's HTTP layer already parses the request and makes a JSON +payload available through the property `payload.body` below. It also provides +access to the raw body, unparsed, which is useful for calculating request +signatures to validate HTTP webhooks. Add as follows to the `registerRoutes()` function: @@ -332,6 +332,24 @@ async function registerRoutes (fastify) { } ``` +**Failing to add this `ContentTypeParser` may lead to the Fastify process +remaining stuck and not processing any other requests after receiving one with +the Content-Type `application/json`.** + +When using Typescript, since the type of `payload` is a native `IncomingMessage` +that gets modified by Firebase, it won't be able to find the property +`payload.body`. + +In order to suppress the error, you can use the following signature: + +```ts +declare module 'http' { + interface IncomingMessage { + body?: unknown; + } +} +``` + ### Export the function using Firebase onRequest Final step is to export the Fastify app instance to Firebase's own From b1b7f6ef541cfa6715a0364be7508f2b412a510a Mon Sep 17 00:00:00 2001 From: Ramy Abbas <86947198+craftsman01@users.noreply.github.com> Date: Sun, 9 Nov 2025 16:14:53 +0200 Subject: [PATCH 1194/1295] docs: update migration guide with date-time breaking change (#6110) --- docs/Guides/Migration-Guide-V5.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index 6998d1ae9dc..ebc6b266378 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -545,6 +545,15 @@ for more information. This was already deprecated in v4 as `FSTDEP014`, so you should have already updated your code. +### `time` and `date-time` formats enforce timezone + +The updated AJV compiler updates `ajv-formats` which now +enforce the use of timezone in `time` and `date-time` format. +A workaround is to use `iso-time` and `iso-date-time` formats +which support an optional timezone for backwards compatibility. +See the +[full discussion](https://github.com/fastify/fluent-json-schema/issues/267). + ## New Features ### Diagnostic Channel support From 1b0e4080bfa4b0c451b7b840ffe26352ea249634 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 10 Nov 2025 07:10:17 +0100 Subject: [PATCH 1195/1295] chore: remove test file (#6384) --- test/decorator-namespace.test._js_ | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 test/decorator-namespace.test._js_ diff --git a/test/decorator-namespace.test._js_ b/test/decorator-namespace.test._js_ deleted file mode 100644 index ed35e7b90a5..00000000000 --- a/test/decorator-namespace.test._js_ +++ /dev/null @@ -1,30 +0,0 @@ -'use strict' - -/* eslint no-prototype-builtins: 0 */ - -const { test } = require('node:test') -const Fastify = require('..') -const fp = require('fastify-plugin') - -test('plugin namespace', async t => { - t.plan(2) - const app = Fastify() - - await app.register(async function plugin (app, opts) { - app.decorate('utility', function () { - return 'utility' - }) - - app.get('/', function (req, reply) { - // ! here the plugin would use app.utility() - // ! the plugin does not know about the namespace - reply.send({ utility: app.utility() }) - }) - }, { namespace: 'foo' }) - - // ! but outside the plugin the decorator would be app.foo.utility() - t.assert.ok(app.foo.utility) - - const res = await app.inject('/') - t.assert.deepStrictEqual(res.json(), { utility: 'utility' }) -}) From 085e1c7c8cd4111064c676b21bfa5f9c6b086d0c Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Mon, 10 Nov 2025 10:46:42 +0100 Subject: [PATCH 1196/1295] feat: speed up loading with custom compiler (#6383) --- lib/schema-controller.js | 4 +- test/internals/schema-controller-perf.test.js | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 test/internals/schema-controller-perf.test.js diff --git a/lib/schema-controller.js b/lib/schema-controller.js index 119c95c727b..a805c85ce5d 100644 --- a/lib/schema-controller.js +++ b/lib/schema-controller.js @@ -1,8 +1,6 @@ 'use strict' const { buildSchemas } = require('./schemas') -const SerializerSelector = require('@fastify/fast-json-stringify-compiler') -const ValidatorSelector = require('@fastify/ajv-compiler') /** * Called at every fastify context that is being created. @@ -21,9 +19,11 @@ function buildSchemaController (parentSchemaCtrl, opts) { }, opts?.compilersFactory) if (!compilersFactory.buildValidator) { + const ValidatorSelector = require('@fastify/ajv-compiler') compilersFactory.buildValidator = ValidatorSelector() } if (!compilersFactory.buildSerializer) { + const SerializerSelector = require('@fastify/fast-json-stringify-compiler') compilersFactory.buildSerializer = SerializerSelector() } diff --git a/test/internals/schema-controller-perf.test.js b/test/internals/schema-controller-perf.test.js new file mode 100644 index 00000000000..0741b41f605 --- /dev/null +++ b/test/internals/schema-controller-perf.test.js @@ -0,0 +1,40 @@ +const { sep } = require('node:path') +const { test } = require('node:test') +const Fastify = require('../../fastify') + +test('SchemaController are NOT loaded when the controllers are custom', async t => { + const app = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: () => () => { }, + buildSerializer: () => () => { } + } + } + }) + + await app.ready() + + const loaded = Object.keys(require.cache) + const ajvModule = loaded.find((path) => path.includes(`@fastify${sep}ajv-compiler`)) + const stringifyModule = loaded.find((path) => path.includes(`@fastify${sep}fast-json-stringify-compiler`)) + + t.assert.equal(ajvModule, undefined, 'Ajv compiler is loaded') + t.assert.equal(stringifyModule, undefined, 'Stringify compiler is loaded') +}) + +test('SchemaController are loaded when the controllers are not custom', async t => { + const app = Fastify() + await app.ready() + + const loaded = Object.keys(require.cache) + const ajvModule = loaded.find((path) => path.includes(`@fastify${sep}ajv-compiler`)) + const stringifyModule = loaded.find((path) => path.includes(`@fastify${sep}fast-json-stringify-compiler`)) + + t.after(() => { + delete require.cache[ajvModule] + delete require.cache[stringifyModule] + }) + + t.assert.ok(ajvModule, 'Ajv compiler is loaded') + t.assert.ok(stringifyModule, 'Stringify compiler is loaded') +}) From 27e3fe30ce9c384aa285302b3c09f1d9e79a7240 Mon Sep 17 00:00:00 2001 From: Abhijeet Singh Date: Mon, 10 Nov 2025 16:18:18 +0530 Subject: [PATCH 1197/1295] chore: replace all instances of twitter.com with x.com (#6355) --- README.md | 64 ++++++++++++++++++++++++++--------------------------- SECURITY.md | 6 ++--- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 6f873b527f5..7f794e36015 100644 --- a/README.md +++ b/README.md @@ -280,70 +280,70 @@ listed in alphabetical order. **Lead Maintainers:** * [__Matteo Collina__](https://github.com/mcollina), - , + , * [__Tomas Della Vedova__](https://github.com/delvedor), - , + , * [__KaKa Ng__](https://github.com/climba03003), * [__Manuel Spigolon__](https://github.com/eomm), - , + , * [__James Sumners__](https://github.com/jsumners), - , + , ### Fastify Core team * [__Aras Abbasi__](https://github.com/uzlopak), * [__Harry Brundage__](https://github.com/airhorns/), - , + , * [__Matteo Collina__](https://github.com/mcollina), - , + , * [__Gürgün Dayıoğlu__](https://github.com/gurgunday), * [__Tomas Della Vedova__](https://github.com/delvedor), - , + , * [__Carlos Fuentes__](https://github.com/metcoder95), - , + , * [__Vincent Le Goff__](https://github.com/zekth) * [__Luciano Mammino__](https://github.com/lmammino), - , + , * [__Jean Michelet__](https://github.com/jean-michelet), * [__KaKa Ng__](https://github.com/climba03003), * [__Luis Orbaiceta__](https://github.com/luisorbaiceta), - , + , * [__Maksim Sinik__](https://github.com/fox1t), - , + , * [__Manuel Spigolon__](https://github.com/eomm), - , + , * [__James Sumners__](https://github.com/jsumners), - , + , ### Fastify Plugins team * [__Harry Brundage__](https://github.com/airhorns/), - , + , * [__Simone Busoli__](https://github.com/simoneb), - , + , * [__Dan Castillo__](https://github.com/dancastillo), * [__Matteo Collina__](https://github.com/mcollina), - , + , * [__Gürgün Dayıoğlu__](https://github.com/gurgunday), * [__Tomas Della Vedova__](https://github.com/delvedor), - , + , * [__Carlos Fuentes__](https://github.com/metcoder95), - , + , * [__Vincent Le Goff__](https://github.com/zekth) * [__Jean Michelet__](https://github.com/jean-michelet), * [__KaKa Ng__](https://github.com/climba03003), * [__Maksim Sinik__](https://github.com/fox1t), - , + , * [__Frazer Smith__](https://github.com/Fdawgs), * [__Manuel Spigolon__](https://github.com/eomm), - , + , ### Emeritus Contributors Great contributors to a specific area of the Fastify ecosystem will be invited @@ -351,32 +351,32 @@ to join this group by Lead Maintainers when they decide to step down from the active contributor's group. * [__Tommaso Allevi__](https://github.com/allevo), - , + , * [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/), - , + , * [__Çağatay Çalı__](https://github.com/cagataycali), - , + , * [__David Mark Clements__](https://github.com/davidmarkclements), - , + , -* [__dalisoft__](https://github.com/dalisoft), , +* [__dalisoft__](https://github.com/dalisoft), , * [__Dustin Deus__](https://github.com/StarpTech), - , + , * [__Denis Fäcke__](https://github.com/SerayaEryn), - , + , * [__Rafael Gonzaga__](https://github.com/rafaelgss), - , + , * [__Trivikram Kamat__](https://github.com/trivikr), - , + , * [__Ayoub El Khattabi__](https://github.com/AyoubElk), - , + , * [__Cemre Mengu__](https://github.com/cemremengu), - , + , * [__Salman Mitha__](https://github.com/salmanm), * [__Nathan Woltman__](https://github.com/nwoltman), - , + , ## Hosted by diff --git a/SECURITY.md b/SECURITY.md index 75699ac1064..ff023b12fc8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -137,13 +137,13 @@ work as a member of the Fastify Core team. ### Members * [__Matteo Collina__](https://github.com/mcollina), - , + , * [__Tomas Della Vedova__](https://github.com/delvedor), - , + , * [__Vincent Le Goff__](https://github.com/zekth) * [__KaKa Ng__](https://github.com/climba03003) * [__James Sumners__](https://github.com/jsumners), - , + , ## OpenSSF CII Best Practices From 61ca0021db7c338626395117ee9fb9444515bfce Mon Sep 17 00:00:00 2001 From: Sahachai Date: Sun, 16 Nov 2025 16:20:22 +0700 Subject: [PATCH 1198/1295] docs: correct logger option in example (#6391) --- docs/Reference/Server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 926bd46a4e4..017fc4587fa 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -317,7 +317,7 @@ Pino interface by having the following methods: `info`, `error`, `debug`, }, }; - const fastify = require('fastify')({logger: customLogger}); + const fastify = require('fastify')({ loggerInstance: customLogger }); ``` ### `disableRequestLogging` From 012c24919679444313cf20d6be084cb9d22165e6 Mon Sep 17 00:00:00 2001 From: Shriti Date: Sun, 23 Nov 2025 14:20:23 +0530 Subject: [PATCH 1199/1295] docs: fix links (#6394) --- docs/Guides/Detecting-When-Clients-Abort.md | 2 +- docs/Guides/Ecosystem.md | 18 +++++------------- docs/Guides/Serverless.md | 6 +++--- docs/Reference/Decorators.md | 4 ++-- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md index e2771013b7a..b2dee213636 100644 --- a/docs/Guides/Detecting-When-Clients-Abort.md +++ b/docs/Guides/Detecting-When-Clients-Abort.md @@ -81,7 +81,7 @@ start() Our code is setting up a Fastify server which includes the following functionality: -- Accepting requests at http://localhost:3000, with a 3 second delayed response +- Accepting requests at `http://localhost:3000`, with a 3 second delayed response of `{ ok: true }`. - An onRequest hook that triggers when every request is received. - Logic that triggers in the hook when the request is closed. diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 33bfca4c752..27e04353a18 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -182,13 +182,8 @@ section. close the server gracefully on `SIGINT` and `SIGTERM` signals. - [`@eropple/fastify-openapi3`](https://github.com/eropple/fastify-openapi3) Provides easy, developer-friendly OpenAPI 3.1 specs + doc explorer based on your routes. -- [`@ethicdevs/fastify-custom-session`](https://github.com/EthicDevs/fastify-custom-session) - A plugin lets you use session and decide only where to load/save from/to. Has - great TypeScript support + built-in adapters for common ORMs/databases (Firebase, - Prisma Client, Postgres (wip), InMemory) and you can easily make your own adapter! -- [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server) - A plugin to easily create git server and make one/many Git repositories available - for clone/fetch/push through the standard `git` (over http) commands. + + - [`@exortek/fastify-mongo-sanitize`](https://github.com/ExorTek/fastify-mongo-sanitize) A Fastify plugin that protects against No(n)SQL injection by sanitizing data. - [`@exortek/remix-fastify`](https://github.com/ExorTek/remix-fastify) @@ -373,7 +368,7 @@ section. - [`fastify-feature-flags`](https://gitlab.com/m03geek/fastify-feature-flags) Fastify feature flags plugin with multiple providers support (e.g. env, [config](https://lorenwest.github.io/node-config/), - [unleash](https://unleash.github.io/)). + [unleash](https://github.com/Unleash/unleash)). - [`fastify-file-routes`](https://github.com/spa5k/fastify-file-routes) Get Next.js based file system routing into fastify. - [`fastify-file-upload`](https://github.com/huangang/fastify-file-upload) @@ -524,10 +519,7 @@ middlewares into Fastify plugins Add `additionalProperties: false` by default to your JSON Schemas. - [`fastify-no-icon`](https://github.com/jsumners/fastify-no-icon) Plugin to eliminate thrown errors for `/favicon.ico` requests. -- [`fastify-normalize-request-reply`](https://github.com/ericrglass/fastify-normalize-request-reply) - Plugin to normalize the request and reply to the Express version 4.x request - and response, which allows use of middleware, like swagger-stats, that was - originally written for Express. + - [`fastify-now`](https://github.com/yonathan06/fastify-now) Structure your endpoints in a folder and load them dynamically with Fastify. - [`fastify-nuxtjs`](https://github.com/gomah/fastify-nuxtjs) Vue server-side @@ -687,7 +679,7 @@ middlewares into Fastify plugins - [`fastify-type-provider-effect-schema`](https://github.com/daotl/fastify-type-provider-effect-schema) Fastify [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/) - for [@effect/schema](https://github.com/effect-ts/schema). + for [@effect/schema](https://github.com/Effect-TS/effect). - [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod) Fastify [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/) diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index fb055a6e083..9302dbef348 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -595,10 +595,10 @@ server-like concurrency with the autoscaling properties of traditional serverless functions. Get started with the -[Fastify Node.js template on Vercel]( -https://vercel.com/templates/other/fastify-serverless-function). +[Fastify template on Vercel]( +https://vercel.com/templates/backend/fastify-on-vercel). [Fluid compute](https://vercel.com/docs/functions/fluid-compute) currently requires an explicit opt-in. Learn more about enabling Fluid compute [here]( -https://vercel.com/docs/functions/fluid-compute#how-to-enable-fluid-compute). +https://vercel.com/docs/functions/fluid-compute#how-to-enable-fluid-compute). \ No newline at end of file diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 4324658db2a..3ff77fb7cbd 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -399,7 +399,7 @@ fastify.register(async function (fastify) { ``` > ℹ️ Note: For TypeScript users, `getDecorator` supports generic type parameters. -> See the [TypeScript documentation](/docs/latest/Reference/TypeScript/) for +> See the [TypeScript documentation](/docs/Reference/TypeScript.md) for > advanced typing examples. #### `setDecorator(name, value)` @@ -429,5 +429,5 @@ fastify.addHook('preHandler', async (req, reply) => { ``` > ℹ️ Note: For TypeScript users, see the -> [TypeScript documentation](/docs/latest/Reference/TypeScript/) for advanced +> [TypeScript documentation](/docs/Reference/TypeScript.md) for advanced > typing examples using `setDecorator`. From db25d12599ffb1be176c199374ad33c2130df9a1 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 30 Nov 2025 10:41:37 +0100 Subject: [PATCH 1200/1295] chore: skip unnecessary object creation (#6400) --- fastify.js | 4 +--- lib/route.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fastify.js b/fastify.js index 3a116b6436b..18166bb16f5 100644 --- a/fastify.js +++ b/fastify.js @@ -94,9 +94,7 @@ function fastify (serverOptions) { } = processOptions(serverOptions, defaultRoute, onBadUrl) // Default router - const router = buildRouting({ - config: options.routerOptions - }) + const router = buildRouting(options.routerOptions) // 404 router, used for handling encapsulated 404 handlers const fourOhFour = build404(options) diff --git a/lib/route.js b/lib/route.js index 3f49c64b3a6..051c6b30989 100644 --- a/lib/route.js +++ b/lib/route.js @@ -69,7 +69,7 @@ const routerKeys = [ ] function buildRouting (options) { - const router = FindMyWay(options.config) + const router = FindMyWay(options) let avvio let fourOhFour From 5d610930326e871f915f5eb0dda5967c1128e47e Mon Sep 17 00:00:00 2001 From: Jonathon Deason Date: Thu, 4 Dec 2025 01:34:24 -0500 Subject: [PATCH 1201/1295] docs: update JSON Schema link in documentation (#6402) --- docs/Reference/Validation-and-Serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index b2ad7eed0d6..4f6e5cdec32 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -1032,7 +1032,7 @@ const refToSharedSchemaDefinitions = { - [JSON Schema](https://json-schema.org/) - [Understanding JSON - Schema](https://spacetelescope.github.io/understanding-json-schema/) + Schema](https://json-schema.org/understanding-json-schema) - [fast-json-stringify documentation](https://github.com/fastify/fast-json-stringify) - [Ajv documentation](https://github.com/epoberezkin/ajv/blob/master/README.md) From c7ae5a01a7ccd97e71a725b965db1ce8ab99f3a9 Mon Sep 17 00:00:00 2001 From: KaranHotwani <37085902+KaranHotwani@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:37:09 +0530 Subject: [PATCH 1202/1295] docs(ecosystem): add fastify-ses-mailer (#6395) Co-authored-by: KK --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 27e04353a18..c77102405b2 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -638,6 +638,8 @@ middlewares into Fastify plugins - [`fastify-server-session`](https://github.com/jsumners/fastify-server-session) A session plugin with support for arbitrary backing caches via `fastify-caching`. +- [`fastify-ses-mailer`](https://github.com/KaranHotwani/fastify-ses-mailer) A + Fastify plugin for sending emails via AWS SES using AWS SDK v3. - [`fastify-shared-schema`](https://github.com/Adibla/fastify-shared-schema) Plugin for sharing schemas between different routes. - [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik From 334fb31b20587e28623079d1e4b55a333cd95e42 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 6 Dec 2025 11:06:36 +0100 Subject: [PATCH 1203/1295] docs: add security note about validation errors in response (#6407) --- docs/Reference/Validation-and-Serialization.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 4f6e5cdec32..84bddc35db4 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -775,6 +775,12 @@ with the following payload: } ``` +> ⚠ Security Consideration: By default, validation error details from the schema +> are included in the response payload. If your organization requires sanitizing +> or customizing these error messages (e.g., to avoid exposing internal schema +> details), configure a custom error handler using +> [`setErrorHandler()`](./Server.md#seterrorhandler). + To handle errors inside the route, specify the `attachValidation` option. If there is a validation error, the `validationError` property of the request will contain the `Error` object with the raw validation result as shown below: From 759e9787b5669abf953068e42a17bffba7521348 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 7 Dec 2025 16:43:35 +0100 Subject: [PATCH 1204/1295] docs: add security threat model (#6406) --- SECURITY.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index ff023b12fc8..069ee08a78c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,6 +3,23 @@ This document describes the management of vulnerabilities for the Fastify project and its official plugins. +## Threat Model + +Fastify's threat model extends the +[Node.js threat model](https://github.com/nodejs/node/blob/main/SECURITY.md#the-nodejs-threat-model). + +**Trusted:** Application code (plugins, handlers, hooks, schemas), configuration, +and the runtime environment. + +**Untrusted:** All network input (HTTP headers, body, query strings, URL +parameters). + +### Examples of Vulnerabilities + +- Parsing flaws that bypass validation or security controls +- DoS through malformed input to Fastify's core +- Bypasses of built-in protections (prototype poisoning, schema validation) + ## Reporting vulnerabilities Individuals who find potential vulnerabilities in Fastify are invited to @@ -155,11 +172,9 @@ There are three “tiers”: passing, silver, and gold. We meet 100% of the “passing” criteria. ### Silver -We meet 87% of the “silver” criteria. The gaps are as follows: +We meet 87% of the "silver" criteria. The gaps are as follows: - we do not have a DCO or a CLA process for contributions. - - we do not currently document - “what the user can and cannot expect in terms of security” for our project. - - we do not currently document ”the architecture (aka high-level design)” + - we do not currently document "the architecture (aka high-level design)" for our project. ### Gold From ab9dae462a138cd0d3c2d4e4e9e691b426fbbf9e Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 14 Dec 2025 10:39:29 +0100 Subject: [PATCH 1205/1295] chore: update onboarding and offboarding instructions (#6403) Add the `org-admin` repo as instruction Signed-off-by: Manuel Spigolon --- CONTRIBUTING.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95ec7c6d554..7c6d8c9e629 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,10 +107,12 @@ the following tasks: in the proper order. Use your GitHub profile icon for the `picture:` field. 5. Read the [pinned announcements](https://github.com/orgs/fastify/discussions/categories/announcements) to be updated with the organization’s news. -6. The person who does the onboarding must add you to the [npm - org](https://www.npmjs.com/org/fastify), so that you can help maintain the - official plugins. -7. Optionally, the person can be added as an Open Collective member +6. The person who does the onboarding must open a pull request to + [`fastify/org-admin`](https://github.com/fastify/org-admin?tab=readme-ov-file#org-admin) + so an admin can add the new member to the + [npm org](https://www.npmjs.com/org/fastify) and the GitHub Team, + so that the new joiner can help maintain the official plugins. +8. Optionally, the person can be added as an Open Collective member by the lead team. ### Offboarding Collaborators @@ -132,11 +134,14 @@ person that did the onboarding must: The person that did the onboarding must: 1. If the collaborator does not reply to the ping in a reasonable time, open the pull requests described above. -2. Remove the collaborator from the Fastify teams on GitHub. -3. Remove the collaborator from the [npm +2. Open a pull request to [`fastify/org-admin`](https://github.com/fastify/org-admin?tab=readme-ov-file#org-admin) + so an admin will: + 1. Remove the collaborator from the Fastify teams on GitHub. + 2. Remove the collaborator from the [npm org](https://www.npmjs.com/org/fastify). -4. Remove the collaborator from the Azure team. -5. Remove the collaborator from the Open Collective members. + 3. Remove the collaborator from the Azure team. + 4. Remove the collaborator from the Open Collective members. + ----------------------------------------- From 970c575832521fff01cc018d928d84454811173b Mon Sep 17 00:00:00 2001 From: Tu Shaokun <53142663+tt-a1i@users.noreply.github.com> Date: Mon, 15 Dec 2025 00:18:38 +0800 Subject: [PATCH 1206/1295] fix: set status code before publishing diagnostics error channel (#6412) --- lib/error-handler.js | 7 +- lib/error-status.js | 14 ++++ lib/handle-request.js | 7 +- lib/wrap-thenable.js | 3 + test/diagnostics-channel/error-status.test.js | 84 +++++++++++++++++++ 5 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 lib/error-status.js diff --git a/lib/error-handler.js b/lib/error-handler.js index 03d5cab36ee..f9a1899ca99 100644 --- a/lib/error-handler.js +++ b/lib/error-handler.js @@ -2,11 +2,11 @@ const statusCodes = require('node:http').STATUS_CODES const wrapThenable = require('./wrap-thenable.js') +const { setErrorStatusCode } = require('./error-status.js') const { kReplyHeaders, kReplyNextErrorHandler, kReplyIsRunningOnErrorHook, - kReplyHasStatusCode, kRouteContext, kDisableRequestLogging } = require('./symbols.js') @@ -81,10 +81,7 @@ function handleError (reply, error, cb) { function defaultErrorHandler (error, request, reply) { setErrorHeaders(error, reply) - if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) { - const statusCode = error && (error.statusCode || error.status) - reply.code(statusCode >= 400 ? statusCode : 500) - } + setErrorStatusCode(reply, error) if (reply.statusCode < 500) { if (!reply.log[kDisableRequestLogging]) { reply.log.info( diff --git a/lib/error-status.js b/lib/error-status.js new file mode 100644 index 00000000000..e6baea2b0e5 --- /dev/null +++ b/lib/error-status.js @@ -0,0 +1,14 @@ +'use strict' + +const { + kReplyHasStatusCode +} = require('./symbols') + +function setErrorStatusCode (reply, err) { + if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) { + const statusCode = err && (err.statusCode || err.status) + reply.code(statusCode >= 400 ? statusCode : 500) + } +} + +module.exports = { setErrorStatusCode } diff --git a/lib/handle-request.js b/lib/handle-request.js index fe0977f95cc..12831e6a7d8 100644 --- a/lib/handle-request.js +++ b/lib/handle-request.js @@ -4,6 +4,7 @@ const diagnostics = require('node:diagnostics_channel') const { validate: validateSchema } = require('./validation') const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') const wrapThenable = require('./wrap-thenable') +const { setErrorStatusCode } = require('./error-status') const { kReplyIsError, kRouteContext, @@ -143,11 +144,13 @@ function preHandlerCallbackInner (err, request, reply, store) { try { if (err != null) { reply[kReplyIsError] = true - reply.send(err) if (store) { store.error = err + // Set status code before publishing so subscribers see the correct value + setErrorStatusCode(reply, err) channels.error.publish(store) } + reply.send(err) return } @@ -158,6 +161,8 @@ function preHandlerCallbackInner (err, request, reply, store) { } catch (err) { if (store) { store.error = err + // Set status code before publishing so subscribers see the correct value + setErrorStatusCode(reply, err) channels.error.publish(store) } diff --git a/lib/wrap-thenable.js b/lib/wrap-thenable.js index 8dddc72ac07..6ff5091d189 100644 --- a/lib/wrap-thenable.js +++ b/lib/wrap-thenable.js @@ -4,6 +4,7 @@ const { kReplyIsError, kReplyHijacked } = require('./symbols') +const { setErrorStatusCode } = require('./error-status') const diagnostics = require('node:diagnostics_channel') const channels = diagnostics.tracingChannel('fastify.request.handler') @@ -52,6 +53,8 @@ function wrapThenable (thenable, reply, store) { }, function (err) { if (store) { store.error = err + // Set status code before publishing so subscribers see the correct value + setErrorStatusCode(reply, err) channels.error.publish(store) // note that error happens before asyncStart channels.asyncStart.publish(store) } diff --git a/test/diagnostics-channel/error-status.test.js b/test/diagnostics-channel/error-status.test.js index e1776f8f418..474a91c2c51 100644 --- a/test/diagnostics-channel/error-status.test.js +++ b/test/diagnostics-channel/error-status.test.js @@ -5,6 +5,90 @@ const Fastify = require('../..') const statusCodes = require('node:http').STATUS_CODES const diagnostics = require('node:diagnostics_channel') +test('diagnostics channel error event should report correct status code', async (t) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + let diagnosticsStatusCode + + const channel = diagnostics.channel('tracing:fastify.request.handler:error') + const handler = (msg) => { + diagnosticsStatusCode = msg.reply.statusCode + } + channel.subscribe(handler) + t.after(() => channel.unsubscribe(handler)) + + fastify.get('/', async () => { + const err = new Error('test error') + err.statusCode = 503 + throw err + }) + + const res = await fastify.inject('/') + + t.assert.strictEqual(res.statusCode, 503) + t.assert.strictEqual(diagnosticsStatusCode, 503, 'diagnostics channel should report correct status code') + t.assert.strictEqual(diagnosticsStatusCode, res.statusCode, 'diagnostics status should match response status') +}) + +test('diagnostics channel error event should report 500 for errors without status', async (t) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + let diagnosticsStatusCode + + const channel = diagnostics.channel('tracing:fastify.request.handler:error') + const handler = (msg) => { + diagnosticsStatusCode = msg.reply.statusCode + } + channel.subscribe(handler) + t.after(() => channel.unsubscribe(handler)) + + fastify.get('/', async () => { + throw new Error('plain error without status') + }) + + const res = await fastify.inject('/') + + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(diagnosticsStatusCode, 500, 'diagnostics channel should report 500 for plain errors') + t.assert.strictEqual(diagnosticsStatusCode, res.statusCode, 'diagnostics status should match response status') +}) + +test('diagnostics channel error event should report correct status with custom error handler', async (t) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + let diagnosticsStatusCode + + const channel = diagnostics.channel('tracing:fastify.request.handler:error') + const handler = (msg) => { + diagnosticsStatusCode = msg.reply.statusCode + } + channel.subscribe(handler) + t.after(() => channel.unsubscribe(handler)) + + fastify.setErrorHandler((error, request, reply) => { + reply.status(503).send({ error: error.message }) + }) + + fastify.get('/', async () => { + throw new Error('handler error') + }) + + const res = await fastify.inject('/') + + // Note: The diagnostics channel fires before the custom error handler runs, + // so it reports 500 (default) rather than 503 (set by custom handler). + // This is expected behavior - the error channel reports the initial error state. + t.assert.strictEqual(res.statusCode, 503) + t.assert.strictEqual(diagnosticsStatusCode, 500, 'diagnostics channel reports status before custom handler') + t.assert.notStrictEqual(diagnosticsStatusCode, res.statusCode, 'custom handler can change status after diagnostics') +}) + test('Error.status property support', (t, done) => { t.plan(4) const fastify = Fastify() From 79ab8e956482971a8cc0544ab9a846168b11998d Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 23 Dec 2025 19:21:46 +0100 Subject: [PATCH 1207/1295] fix: use JSON.stringify in onBadUrl for proper escaping (#6420) Use JSON.stringify to properly escape special characters in the bad URL path instead of template string interpolation. Also use Buffer.byteLength for accurate Content-Length calculation with multi-byte characters. --- fastify.js | 9 +++++++-- test/404s.test.js | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 18166bb16f5..d02ab6dd8f5 100644 --- a/fastify.js +++ b/fastify.js @@ -647,10 +647,15 @@ function fastify (serverOptions) { return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply) } - const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}` + const body = JSON.stringify({ + error: 'Bad Request', + code: 'FST_ERR_BAD_URL', + message: `'${path}' is not a valid url component`, + statusCode: 400 + }) res.writeHead(400, { 'Content-Type': 'application/json', - 'Content-Length': body.length + 'Content-Length': Buffer.byteLength(body) }) res.end(body) } diff --git a/test/404s.test.js b/test/404s.test.js index 02a68762d18..a6d3a06f814 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -1845,6 +1845,26 @@ test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', async t => { done() }) }) + + await t.test('Bad URL with special characters should be properly JSON escaped', (t, done) => { + t.plan(3) + const fastify = Fastify() + fastify.get('/hello/:id', () => t.assert.fail('we should not be here')) + fastify.inject({ + url: '/hello/%world%22test', + method: 'GET' + }, (err, response) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(response.payload), { + error: 'Bad Request', + message: '\'/hello/%world%22test\' is not a valid url component', + statusCode: 400, + code: 'FST_ERR_BAD_URL' + }) + done() + }) + }) }) test('setNotFoundHandler should be chaining fastify instance', async t => { From 38973670e5a90d7ea6dbce12977e9d7cf9aa7c75 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Wed, 24 Dec 2025 15:11:15 +0200 Subject: [PATCH 1208/1295] chore: fix type test (#6418) --- test/types/fastify.test-d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 9a175c40258..17740b5b8a0 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -65,12 +65,12 @@ expectType< >(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 })) expectType(fastify({ http2: true, https: {} }).inject()) expectType< - FastifyInstance & - SafePromiseLike> + FastifyInstance & + SafePromiseLike> >(fastify({ schemaController: {} })) expectType< - FastifyInstance & - SafePromiseLike> + FastifyInstance & + SafePromiseLike> >( fastify({ schemaController: { From 5317c9068a43a02b671cd6df7392dd45a9aabaac Mon Sep 17 00:00:00 2001 From: twenty two <51849001+twentytwo777@users.noreply.github.com> Date: Fri, 26 Dec 2025 20:05:15 +0400 Subject: [PATCH 1209/1295] docs: improve Validation-and-Serialization.md (#6423) --- docs/Reference/Validation-and-Serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 84bddc35db4..2eb3945d47b 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -1038,7 +1038,7 @@ const refToSharedSchemaDefinitions = { - [JSON Schema](https://json-schema.org/) - [Understanding JSON - Schema](https://json-schema.org/understanding-json-schema) + Schema](https://json-schema.org/understanding-json-schema/about) - [fast-json-stringify documentation](https://github.com/fastify/fast-json-stringify) - [Ajv documentation](https://github.com/epoberezkin/ajv/blob/master/README.md) From 25aa29dd392fe75780df0311ecfbfc2b348d33d3 Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Sun, 28 Dec 2025 23:35:34 +0800 Subject: [PATCH 1210/1295] test: skip IPv6 test if its support is not present (#6428) --- test/server.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/server.test.js b/test/server.test.js index 7fa2b4ad9b4..56c83c1439a 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -1,11 +1,14 @@ 'use strict' const dns = require('node:dns') +const { networkInterfaces } = require('node:os') const { test } = require('node:test') const Fastify = require('..') const undici = require('undici') const proxyquire = require('proxyquire') +const isIPv6Missing = !Object.values(networkInterfaces()).flat().some(({ family }) => family === 'IPv6') + test('listen should accept null port', async t => { const fastify = Fastify() t.after(() => fastify.close()) @@ -81,7 +84,7 @@ test('Test for hostname and port', async (t) => { await fetch('http://localhost:8000/host') }) -test('Test for IPV6 port', async (t) => { +test('Test for IPV6 port', { skip: isIPv6Missing }, async (t) => { t.plan(3) const app = Fastify() t.after(() => app.close()) From 270d367a39e467219477b8ac0b8453e81cf986ab Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Sun, 28 Dec 2025 23:36:18 +0800 Subject: [PATCH 1211/1295] test: fix test when localhost has multiple addresses (#6427) --- test/listen.1.test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/listen.1.test.js b/test/listen.1.test.js index 94862d5d83f..92d9cc0de75 100644 --- a/test/listen.1.test.js +++ b/test/listen.1.test.js @@ -1,5 +1,6 @@ 'use strict' +const { networkInterfaces } = require('node:os') const { test, before } = require('node:test') const Fastify = require('..') const helper = require('./helper') @@ -41,7 +42,14 @@ test('Async/await listen with arguments', async t => { }) const addr = await fastify.listen({ port: 0, host: '0.0.0.0' }) const address = fastify.server.address() - t.assert.strictEqual(addr, `http://127.0.0.1:${address.port}`) + const { protocol, hostname, port, pathname } = new URL(addr) + t.assert.strictEqual(protocol, 'http:') + t.assert.ok(Object.values(networkInterfaces()) + .flat() + .filter(({ internal }) => internal) + .some(({ address }) => address === hostname)) + t.assert.strictEqual(pathname, '/') + t.assert.strictEqual(Number(port), address.port) t.assert.deepEqual(address, { address: '0.0.0.0', family: 'IPv4', From 42c4f7e54009a98019b91b078df39824aa05e87e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 29 Dec 2025 12:04:25 +0100 Subject: [PATCH 1212/1295] docs: add security warning for requestIdHeader (#6425) * docs: add security warning for requestIdHeader * Apply suggestion from @Eomm Signed-off-by: Manuel Spigolon --------- Signed-off-by: Manuel Spigolon Co-authored-by: Manuel Spigolon --- docs/Reference/Logging.md | 4 ++++ docs/Reference/Server.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index f7a25368d6b..d7ca2caad48 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -107,6 +107,10 @@ value is used; otherwise, a new incremental ID is generated. See Fastify Factory [`requestIdHeader`](./Server.md#factory-request-id-header) and Fastify Factory [`genReqId`](./Server.md#genreqid) for customization options. +> ⚠ Warning: enabling `requestIdHeader` allows any callers to set `reqId` to a +> value of their choosing. +> No validation is performed on `requestIdHeader`. + #### Serializers The default logger uses standard serializers for objects with `req`, `res`, and `err` properties. The `req` object is the Fastify [`Request`](./Request.md) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 017fc4587fa..7eff0fc8172 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -413,6 +413,10 @@ const fastify = require('fastify')({ }) ``` +> ⚠ Warning: enabling this allows any callers to set `reqId` to a +> value of their choosing. +> No validation is performed on `requestIdHeader`. + ### `requestIdLogLabel` From ee9de39fb190199bd6e905347b175952b051dff7 Mon Sep 17 00:00:00 2001 From: Rohit Soni Date: Mon, 29 Dec 2025 16:38:22 +0530 Subject: [PATCH 1213/1295] docs(ecosystem): add elements-fastify (#6416) * add elements-fastify * fix linting --------- Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index c77102405b2..6bba623de8e 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -241,6 +241,9 @@ section. request IDs into your logs. - [`electron-server`](https://github.com/anonrig/electron-server) A plugin for using Fastify without the need of consuming a port on Electron apps. +- [`elements-fastify`](https://github.com/rohitsoni007/elements-fastify) Fastify + Plugin for Stoplight Elements API Documentation using + openapi swagger json yml. - [`fast-water`](https://github.com/tswayne/fast-water) A Fastify plugin for waterline. Decorates Fastify with waterline models. - [`fastify-204`](https://github.com/Shiva127/fastify-204) Fastify plugin that From eb2647f8d779513e20ab2b0c5f5b08270f6f3dcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:07:46 +0000 Subject: [PATCH 1214/1295] chore: Bump actions/dependency-review-action from 4.8.1 to 4.8.2 (#6433) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.8.1 to 4.8.2. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/40c09b7dc99638e5ddb0bfd91c1673effc064d8a...3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-version: 4.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 014d061076a..7a3bc2b88cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Dependency review - uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1 + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 check-licenses: name: Check licenses From 41662018895b4009f676e5cb0820f7ecaca59e8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:09:14 +0000 Subject: [PATCH 1215/1295] chore: Bump markdownlint-cli2 from 0.18.1 to 0.20.0 (#6436) Bumps [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) from 0.18.1 to 0.20.0. - [Changelog](https://github.com/DavidAnson/markdownlint-cli2/blob/main/CHANGELOG.md) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.18.1...v0.20.0) --- updated-dependencies: - dependency-name: markdownlint-cli2 dependency-version: 0.20.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 227c02f64ca..9734fa2a2f4 100644 --- a/package.json +++ b/package.json @@ -191,7 +191,7 @@ "joi": "^18.0.1", "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", - "markdownlint-cli2": "^0.18.1", + "markdownlint-cli2": "^0.20.0", "neostandard": "^0.12.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", From 4c10980bd4a5a4d19e051f52a018779e6afb588b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:10:13 +0000 Subject: [PATCH 1216/1295] chore: Bump @types/node in the dev-dependencies-typescript group (#6435) Bumps the dev-dependencies-typescript group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 24.10.4 to 25.0.3 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.0.3 dependency-type: direct:development update-type: version-update:semver-major dependency-group: dev-dependencies-typescript ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9734fa2a2f4..e6cb8f05797 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "@sinonjs/fake-timers": "^11.2.2", "@stylistic/eslint-plugin": "^5.1.0", "@stylistic/eslint-plugin-js": "^4.1.0", - "@types/node": "^24.0.12", + "@types/node": "^25.0.3", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", From f18cda12d62a81f41d14b5c41cd674dd2abef817 Mon Sep 17 00:00:00 2001 From: Ankan Misra <143676135+AnkanMisra@users.noreply.github.com> Date: Fri, 2 Jan 2026 22:09:58 +0530 Subject: [PATCH 1217/1295] fix(ts): Align routerOptions defaultRoute types with runtime (#6392) --- build/build-validation.js | 2 +- docs/Reference/Server.md | 9 +++++++ fastify.d.ts | 28 +++++++++++----------- lib/config-validator.js | 20 +++++----------- test/issue-4959.test.js | 43 +++++++++++++++++++++++++++------- test/router-options.test.js | 18 ++++++++++++++ test/types/instance.test-d.ts | 44 +++++++++++++++++++++-------------- 7 files changed, 107 insertions(+), 57 deletions(-) diff --git a/build/build-validation.js b/build/build-validation.js index 0b6d4c8b427..266dc33d7e8 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -112,7 +112,7 @@ const schema = { useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.useSemicolonDelimiter }, routerOptions: { type: 'object', - additionalProperties: false, + additionalProperties: true, properties: { ignoreTrailingSlash: { type: 'boolean', default: defaultInitOptions.routerOptions.ignoreTrailingSlash }, ignoreDuplicateSlashes: { type: 'boolean', default: defaultInitOptions.routerOptions.ignoreDuplicateSlashes }, diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 7eff0fc8172..8832ddfce79 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -843,6 +843,12 @@ const fastify = require('fastify')({ }) ``` +> **Note** +> The `req` and `res` objects passed to `defaultRoute` are the raw Node.js +> `IncomingMessage` and `ServerResponse` instances. They do **not** expose the +> Fastify-specific methods available on `FastifyRequest`/`FastifyReply` (for +> example, `res.send`). + ### `ignoreDuplicateSlashes` @@ -934,6 +940,9 @@ const fastify = require('fastify')({ }) ``` +As with `defaultRoute`, `req` and `res` are the raw Node.js request/response +objects and do not provide Fastify's decorated helpers. + ### `querystringParser` diff --git a/fastify.d.ts b/fastify.d.ts index 65891df118e..2f34f8b2f1c 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -6,7 +6,7 @@ import { Socket } from 'node:net' import { Options as AjvOptions, ValidatorFactory } from '@fastify/ajv-compiler' import { FastifyError } from '@fastify/error' import { Options as FJSOptions, SerializerFactory } from '@fastify/fast-json-stringify-compiler' -import { ConstraintStrategy, HTTPVersion } from 'find-my-way' +import { Config as FindMyWayConfig, ConstraintStrategy, HTTPVersion } from 'find-my-way' import { InjectOptions, CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request' import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, FastifyContentTypeParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction } from './types/content-type-parser' @@ -77,6 +77,7 @@ declare namespace fastify { } type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2 + type FindMyWayConfigForServer = FindMyWayConfig> export interface ConnectionError extends Error { code: string, @@ -89,20 +90,17 @@ declare namespace fastify { type TrustProxyFunction = (address: string, hop: number) => boolean - export type FastifyRouterOptions = { - allowUnsafeRegex?: boolean, - buildPrettyMeta?: (route: { [k: string]: unknown, store: { [k: string]: unknown } }) => object, - caseSensitive?: boolean, - constraints?: { - [name: string]: ConstraintStrategy, unknown>, - }, - defaultRoute?: (req: FastifyRequest, res: FastifyReply) => void, - ignoreDuplicateSlashes?: boolean, - ignoreTrailingSlash?: boolean, - maxParamLength?: number, - onBadUrl?: (path: string, req: FastifyRequest, res: FastifyReply) => void, - querystringParser?: (str: string) => { [key: string]: unknown }, - useSemicolonDelimiter?: boolean, + export type FastifyRouterOptions = Omit, 'defaultRoute' | 'onBadUrl' | 'querystringParser'> & { + defaultRoute?: ( + req: RawRequestDefaultExpression, + res: RawReplyDefaultExpression + ) => void, + onBadUrl?: ( + path: string, + req: RawRequestDefaultExpression, + res: RawReplyDefaultExpression + ) => void, + querystringParser?: (str: string) => { [key: string]: unknown } } /** diff --git a/lib/config-validator.js b/lib/config-validator.js index a110fb6bcb5..b44ef0b1309 100644 --- a/lib/config-validator.js +++ b/lib/config-validator.js @@ -3,7 +3,7 @@ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":false,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":true,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -1022,13 +1022,6 @@ data23.allowUnsafeRegex = false; if(data23.useSemicolonDelimiter === undefined){ data23.useSemicolonDelimiter = false; } -const _errs71 = errors; -for(const key2 in data23){ -if(!(((((key2 === "ignoreTrailingSlash") || (key2 === "ignoreDuplicateSlashes")) || (key2 === "maxParamLength")) || (key2 === "allowUnsafeRegex")) || (key2 === "useSemicolonDelimiter"))){ -delete data23[key2]; -} -} -if(_errs71 === errors){ let data24 = data23.ignoreTrailingSlash; const _errs72 = errors; if(typeof data24 !== "boolean"){ @@ -1157,7 +1150,6 @@ var valid7 = _errs80 === errors; } } } -} else { validate10.errors = [{instancePath:instancePath+"/routerOptions",schemaPath:"#/properties/routerOptions/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; @@ -1174,14 +1166,14 @@ let data29 = data.constraints; const _errs82 = errors; if(errors === _errs82){ if(data29 && typeof data29 == "object" && !Array.isArray(data29)){ -for(const key3 in data29){ -let data30 = data29[key3]; +for(const key2 in data29){ +let data30 = data29[key2]; const _errs85 = errors; if(errors === _errs85){ if(data30 && typeof data30 == "object" && !Array.isArray(data30)){ let missing1; if(((((data30.name === undefined) && (missing1 = "name")) || ((data30.storage === undefined) && (missing1 = "storage"))) || ((data30.validate === undefined) && (missing1 = "validate"))) || ((data30.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){ -validate10.errors = [{instancePath:instancePath+"/constraints/" + key3.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; +validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; return false; } else { @@ -1198,7 +1190,7 @@ else if(data31 === null){ coerced31 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/constraints/" + key3.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } @@ -1213,7 +1205,7 @@ data30["name"] = coerced31; } } else { -validate10.errors = [{instancePath:instancePath+"/constraints/" + key3.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/type",keyword:"type",params:{type: "object"},message:"must be object"}]; +validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; } } diff --git a/test/issue-4959.test.js b/test/issue-4959.test.js index 271bc5d9ccf..87c98a3a520 100644 --- a/test/issue-4959.test.js +++ b/test/issue-4959.test.js @@ -11,7 +11,7 @@ const { setTimeout } = require('node:timers') * * @see https://github.com/fastify/fastify/issues/4959 */ -function runBadClientCall (reqOptions, payload) { +function runBadClientCall (reqOptions, payload, waitBeforeDestroy) { let innerResolve, innerReject const promise = new Promise((resolve, reject) => { innerResolve = resolve @@ -30,11 +30,25 @@ function runBadClientCall (reqOptions, payload) { innerReject(new Error('Request should have failed')) }) - // Kill the socket immediately (before sending data) - req.on('socket', (socket) => { - socket.on('connect', () => { - setTimeout(() => { socket.destroy() }, 0) - }) + // Kill the socket after the request has been fully written. + // Destroying it on `connect` can race before any bytes are sent, making the + // server-side assertions (hooks/handler) non-deterministic. + // + // To keep the test deterministic, we optionally wait for a server-side signal + // (e.g. onSend entered) before aborting the client. + let socket + req.on('socket', (s) => { socket = s }) + req.on('finish', () => { + if (waitBeforeDestroy && typeof waitBeforeDestroy.then === 'function') { + Promise.race([ + waitBeforeDestroy, + new Promise(resolve => setTimeout(resolve, 200)) + ]).then(() => { + if (socket) socket.destroy() + }, innerResolve) + return + } + setTimeout(() => { socket.destroy() }, 0) }) req.on('error', innerResolve) req.write(postData) @@ -47,6 +61,11 @@ test('should handle a socket error', async (t) => { t.plan(4) const fastify = Fastify() + let resolveOnSendEntered + const onSendEntered = new Promise((resolve) => { + resolveOnSendEntered = resolve + }) + function shouldNotHappen () { t.assert.fail('This should not happen') } @@ -70,8 +89,14 @@ test('should handle a socket error', async (t) => { t.assert.ok('onSend hook called') request.onSendCalled = true - // Introduce a delay - await new Promise(resolve => setTimeout(resolve, 5)) + if (resolveOnSendEntered) { + resolveOnSendEntered() + resolveOnSendEntered = null + } + + // Introduce a delay (gives time for client-side abort to happen while the + // request has already been processed, exercising the original issue). + await new Promise(resolve => setTimeout(resolve, 50)) return payload }) @@ -88,6 +113,6 @@ test('should handle a socket error', async (t) => { port: fastify.server.address().port, path: '/', method: 'PUT' - }, { test: 'me' }) + }, { test: 'me' }, onSendEntered) t.assert.equal(err.code, 'ECONNRESET') }) diff --git a/test/router-options.test.js b/test/router-options.test.js index ad5fafd57eb..1a4998c6cec 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -895,3 +895,21 @@ test('Should honor routerOptions.useSemicolonDelimiter over useSemicolonDelimite }) t.assert.strictEqual(res.statusCode, 200) }) + +test('Should support extra find-my-way options', async t => { + t.plan(1) + // Use a real upstream option from find-my-way + const fastify = Fastify({ + routerOptions: { + buildPrettyMeta: (route) => { + const cleanMeta = Object.assign({}, route.store) + return cleanMeta + } + } + }) + + await fastify.ready() + + // Ensure the option is preserved after validation + t.assert.strictEqual(typeof fastify.initialConfig.routerOptions.buildPrettyMeta, 'function') +}) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index b975249fec8..8b7144e96c9 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -1,9 +1,10 @@ -import { expectAssignable, expectError, expectNotDeprecated, expectType } from 'tsd' +import { expectAssignable, expectError, expectNotAssignable, expectNotDeprecated, expectType } from 'tsd' import fastify, { FastifyBaseLogger, FastifyBodyParser, FastifyError, FastifyInstance, + FastifyRouterOptions, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault, @@ -15,7 +16,7 @@ import { FastifyRequest } from '../../types/request' import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema' import { AddressInfo } from 'node:net' import { Bindings, ChildLoggerOptions } from '../../types/logger' -import { ConstraintStrategy } from 'find-my-way' +import { Config as FindMyWayConfig, ConstraintStrategy } from 'find-my-way' import { FindMyWayVersion } from '../../types/instance' const server = fastify() @@ -332,25 +333,32 @@ type InitialConfig = Readonly<{ requestIdLogLabel?: string, http2SessionTimeout?: number, useSemicolonDelimiter?: boolean, - routerOptions?: { - allowUnsafeRegex?: boolean, - buildPrettyMeta?: (route: { [k: string]: unknown, store: { [k: string]: unknown } }) => object, - caseSensitive?: boolean, - constraints?: { - [name: string]: ConstraintStrategy, unknown> - } - defaultRoute?: (req: FastifyRequest, res: FastifyReply) => void, - ignoreDuplicateSlashes?: boolean, - ignoreTrailingSlash?: boolean, - maxParamLength?: number, - onBadUrl?: (path: string, req: FastifyRequest, res: FastifyReply) => void, - querystringParser?: (str: string) => { [key: string]: unknown }, - useSemicolonDelimiter?: boolean, - } + routerOptions?: FastifyRouterOptions }> expectType(fastify().initialConfig) +const routerOptionsForFindMyWay = {} as FastifyRouterOptions +expectAssignable>>(routerOptionsForFindMyWay) + +fastify({ + routerOptions: { + defaultRoute: (req, res) => { + expectType>(req) + expectType>(res) + expectNotAssignable(res) + res.end('foo') + }, + onBadUrl: (path, req, res) => { + expectType(path) + expectType>(req) + expectType>(res) + expectNotAssignable(res) + res.end('foo') + } + } +}) + expectType>(server.defaultTextParser) expectType>(server.getDefaultJsonParser('ignore', 'error')) @@ -562,7 +570,7 @@ expectError(server.decorateReply('typedTestReplyMethod', async function (x) { const foo = server.getDecorator('foo') expectType(foo) -const versionConstraintStrategy = { +const versionConstraintStrategy: ConstraintStrategy> = { name: 'version', storage: () => ({ get: () => () => {}, From c02c5f5696c8695184f355891106ec26139cb559 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <53142663+tt-a1i@users.noreply.github.com> Date: Sat, 3 Jan 2026 18:14:22 +0800 Subject: [PATCH 1218/1295] fix(types): require send() payload when Reply type is specified (#6432) --- test/types/instance.test-d.ts | 4 +- test/types/reply.test-d.ts | 59 ++++++++++++++++++++++++++++-- test/types/type-provider.test-d.ts | 12 +++--- types/reply.d.ts | 4 +- types/type-provider.d.ts | 16 ++++++++ 5 files changed, 81 insertions(+), 14 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 8b7144e96c9..7e6bf658409 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -93,12 +93,12 @@ interface ReplyPayload { // typed sync error handler server.setErrorHandler((error, request, reply) => { expectType(error) - expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) + expectType<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) }) // typed async error handler send server.setErrorHandler(async (error, request, reply) => { expectType(error) - expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) + expectType<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) }) // typed async error handler return server.setErrorHandler(async (error, request, reply) => { diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index 872a129827d..f66e298d6e4 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -16,14 +16,14 @@ const getHandler: RouteHandlerMethod = function (_request, reply) { expectType>(reply.request) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.status) - expectType<(payload?: unknown) => FastifyReply>(reply.code(100 as number).send) + expectType<(...args: [payload?: unknown]) => FastifyReply>(reply.code(100 as number).send) expectType(reply.elapsedTime) expectType(reply.statusCode) expectType(reply.sent) expectType< (hints: Record, callback?: (() => void) | undefined) => void >(reply.writeEarlyHints) - expectType<((payload?: unknown) => FastifyReply)>(reply.send) + expectType<((...args: [payload?: unknown]) => FastifyReply)>(reply.send) expectAssignable<(key: string, value: any) => FastifyReply>(reply.header) expectAssignable<(values: { [key: string]: any }) => FastifyReply>(reply.headers) expectAssignable<(key: string) => number | string | string[] | undefined>(reply.getHeader) @@ -100,9 +100,29 @@ interface InvalidReplyHttpCodes { } } +interface ReplyVoid { + Reply: void; +} + +interface ReplyUndefined { + Reply: undefined; +} + +// Issue #5534 scenario: 204 No Content should allow empty send(), 201 Created should require payload +// Note: `204: undefined` gets converted to `unknown` via UndefinedToUnknown in type-provider.d.ts, +// meaning send() is optional but send({}) is also allowed. Use `void` instead of `undefined` +// if you want stricter "no payload allowed" semantics. +interface ReplyHttpCodesWithNoContent { + Reply: { + 201: { id: string }; + 204: undefined; + } +} + const typedHandler: RouteHandler = async (request, reply) => { - expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) - expectType<((payload?: ReplyPayload['Reply']) => FastifyReply, RawReplyDefaultExpression>)>(reply.code(100).send) + // When Reply type is specified, send() requires a payload argument + expectType<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) + expectType<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply, RawReplyDefaultExpression>)>(reply.code(100).send) } const server = fastify() @@ -111,6 +131,10 @@ server.get('/typed', typedHandler) server.get('/get-generic-send', async function handler (request, reply) { reply.send({ test: true }) }) +// When Reply type is specified, send() requires a payload - calling without arguments should error +expectError(server.get('/get-generic-send-missing-payload', async function handler (request, reply) { + reply.send() +})) server.get('/get-generic-return', async function handler (request, reply) { return { test: false } }) @@ -201,3 +225,30 @@ const httpHeaderHandler: RouteHandlerMethod = function (_request, reply) { reply.headers({ 'x-fastify-test': 'test' }) reply.removeHeader('x-fastify-test') } + +// Test: send() without arguments is valid when no Reply type is specified (default unknown) +server.get('/get-no-type-send-empty', async function handler (request, reply) { + reply.send() +}) + +// Test: send() without arguments is valid when Reply type is void +server.get('/get-void-send-empty', async function handler (request, reply) { + reply.send() +}) + +// Test: send() without arguments is valid when Reply type is undefined +server.get('/get-undefined-send-empty', async function handler (request, reply) { + reply.send() +}) + +// Issue #5534 scenario: HTTP status codes with 204 No Content +server.get('/get-http-codes-no-content', async function handler (request, reply) { + // 204 No Content - send() without payload is valid because Reply is undefined + reply.code(204).send() + // 201 Created - send() requires payload + reply.code(201).send({ id: '123' }) +}) +// 201 Created without payload should error +expectError(server.get('/get-http-codes-201-missing-payload', async function handler (request, reply) { + reply.code(201).send() +})) diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index 6cc20d87046..bff7b944d17 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -479,9 +479,9 @@ expectAssignable(server.withTypeProvider().get( res.send('hello') res.send(42) res.send({ error: 'error' }) - expectType<(payload?: string) => typeof res>(res.code(200).send) - expectType<(payload?: number) => typeof res>(res.code(400).send) - expectType<(payload?: { error: string }) => typeof res>(res.code(500).send) + expectType<((...args: [payload: string]) => typeof res)>(res.code(200).send) + expectType<((...args: [payload: number]) => typeof res)>(res.code(400).send) + expectType<((...args: [payload: { error: string }]) => typeof res)>(res.code(500).send) expectError<(payload?: unknown) => typeof res>(res.code(200).send) } )) @@ -711,9 +711,9 @@ expectAssignable(server.withTypeProvider().get( res.send('hello') res.send(42) res.send({ error: 'error' }) - expectType<(payload?: string) => typeof res>(res.code(200).send) - expectType<(payload?: number) => typeof res>(res.code(400).send) - expectType<(payload?: { [x: string]: unknown; error?: string }) => typeof res>(res.code(500).send) + expectType<((...args: [payload: string]) => typeof res)>(res.code(200).send) + expectType<((...args: [payload: number]) => typeof res)>(res.code(400).send) + expectType<((...args: [payload: { [x: string]: unknown; error?: string }]) => typeof res)>(res.code(500).send) expectError<(payload?: unknown) => typeof res>(res.code(200).send) } )) diff --git a/types/reply.d.ts b/types/reply.d.ts index 631c4037720..ff405c366bf 100644 --- a/types/reply.d.ts +++ b/types/reply.d.ts @@ -4,7 +4,7 @@ import { FastifyBaseLogger } from './logger' import { FastifyRequest, RequestRouteOptions } from './request' import { RouteGenericInterface } from './route' import { FastifySchema } from './schema' -import { CallSerializerTypeProvider, FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider' +import { CallSerializerTypeProvider, FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType, SendArgs } from './type-provider' import { CodeToReplyKey, ContextConfigDefault, HttpHeader, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils' export interface ReplyGenericInterface { @@ -50,7 +50,7 @@ export interface FastifyReply< status : keyof SchemaCompiler['response'] extends ReplyKeysToCodes ? keyof SchemaCompiler['response'] : ReplyKeysToCodes>(statusCode: Code): FastifyReply>; statusCode: number; sent: boolean; - send(payload?: ReplyType): FastifyReply; + send(...args: SendArgs): FastifyReply; header(key: HttpHeader, value: any): FastifyReply; headers(values: Partial>): FastifyReply; getHeader(key: HttpHeader): number | string | string[] | undefined; diff --git a/types/type-provider.d.ts b/types/type-provider.d.ts index b877d3ebfeb..002b08248ce 100644 --- a/types/type-provider.d.ts +++ b/types/type-provider.d.ts @@ -112,3 +112,19 @@ RouteGeneric * https://github.com/fastify/fastify/issues/5498 */ export type SafePromiseLike = PromiseLike & { __linterBrands: 'SafePromiseLike' } + +// ----------------------------------------------------------------------------------------------- +// SendArgs +// ----------------------------------------------------------------------------------------------- + +/** + * Determines whether the send() payload parameter should be required or optional. + * - When ReplyType is unknown (default/unspecified), payload is optional + * - When ReplyType is undefined or void, payload is optional (returning undefined is valid) + * - Otherwise, payload is required + */ +export type SendArgs = unknown extends ReplyType + ? [payload?: ReplyType] + : [ReplyType] extends [undefined | void] + ? [payload?: ReplyType] + : [payload: ReplyType] From 1d5f8ae199edddea68f6df4c5d68b62279e6c59d Mon Sep 17 00:00:00 2001 From: Gianmarco Poggi <37111498+gianmarco27@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:34:18 +0100 Subject: [PATCH 1219/1295] fix: ajv options type validation (#6437) --- docs/Reference/Server.md | 6 +++++- fastify.d.ts | 7 ++----- fastify.js | 2 +- package.json | 2 +- test/types/fastify.test-d.ts | 26 +++++++++++++++++++++++--- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 8832ddfce79..371d1214536 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -569,7 +569,11 @@ const fastify = require('fastify')({ [require('ajv-keywords'), 'instanceof'] // Usage: [plugin, pluginOptions] - Plugin with options // Usage: plugin - Plugin without options - ] + ], + onCreate: (ajv) => { + // Modify the ajv instance as you need. + ajv.addFormat('myFormat', (data) => typeof data === 'string') + } } }) ``` diff --git a/fastify.d.ts b/fastify.d.ts index 2f34f8b2f1c..8ef540879ae 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -3,7 +3,7 @@ import * as http2 from 'node:http2' import * as https from 'node:https' import { Socket } from 'node:net' -import { Options as AjvOptions, ValidatorFactory } from '@fastify/ajv-compiler' +import { BuildCompilerFromPool, ValidatorFactory } from '@fastify/ajv-compiler' import { FastifyError } from '@fastify/error' import { Options as FJSOptions, SerializerFactory } from '@fastify/fast-json-stringify-compiler' import { Config as FindMyWayConfig, ConstraintStrategy, HTTPVersion } from 'find-my-way' @@ -151,10 +151,7 @@ declare namespace fastify { }; }; return503OnClosing?: boolean, - ajv?: { - customOptions?: AjvOptions, - plugins?: (Function | [Function, unknown])[] - }, + ajv?: Parameters[1], frameworkErrors?: ( error: FastifyError, req: FastifyRequest, FastifySchema, TypeProvider>, diff --git a/fastify.js b/fastify.js index d02ab6dd8f5..d922ce55378 100644 --- a/fastify.js +++ b/fastify.js @@ -82,7 +82,7 @@ const { FSTWRN004 } = require('./lib/warnings.js') const initChannel = diagnostics.channel('fastify.initialization') /** - * @param {import('./fastify.js').FastifyServerOptions} options + * @param {import('./fastify.js').FastifyServerOptions} serverOptions */ function fastify (serverOptions) { const { diff --git a/package.json b/package.json index e6cb8f05797..f62fa0461fd 100644 --- a/package.json +++ b/package.json @@ -203,7 +203,7 @@ "yup": "^1.4.0" }, "dependencies": { - "@fastify/ajv-compiler": "^4.0.0", + "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 17740b5b8a0..49de8b82269 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -1,4 +1,4 @@ -import { ErrorObject as AjvErrorObject } from 'ajv' +import Ajv, { ErrorObject as AjvErrorObject } from 'ajv' import * as http from 'node:http' import * as http2 from 'node:http2' import * as https from 'node:https' @@ -232,12 +232,32 @@ expectAssignable(fastify({ customOptions: { removeAdditional: 'all' }, - plugins: [() => { }] + plugins: [(ajv: Ajv): Ajv => ajv] } })) expectAssignable(fastify({ ajv: { - plugins: [[() => { }, ['keyword1', 'keyword2']]] + plugins: [[(ajv: Ajv): Ajv => ajv, ['keyword1', 'keyword2']]] + } +})) +expectError(fastify({ + ajv: { + customOptions: { + removeAdditional: 'all' + }, + plugins: [ + () => { + // error, plugins always return the Ajv instance fluently + } + ] + } +})) +expectAssignable(fastify({ + ajv: { + onCreate: (ajvInstance) => { + expectType(ajvInstance) + return ajvInstance + } } })) expectAssignable(fastify({ frameworkErrors: () => { } })) From a397559a8791d82a4a26ee937b9e09e6fdb9a671 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Tue, 6 Jan 2026 13:26:51 -0300 Subject: [PATCH 1220/1295] docs: add non-vulnerability examples to threat model (#6431) Extends the threat model section with clear examples of what should not be reported as vulnerabilities in Fastify core, including application code issues, configuration mistakes, and missing features. Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- SECURITY.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index 069ee08a78c..4dd4f7af96d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -20,6 +20,29 @@ parameters). - DoS through malformed input to Fastify's core - Bypasses of built-in protections (prototype poisoning, schema validation) +### Examples of Non-Vulnerabilities + +The following are **not** considered vulnerabilities in Fastify: + +- **Application code vulnerabilities**: XSS, SQL injection, or other flaws in +user-written route handlers, hooks, or plugins +- **Malicious application code**: Issues caused by intentionally malicious +plugins or handlers (application code is trusted) +- **Validation schema issues**: Weak or incorrect schemas provided by developers +(schemas are trusted) +- **ReDoS in user patterns**: Regular expression DoS in user-provided regex +patterns for routes or validation +- **Missing security features**: Lack of rate limiting, authentication, or +authorization (these are application-level concerns) +- **Configuration mistakes**: Security issues arising from developer +misconfiguration (configuration is trusted) +- **Third-party dependencies**: Vulnerabilities in npm packages used by the +application (not Fastify core dependencies) +- **Resource exhaustion from handlers**: DoS caused by expensive operations in +user route handlers +- **Information disclosure by design**: Exposing error details or stack traces +explicitly enabled via configuration options + ## Reporting vulnerabilities Individuals who find potential vulnerabilities in Fastify are invited to From 1b3381e73a423b54563c8207b31017a3381aa122 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Tue, 13 Jan 2026 10:08:00 +0200 Subject: [PATCH 1221/1295] feat: implement conditional request logging (#5732) --- README.md | 3 +- build/build-validation.js | 1 - docs/Guides/Ecosystem.md | 4 +- docs/Guides/Migration-Guide-V4.md | 3 +- docs/Guides/Testing.md | 4 +- docs/Reference/Decorators.md | 3 +- docs/Reference/Encapsulation.md | 4 +- docs/Reference/Plugins.md | 4 +- docs/Reference/Principles.md | 4 +- docs/Reference/Reply.md | 5 +- docs/Reference/Server.md | 18 +- docs/Reference/TypeScript.md | 3 +- fastify.d.ts | 2 +- fastify.js | 6 +- lib/config-validator.js | 398 ++++++++++++++---------------- lib/four-oh-four.js | 3 +- lib/route.js | 36 ++- test/404s.test.js | 49 ++++ test/logger/logging.test.js | 39 ++- test/router-options.test.js | 151 ++++++++++++ test/types/fastify.test-d.ts | 1 + test/types/instance.test-d.ts | 2 +- types/instance.d.ts | 2 +- 23 files changed, 496 insertions(+), 249 deletions(-) diff --git a/README.md b/README.md index 7f794e36015..2974d4bffc4 100644 --- a/README.md +++ b/README.md @@ -260,7 +260,8 @@ application. You should __always__ benchmark if performance matters to you. Please visit [Fastify help](https://github.com/fastify/help) to view prior support issues and to ask new support questions. -Version 3 of Fastify and lower are EOL and will not receive any security or bug fixes. +Version 3 of Fastify and lower are EOL and will not receive any security or bug +fixes. Fastify's partner, HeroDevs, provides commercial security fixes for all unsupported versions at [https://herodevs.com/support/fastify-nes][hd-link]. diff --git a/build/build-validation.js b/build/build-validation.js index 266dc33d7e8..c252478a396 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -98,7 +98,6 @@ const schema = { ignoreTrailingSlash: { type: 'boolean', default: defaultInitOptions.ignoreTrailingSlash }, ignoreDuplicateSlashes: { type: 'boolean', default: defaultInitOptions.ignoreDuplicateSlashes }, disableRequestLogging: { - type: 'boolean', default: false }, maxParamLength: { type: 'integer', default: defaultInitOptions.maxParamLength }, diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 6bba623de8e..2ca8346549b 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -566,8 +566,8 @@ middlewares into Fastify plugins custom permission checks. - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker thread pool plugin using [Piscina](https://github.com/piscinajs/piscina). -- [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin to - handle i18n using +- [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin + to handle i18n using [node-polyglot](https://www.npmjs.com/package/node-polyglot). - [`fastify-postgraphile`](https://github.com/alemagio/fastify-postgraphile) Plugin to integrate [PostGraphile](https://www.graphile.org/postgraphile/) in diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 4e66020727f..34a820acc7e 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -109,7 +109,8 @@ argument from your router handler. ### `exposeHeadRoutes` true by default Starting with v4, every `GET` route will create a sibling `HEAD` route. -You can revert this behavior by setting `exposeHeadRoutes: false` in the server options. +You can revert this behavior by setting `exposeHeadRoutes: false` in the server +options. ### Synchronous route definitions ([#2954](https://github.com/fastify/fastify/pull/2954)) diff --git a/docs/Guides/Testing.md b/docs/Guides/Testing.md index 5fdf864787b..4901191e5a6 100644 --- a/docs/Guides/Testing.md +++ b/docs/Guides/Testing.md @@ -353,8 +353,8 @@ Now you should be able to step through your test file (and the rest of ## Plugins -Let's `cd` into a fresh directory called 'testing-plugin-example' and type `npm init --y` in our terminal. +Let's `cd` into a fresh directory called 'testing-plugin-example' and type +`npm init -y` in our terminal. Run `npm i fastify fastify-plugin` diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 3ff77fb7cbd..80878d19c76 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -369,7 +369,8 @@ console.log(fastify.foo) // 'a getter' #### `getDecorator(name)` -Used to retrieve an existing decorator from the Fastify instance, `Request`, or `Reply`. +Used to retrieve an existing decorator from the Fastify instance, `Request`, +or `Reply`. If the decorator is not defined, an `FST_ERR_DEC_UNDECLARED` error is thrown. ```js diff --git a/docs/Reference/Encapsulation.md b/docs/Reference/Encapsulation.md index 0ea5cc6bdfe..cb02ffb0126 100644 --- a/docs/Reference/Encapsulation.md +++ b/docs/Reference/Encapsulation.md @@ -28,8 +28,8 @@ registered within its _grandchild context_. Given that everything in Fastify is a [plugin](./Plugins.md) except for the _root context_, every "context" and "plugin" in this example is a plugin that can consist of decorators, hooks, plugins, and routes. As plugins, they -must still signal completion either by returning a Promise (e.g., using `async` functions) -or by calling the `done` function if using the callback style. +must still signal completion either by returning a Promise (e.g., using `async` +functions) or by calling the `done` function if using the callback style. To put this example into concrete terms, consider a basic scenario of a REST API server diff --git a/docs/Reference/Plugins.md b/docs/Reference/Plugins.md index 6e98a470e0e..944553d76c3 100644 --- a/docs/Reference/Plugins.md +++ b/docs/Reference/Plugins.md @@ -79,8 +79,8 @@ the Fastify instance by a preceding plugin, such as utilizing an existing databa connection. Keep in mind that the Fastify instance passed to the function is the same as the -one passed into the plugin, a copy of the external Fastify instance rather than a -reference. Any usage of the instance will behave the same as it would if called +one passed into the plugin, a copy of the external Fastify instance rather than +a reference. Any usage of the instance will behave the same as it would if called within the plugin's function. For example, if `decorate` is called, the decorated variables will be available within the plugin's function unless it was wrapped with [`fastify-plugin`](https://github.com/fastify/fastify-plugin). diff --git a/docs/Reference/Principles.md b/docs/Reference/Principles.md index 6acd3adce0a..fc5694cbbc8 100644 --- a/docs/Reference/Principles.md +++ b/docs/Reference/Principles.md @@ -64,8 +64,8 @@ frameworks do this; Fastify does not. ## Semantic Versioning and Long Term Support -A clear [Long Term Support strategy is provided](./LTS.md) to inform developers when -to upgrade. +A clear [Long Term Support strategy is provided](./LTS.md) to inform developers +when to upgrade. ## Specification adherence diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index cc1eac7611a..ed21f9756ec 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -70,8 +70,9 @@ since the request was received by Fastify. - `.serialize(payload)` - Serializes the specified payload using the default JSON serializer or using the custom serializer (if one is set) and returns the serialized payload. -- `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the serialization - function for the specified schema or http status, if any of either are set. +- `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the + serialization function for the specified schema or http status, if any of + either are set. - `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles the specified schema and returns a serialization function using the default (or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 371d1214536..7f217f72c2e 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -331,6 +331,20 @@ been sent. By setting this option to `true`, these log messages will be disabled. This allows for more flexible request start and end logging by attaching custom `onRequest` and `onResponse` hooks. +This option can also be a function that receives the Fastify request object +and returns a boolean. This allows for conditional request logging based on the +request properties (e.g., URL, headers, decorations). + +```js +const fastify = require('fastify')({ + logger: true, + disableRequestLogging: (request) => { + // Disable logging for health check endpoints + return request.url === '/health' || request.url === '/ready' + } +}) +``` + The other log entries that will be disabled are: - an error log written by the default `onResponse` hook on reply callback errors - the error and info logs written by the `defaultErrorHandler` @@ -1806,8 +1820,8 @@ different plugins can set different logger factories. `fastify.setGenReqId(function (rawReq))` Synchronous function for setting the request-id -for additional Fastify instances. It will receive the _raw_ incoming request as a -parameter. The provided function should not throw an Error in any case. +for additional Fastify instances. It will receive the _raw_ incoming request as +a parameter. The provided function should not throw an Error in any case. Especially in distributed systems, you may want to override the default ID generation behavior to handle custom ways of generating different IDs in diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index e647a860759..e98f065f7b4 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -691,7 +691,8 @@ Or even explicit config on tsconfig Fastify's `getDecorator` method retrieves decorators with enhanced type safety. -The `getDecorator` method supports generic type parameters for enhanced type safety: +The `getDecorator` method supports generic type parameters for enhanced type +safety: ```typescript // Type-safe decorator retrieval diff --git a/fastify.d.ts b/fastify.d.ts index 8ef540879ae..548772091ae 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -120,7 +120,7 @@ declare namespace fastify { pluginTimeout?: number, bodyLimit?: number, maxParamLength?: number, - disableRequestLogging?: boolean, + disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean), exposeHeadRoutes?: boolean, onProtoPoisoning?: ProtoAction, onConstructorPoisoning?: ConstructorAction, diff --git a/fastify.js b/fastify.js index d922ce55378..4d866c01126 100644 --- a/fastify.js +++ b/fastify.js @@ -641,7 +641,8 @@ function fastify (serverOptions) { const request = new Request(id, null, req, null, childLogger, onBadUrlContext) const reply = new Reply(res, request, childLogger) - if (disableRequestLogging === false) { + const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(req) : disableRequestLogging + if (resolvedDisableRequestLogging === false) { childLogger.info({ req: request }, 'incoming request') } @@ -671,7 +672,8 @@ function fastify (serverOptions) { const request = new Request(id, null, req, null, childLogger, onBadUrlContext) const reply = new Reply(res, request, childLogger) - if (disableRequestLogging === false) { + const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(req) : disableRequestLogging + if (resolvedDisableRequestLogging === false) { childLogger.info({ req: request }, 'incoming request') } diff --git a/lib/config-validator.js b/lib/config-validator.js index b44ef0b1309..4ce5787b074 100644 --- a/lib/config-validator.js +++ b/lib/config-validator.js @@ -3,7 +3,7 @@ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":true,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":true,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -685,56 +685,57 @@ data["ignoreDuplicateSlashes"] = coerced14; } var valid0 = _errs43 === errors; if(valid0){ -let data13 = data.disableRequestLogging; +let data13 = data.maxParamLength; const _errs45 = errors; -if(typeof data13 !== "boolean"){ +if(!(((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13))) && (isFinite(data13)))){ +let dataType15 = typeof data13; let coerced15 = undefined; if(!(coerced15 !== undefined)){ -if(data13 === "false" || data13 === 0 || data13 === null){ -coerced15 = false; -} -else if(data13 === "true" || data13 === 1){ -coerced15 = true; +if(dataType15 === "boolean" || data13 === null + || (dataType15 === "string" && data13 && data13 == +data13 && !(data13 % 1))){ +coerced15 = +data13; } else { -validate10.errors = [{instancePath:instancePath+"/disableRequestLogging",schemaPath:"#/properties/disableRequestLogging/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/maxParamLength",schemaPath:"#/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced15 !== undefined){ data13 = coerced15; if(data !== undefined){ -data["disableRequestLogging"] = coerced15; +data["maxParamLength"] = coerced15; } } } var valid0 = _errs45 === errors; if(valid0){ -let data14 = data.maxParamLength; +let data14 = data.onProtoPoisoning; const _errs47 = errors; -if(!(((typeof data14 == "number") && (!(data14 % 1) && !isNaN(data14))) && (isFinite(data14)))){ +if(typeof data14 !== "string"){ let dataType16 = typeof data14; let coerced16 = undefined; if(!(coerced16 !== undefined)){ -if(dataType16 === "boolean" || data14 === null - || (dataType16 === "string" && data14 && data14 == +data14 && !(data14 % 1))){ -coerced16 = +data14; +if(dataType16 == "number" || dataType16 == "boolean"){ +coerced16 = "" + data14; +} +else if(data14 === null){ +coerced16 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/maxParamLength",schemaPath:"#/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/onProtoPoisoning",schemaPath:"#/properties/onProtoPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } if(coerced16 !== undefined){ data14 = coerced16; if(data !== undefined){ -data["maxParamLength"] = coerced16; +data["onProtoPoisoning"] = coerced16; } } } var valid0 = _errs47 === errors; if(valid0){ -let data15 = data.onProtoPoisoning; +let data15 = data.onConstructorPoisoning; const _errs49 = errors; if(typeof data15 !== "string"){ let dataType17 = typeof data15; @@ -747,82 +748,56 @@ else if(data15 === null){ coerced17 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/onProtoPoisoning",schemaPath:"#/properties/onProtoPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/onConstructorPoisoning",schemaPath:"#/properties/onConstructorPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } if(coerced17 !== undefined){ data15 = coerced17; if(data !== undefined){ -data["onProtoPoisoning"] = coerced17; +data["onConstructorPoisoning"] = coerced17; } } } var valid0 = _errs49 === errors; if(valid0){ -let data16 = data.onConstructorPoisoning; +let data16 = data.pluginTimeout; const _errs51 = errors; -if(typeof data16 !== "string"){ +if(!(((typeof data16 == "number") && (!(data16 % 1) && !isNaN(data16))) && (isFinite(data16)))){ let dataType18 = typeof data16; let coerced18 = undefined; if(!(coerced18 !== undefined)){ -if(dataType18 == "number" || dataType18 == "boolean"){ -coerced18 = "" + data16; -} -else if(data16 === null){ -coerced18 = ""; +if(dataType18 === "boolean" || data16 === null + || (dataType18 === "string" && data16 && data16 == +data16 && !(data16 % 1))){ +coerced18 = +data16; } else { -validate10.errors = [{instancePath:instancePath+"/onConstructorPoisoning",schemaPath:"#/properties/onConstructorPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/pluginTimeout",schemaPath:"#/properties/pluginTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced18 !== undefined){ data16 = coerced18; if(data !== undefined){ -data["onConstructorPoisoning"] = coerced18; +data["pluginTimeout"] = coerced18; } } } var valid0 = _errs51 === errors; if(valid0){ -let data17 = data.pluginTimeout; +let data17 = data.requestIdHeader; const _errs53 = errors; -if(!(((typeof data17 == "number") && (!(data17 % 1) && !isNaN(data17))) && (isFinite(data17)))){ -let dataType19 = typeof data17; +const _errs54 = errors; +let valid6 = false; +const _errs55 = errors; +if(typeof data17 !== "boolean"){ let coerced19 = undefined; if(!(coerced19 !== undefined)){ -if(dataType19 === "boolean" || data17 === null - || (dataType19 === "string" && data17 && data17 == +data17 && !(data17 % 1))){ -coerced19 = +data17; -} -else { -validate10.errors = [{instancePath:instancePath+"/pluginTimeout",schemaPath:"#/properties/pluginTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; -return false; -} -} -if(coerced19 !== undefined){ -data17 = coerced19; -if(data !== undefined){ -data["pluginTimeout"] = coerced19; -} +if(data17 === "false" || data17 === 0 || data17 === null){ +coerced19 = false; } -} -var valid0 = _errs53 === errors; -if(valid0){ -let data18 = data.requestIdHeader; -const _errs55 = errors; -const _errs56 = errors; -let valid6 = false; -const _errs57 = errors; -if(typeof data18 !== "boolean"){ -let coerced20 = undefined; -if(!(coerced20 !== undefined)){ -if(data18 === "false" || data18 === 0 || data18 === null){ -coerced20 = false; -} -else if(data18 === "true" || data18 === 1){ -coerced20 = true; +else if(data17 === "true" || data17 === 1){ +coerced19 = true; } else { const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; @@ -835,26 +810,26 @@ vErrors.push(err12); errors++; } } -if(coerced20 !== undefined){ -data18 = coerced20; +if(coerced19 !== undefined){ +data17 = coerced19; if(data !== undefined){ -data["requestIdHeader"] = coerced20; +data["requestIdHeader"] = coerced19; } } } -var _valid3 = _errs57 === errors; +var _valid3 = _errs55 === errors; valid6 = valid6 || _valid3; if(!valid6){ -const _errs59 = errors; -if(typeof data18 !== "string"){ -let dataType21 = typeof data18; -let coerced21 = undefined; -if(!(coerced21 !== undefined)){ -if(dataType21 == "number" || dataType21 == "boolean"){ -coerced21 = "" + data18; +const _errs57 = errors; +if(typeof data17 !== "string"){ +let dataType20 = typeof data17; +let coerced20 = undefined; +if(!(coerced20 !== undefined)){ +if(dataType20 == "number" || dataType20 == "boolean"){ +coerced20 = "" + data17; } -else if(data18 === null){ -coerced21 = ""; +else if(data17 === null){ +coerced20 = ""; } else { const err13 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"}; @@ -867,14 +842,14 @@ vErrors.push(err13); errors++; } } -if(coerced21 !== undefined){ -data18 = coerced21; +if(coerced20 !== undefined){ +data17 = coerced20; if(data !== undefined){ -data["requestIdHeader"] = coerced21; +data["requestIdHeader"] = coerced20; } } } -var _valid3 = _errs59 === errors; +var _valid3 = _errs57 === errors; valid6 = valid6 || _valid3; } if(!valid6){ @@ -890,69 +865,94 @@ validate10.errors = vErrors; return false; } else { -errors = _errs56; +errors = _errs54; if(vErrors !== null){ -if(_errs56){ -vErrors.length = _errs56; +if(_errs54){ +vErrors.length = _errs54; } else { vErrors = null; } } } -var valid0 = _errs55 === errors; +var valid0 = _errs53 === errors; +if(valid0){ +let data18 = data.requestIdLogLabel; +const _errs59 = errors; +if(typeof data18 !== "string"){ +let dataType21 = typeof data18; +let coerced21 = undefined; +if(!(coerced21 !== undefined)){ +if(dataType21 == "number" || dataType21 == "boolean"){ +coerced21 = "" + data18; +} +else if(data18 === null){ +coerced21 = ""; +} +else { +validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +return false; +} +} +if(coerced21 !== undefined){ +data18 = coerced21; +if(data !== undefined){ +data["requestIdLogLabel"] = coerced21; +} +} +} +var valid0 = _errs59 === errors; if(valid0){ -let data19 = data.requestIdLogLabel; +let data19 = data.http2SessionTimeout; const _errs61 = errors; -if(typeof data19 !== "string"){ +if(!(((typeof data19 == "number") && (!(data19 % 1) && !isNaN(data19))) && (isFinite(data19)))){ let dataType22 = typeof data19; let coerced22 = undefined; if(!(coerced22 !== undefined)){ -if(dataType22 == "number" || dataType22 == "boolean"){ -coerced22 = "" + data19; -} -else if(data19 === null){ -coerced22 = ""; +if(dataType22 === "boolean" || data19 === null + || (dataType22 === "string" && data19 && data19 == +data19 && !(data19 % 1))){ +coerced22 = +data19; } else { -validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced22 !== undefined){ data19 = coerced22; if(data !== undefined){ -data["requestIdLogLabel"] = coerced22; +data["http2SessionTimeout"] = coerced22; } } } var valid0 = _errs61 === errors; if(valid0){ -let data20 = data.http2SessionTimeout; +let data20 = data.exposeHeadRoutes; const _errs63 = errors; -if(!(((typeof data20 == "number") && (!(data20 % 1) && !isNaN(data20))) && (isFinite(data20)))){ -let dataType23 = typeof data20; +if(typeof data20 !== "boolean"){ let coerced23 = undefined; if(!(coerced23 !== undefined)){ -if(dataType23 === "boolean" || data20 === null - || (dataType23 === "string" && data20 && data20 == +data20 && !(data20 % 1))){ -coerced23 = +data20; +if(data20 === "false" || data20 === 0 || data20 === null){ +coerced23 = false; +} +else if(data20 === "true" || data20 === 1){ +coerced23 = true; } else { -validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced23 !== undefined){ data20 = coerced23; if(data !== undefined){ -data["http2SessionTimeout"] = coerced23; +data["exposeHeadRoutes"] = coerced23; } } } var valid0 = _errs63 === errors; if(valid0){ -let data21 = data.exposeHeadRoutes; +let data21 = data.useSemicolonDelimiter; const _errs65 = errors; if(typeof data21 !== "boolean"){ let coerced24 = undefined; @@ -964,65 +964,65 @@ else if(data21 === "true" || data21 === 1){ coerced24 = true; } else { -validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced24 !== undefined){ data21 = coerced24; if(data !== undefined){ -data["exposeHeadRoutes"] = coerced24; +data["useSemicolonDelimiter"] = coerced24; } } } var valid0 = _errs65 === errors; if(valid0){ -let data22 = data.useSemicolonDelimiter; +if(data.routerOptions !== undefined){ +let data22 = data.routerOptions; const _errs67 = errors; -if(typeof data22 !== "boolean"){ +if(errors === _errs67){ +if(data22 && typeof data22 == "object" && !Array.isArray(data22)){ +if(data22.ignoreTrailingSlash === undefined){ +data22.ignoreTrailingSlash = false; +} +if(data22.ignoreDuplicateSlashes === undefined){ +data22.ignoreDuplicateSlashes = false; +} +if(data22.maxParamLength === undefined){ +data22.maxParamLength = 100; +} +if(data22.allowUnsafeRegex === undefined){ +data22.allowUnsafeRegex = false; +} +if(data22.useSemicolonDelimiter === undefined){ +data22.useSemicolonDelimiter = false; +} +let data23 = data22.ignoreTrailingSlash; +const _errs70 = errors; +if(typeof data23 !== "boolean"){ let coerced25 = undefined; if(!(coerced25 !== undefined)){ -if(data22 === "false" || data22 === 0 || data22 === null){ +if(data23 === "false" || data23 === 0 || data23 === null){ coerced25 = false; } -else if(data22 === "true" || data22 === 1){ +else if(data23 === "true" || data23 === 1){ coerced25 = true; } else { -validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced25 !== undefined){ -data22 = coerced25; -if(data !== undefined){ -data["useSemicolonDelimiter"] = coerced25; -} -} -} -var valid0 = _errs67 === errors; -if(valid0){ -if(data.routerOptions !== undefined){ -let data23 = data.routerOptions; -const _errs69 = errors; -if(errors === _errs69){ -if(data23 && typeof data23 == "object" && !Array.isArray(data23)){ -if(data23.ignoreTrailingSlash === undefined){ -data23.ignoreTrailingSlash = false; -} -if(data23.ignoreDuplicateSlashes === undefined){ -data23.ignoreDuplicateSlashes = false; +data23 = coerced25; +if(data22 !== undefined){ +data22["ignoreTrailingSlash"] = coerced25; } -if(data23.maxParamLength === undefined){ -data23.maxParamLength = 100; } -if(data23.allowUnsafeRegex === undefined){ -data23.allowUnsafeRegex = false; } -if(data23.useSemicolonDelimiter === undefined){ -data23.useSemicolonDelimiter = false; -} -let data24 = data23.ignoreTrailingSlash; +var valid7 = _errs70 === errors; +if(valid7){ +let data24 = data22.ignoreDuplicateSlashes; const _errs72 = errors; if(typeof data24 !== "boolean"){ let coerced26 = undefined; @@ -1034,69 +1034,69 @@ else if(data24 === "true" || data24 === 1){ coerced26 = true; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreDuplicateSlashes",schemaPath:"#/properties/routerOptions/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced26 !== undefined){ data24 = coerced26; -if(data23 !== undefined){ -data23["ignoreTrailingSlash"] = coerced26; +if(data22 !== undefined){ +data22["ignoreDuplicateSlashes"] = coerced26; } } } var valid7 = _errs72 === errors; if(valid7){ -let data25 = data23.ignoreDuplicateSlashes; +let data25 = data22.maxParamLength; const _errs74 = errors; -if(typeof data25 !== "boolean"){ +if(!(((typeof data25 == "number") && (!(data25 % 1) && !isNaN(data25))) && (isFinite(data25)))){ +let dataType27 = typeof data25; let coerced27 = undefined; if(!(coerced27 !== undefined)){ -if(data25 === "false" || data25 === 0 || data25 === null){ -coerced27 = false; -} -else if(data25 === "true" || data25 === 1){ -coerced27 = true; +if(dataType27 === "boolean" || data25 === null + || (dataType27 === "string" && data25 && data25 == +data25 && !(data25 % 1))){ +coerced27 = +data25; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreDuplicateSlashes",schemaPath:"#/properties/routerOptions/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/maxParamLength",schemaPath:"#/properties/routerOptions/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced27 !== undefined){ data25 = coerced27; -if(data23 !== undefined){ -data23["ignoreDuplicateSlashes"] = coerced27; +if(data22 !== undefined){ +data22["maxParamLength"] = coerced27; } } } var valid7 = _errs74 === errors; if(valid7){ -let data26 = data23.maxParamLength; +let data26 = data22.allowUnsafeRegex; const _errs76 = errors; -if(!(((typeof data26 == "number") && (!(data26 % 1) && !isNaN(data26))) && (isFinite(data26)))){ -let dataType28 = typeof data26; +if(typeof data26 !== "boolean"){ let coerced28 = undefined; if(!(coerced28 !== undefined)){ -if(dataType28 === "boolean" || data26 === null - || (dataType28 === "string" && data26 && data26 == +data26 && !(data26 % 1))){ -coerced28 = +data26; +if(data26 === "false" || data26 === 0 || data26 === null){ +coerced28 = false; +} +else if(data26 === "true" || data26 === 1){ +coerced28 = true; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/maxParamLength",schemaPath:"#/properties/routerOptions/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/allowUnsafeRegex",schemaPath:"#/properties/routerOptions/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced28 !== undefined){ data26 = coerced28; -if(data23 !== undefined){ -data23["maxParamLength"] = coerced28; +if(data22 !== undefined){ +data22["allowUnsafeRegex"] = coerced28; } } } var valid7 = _errs76 === errors; if(valid7){ -let data27 = data23.allowUnsafeRegex; +let data27 = data22.useSemicolonDelimiter; const _errs78 = errors; if(typeof data27 !== "boolean"){ let coerced29 = undefined; @@ -1108,43 +1108,18 @@ else if(data27 === "true" || data27 === 1){ coerced29 = true; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/allowUnsafeRegex",schemaPath:"#/properties/routerOptions/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/useSemicolonDelimiter",schemaPath:"#/properties/routerOptions/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced29 !== undefined){ data27 = coerced29; -if(data23 !== undefined){ -data23["allowUnsafeRegex"] = coerced29; +if(data22 !== undefined){ +data22["useSemicolonDelimiter"] = coerced29; } } } var valid7 = _errs78 === errors; -if(valid7){ -let data28 = data23.useSemicolonDelimiter; -const _errs80 = errors; -if(typeof data28 !== "boolean"){ -let coerced30 = undefined; -if(!(coerced30 !== undefined)){ -if(data28 === "false" || data28 === 0 || data28 === null){ -coerced30 = false; -} -else if(data28 === "true" || data28 === 1){ -coerced30 = true; -} -else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/useSemicolonDelimiter",schemaPath:"#/properties/routerOptions/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; -return false; -} -} -if(coerced30 !== undefined){ -data28 = coerced30; -if(data23 !== undefined){ -data23["useSemicolonDelimiter"] = coerced30; -} -} -} -var valid7 = _errs80 === errors; } } } @@ -1155,49 +1130,49 @@ validate10.errors = [{instancePath:instancePath+"/routerOptions",schemaPath:"#/p return false; } } -var valid0 = _errs69 === errors; +var valid0 = _errs67 === errors; } else { var valid0 = true; } if(valid0){ if(data.constraints !== undefined){ -let data29 = data.constraints; -const _errs82 = errors; -if(errors === _errs82){ +let data28 = data.constraints; +const _errs80 = errors; +if(errors === _errs80){ +if(data28 && typeof data28 == "object" && !Array.isArray(data28)){ +for(const key2 in data28){ +let data29 = data28[key2]; +const _errs83 = errors; +if(errors === _errs83){ if(data29 && typeof data29 == "object" && !Array.isArray(data29)){ -for(const key2 in data29){ -let data30 = data29[key2]; -const _errs85 = errors; -if(errors === _errs85){ -if(data30 && typeof data30 == "object" && !Array.isArray(data30)){ let missing1; -if(((((data30.name === undefined) && (missing1 = "name")) || ((data30.storage === undefined) && (missing1 = "storage"))) || ((data30.validate === undefined) && (missing1 = "validate"))) || ((data30.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){ +if(((((data29.name === undefined) && (missing1 = "name")) || ((data29.storage === undefined) && (missing1 = "storage"))) || ((data29.validate === undefined) && (missing1 = "validate"))) || ((data29.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; return false; } else { -if(data30.name !== undefined){ -let data31 = data30.name; -if(typeof data31 !== "string"){ -let dataType31 = typeof data31; -let coerced31 = undefined; -if(!(coerced31 !== undefined)){ -if(dataType31 == "number" || dataType31 == "boolean"){ -coerced31 = "" + data31; +if(data29.name !== undefined){ +let data30 = data29.name; +if(typeof data30 !== "string"){ +let dataType30 = typeof data30; +let coerced30 = undefined; +if(!(coerced30 !== undefined)){ +if(dataType30 == "number" || dataType30 == "boolean"){ +coerced30 = "" + data30; } -else if(data31 === null){ -coerced31 = ""; +else if(data30 === null){ +coerced30 = ""; } else { validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } -if(coerced31 !== undefined){ -data31 = coerced31; -if(data30 !== undefined){ -data30["name"] = coerced31; +if(coerced30 !== undefined){ +data30 = coerced30; +if(data29 !== undefined){ +data29["name"] = coerced30; } } } @@ -1209,7 +1184,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/ return false; } } -var valid8 = _errs85 === errors; +var valid8 = _errs83 === errors; if(!valid8){ break; } @@ -1220,7 +1195,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs82 === errors; +var valid0 = _errs80 === errors; } else { var valid0 = true; @@ -1249,7 +1224,6 @@ var valid0 = true; } } } -} else { validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; diff --git a/lib/four-oh-four.js b/lib/four-oh-four.js index ac6dfa6d370..140cd046aba 100644 --- a/lib/four-oh-four.js +++ b/lib/four-oh-four.js @@ -49,7 +49,8 @@ function fourOhFour (options) { function basic404 (request, reply) { const { url, method } = request.raw const message = `Route ${method}:${url} not found` - if (!disableRequestLogging) { + const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(request.raw) : disableRequestLogging + if (!resolvedDisableRequestLogging) { request.log.info(message) } reply.code(404).send({ diff --git a/lib/route.js b/lib/route.js index 051c6b30989..9eb46f0b343 100644 --- a/lib/route.js +++ b/lib/route.js @@ -29,8 +29,6 @@ const { FST_ERR_HOOK_INVALID_ASYNC_HANDLER } = require('./errors') -const { FSTDEP022 } = require('./warnings') - const { kRoutePrefix, kSupportedHTTPMethods, @@ -53,6 +51,7 @@ const { const { buildErrorHandler } = require('./error-handler') const { createChildLogger } = require('./logger-factory.js') const { getGenReqId } = require('./req-id-gen-factory.js') +const { FSTDEP022 } = require('./warnings') const routerKeys = [ 'allowUnsafeRegex', @@ -78,6 +77,7 @@ function buildRouting (options) { let setupResponseListeners let throwIfAlreadyStarted let disableRequestLogging + let disableRequestLoggingFn let ignoreTrailingSlash let ignoreDuplicateSlashes let return503OnClosing @@ -94,13 +94,17 @@ function buildRouting (options) { setup (options, fastifyArgs) { avvio = fastifyArgs.avvio fourOhFour = fastifyArgs.fourOhFour + logger = options.logger hasLogger = fastifyArgs.hasLogger setupResponseListeners = fastifyArgs.setupResponseListeners throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted - logger = options.logger globalExposeHeadRoutes = options.exposeHeadRoutes disableRequestLogging = options.disableRequestLogging + if (typeof disableRequestLogging === 'function') { + disableRequestLoggingFn = options.disableRequestLogging + } + ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash ignoreDuplicateSlashes = options.routerOptions.ignoreDuplicateSlashes return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true @@ -373,9 +377,8 @@ function buildRouting (options) { context.logSerializers = opts.logSerializers context.attachValidation = opts.attachValidation context[kReplySerializerDefault] = this[kReplySerializerDefault] - context.schemaErrorFormatter = opts.schemaErrorFormatter || - this[kSchemaErrorFormatter] || - context.schemaErrorFormatter + context.schemaErrorFormatter = + opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter // Run hooks and more avvio.once('preReady', () => { @@ -402,9 +405,11 @@ function buildRouting (options) { context.schema = normalizeSchema(context.schema, this.initialConfig) const schemaController = this[kSchemaController] - if (!opts.validatorCompiler && ( - opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params - )) { + const hasValidationSchema = opts.schema.body || + opts.schema.headers || + opts.schema.querystring || + opts.schema.params + if (!opts.validatorCompiler && hasValidationSchema) { schemaController.setupValidator(this[kOptions]) } try { @@ -455,7 +460,8 @@ function buildRouting (options) { loggerOpts.serializers = context.logSerializers } const childLogger = createChildLogger(context, logger, req, id, loggerOpts) - childLogger[kDisableRequestLogging] = disableRequestLogging + // Set initial value; will be re-evaluated after FastifyRequest is constructed if it's a function + childLogger[kDisableRequestLogging] = disableRequestLoggingFn ? false : disableRequestLogging if (closing === true) { /* istanbul ignore next mac, windows */ @@ -499,7 +505,15 @@ function buildRouting (options) { const request = new context.Request(id, params, req, query, childLogger, context) const reply = new context.Reply(res, request, childLogger) - if (disableRequestLogging === false) { + + // Evaluate disableRequestLogging after FastifyRequest is constructed + // so the caller has access to decorations and customizations + const resolvedDisableRequestLogging = disableRequestLoggingFn + ? disableRequestLoggingFn(request) + : disableRequestLogging + childLogger[kDisableRequestLogging] = resolvedDisableRequestLogging + + if (resolvedDisableRequestLogging === false) { childLogger.info({ req: request }, 'incoming request') } diff --git a/test/404s.test.js b/test/404s.test.js index a6d3a06f814..f8bc8cefe7f 100644 --- a/test/404s.test.js +++ b/test/404s.test.js @@ -1984,3 +1984,52 @@ test('hooks are applied to not found handlers /3', async t => { const { statusCode } = await fastify.inject('/') t.assert.strictEqual(statusCode, 401) }) + +test('should honor disableRequestLogging function for 404', async t => { + t.plan(3) + + const Writable = require('node:stream').Writable + + const logStream = new Writable() + logStream.logs = [] + logStream._write = function (chunk, encoding, callback) { + this.logs.push(JSON.parse(chunk.toString())) + callback() + } + + const fastify = Fastify({ + logger: { + level: 'info', + stream: logStream + }, + disableRequestLogging: (req) => { + // Disable logging for URLs containing 'silent' + return req.url.includes('silent') + } + }) + + fastify.get('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + + t.after(() => { fastify.close() }) + + // First request to a non-existent route (no 'silent' in URL) - should log + const response1 = await fastify.inject({ + method: 'GET', + url: '/not-found' + }) + t.assert.strictEqual(response1.statusCode, 404) + + // Second request to a non-existent route with 'silent' in URL - should not log + const response2 = await fastify.inject({ + method: 'GET', + url: '/silent-route' + }) + t.assert.strictEqual(response2.statusCode, 404) + + // Check logs: first request should have logged, second should not + // We expect: incoming request, Route not found info, request completed (for first request only) + const infoLogs = logStream.logs.filter(log => log.msg && log.msg.includes('Route GET:/not-found not found')) + t.assert.strictEqual(infoLogs.length, 1, 'Should log 404 info only for non-silent route') +}) diff --git a/test/logger/logging.test.js b/test/logger/logging.test.js index 5d9c5300f40..0ed5187252d 100644 --- a/test/logger/logging.test.js +++ b/test/logger/logging.test.js @@ -16,7 +16,7 @@ t.test('logging', { timeout: 60000 }, async (t) => { let localhost let localhostForURL - t.plan(13) + t.plan(14) t.before(async function () { [localhost, localhostForURL] = await helper.getLoopbackHost() @@ -282,6 +282,43 @@ t.test('logging', { timeout: 60000 }, async (t) => { t.assert.strictEqual(stream.readableLength, 0) }) + await t.test('should log incoming request and outgoing response based on disableRequestLogging function', async (t) => { + const lines = [ + 'incoming request', + 'request completed' + ] + t.plan(lines.length) + + const stream = split(JSON.parse) + const loggerInstance = pino(stream) + + const fastify = Fastify({ + disableRequestLogging: (request) => { + return request.url !== '/not-logged' + }, + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/logged', (req, reply) => { + return reply.code(200).send({}) + }) + + fastify.get('/not-logged', (req, reply) => { + return reply.code(200).send({}) + }) + + await fastify.ready() + + await fastify.inject({ method: 'GET', url: '/not-logged' }) + await fastify.inject({ method: 'GET', url: '/logged' }) + + for await (const [line] of on(stream, 'data')) { + t.assert.strictEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + await t.test('defaults to info level', async (t) => { const lines = [ { req: { method: 'GET' }, msg: 'incoming request' }, diff --git a/test/router-options.test.js b/test/router-options.test.js index 1a4998c6cec..cdb9d3898ce 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -447,6 +447,157 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST ) }) +test('Should honor disableRequestLogging function in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => { + t.plan(4) + + let logCallCount = 0 + const logStream = split(JSON.parse) + + const fastify = Fastify({ + disableRequestLogging: (req) => { + // Disable logging for URLs containing 'silent' + return req.url.includes('silent') + }, + frameworkErrors: function (err, req, res) { + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + level: 'info' + } + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + logStream.on('data', (json) => { + if (json.msg === 'incoming request') { + logCallCount++ + } + }) + + // First request: URL does not contain 'silent', so logging should happen + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + + // Second request: URL contains 'silent', so logging should be disabled + fastify.inject( + { + method: 'GET', + url: '/silent/%world' + }, + (err2, res2) => { + t.assert.ifError(err2) + // Give time for any potential log events + setImmediate(() => { + // Only the first request should have logged + t.assert.strictEqual(logCallCount, 1) + done() + }) + } + ) + } + ) +}) + +test('Should honor disableRequestLogging function in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => { + t.plan(4) + + let logCallCount = 0 + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const logStream = split(JSON.parse) + + const fastify = Fastify({ + constraints: { secret: constraint }, + disableRequestLogging: (req) => { + // Disable logging for URLs containing 'silent' + return req.url.includes('silent') + }, + frameworkErrors: function (err, req, res) { + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + level: 'info' + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/silent', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + logStream.on('data', (json) => { + if (json.msg === 'incoming request') { + logCallCount++ + } + }) + + // First request: URL does not contain 'silent', so logging should happen + fastify.inject( + { + method: 'GET', + url: '/' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + + // Second request: URL contains 'silent', so logging should be disabled + fastify.inject( + { + method: 'GET', + url: '/silent' + }, + (err2, res2) => { + t.assert.ifError(err2) + // Give time for any potential log events + setImmediate(() => { + // Only the first request should have logged + t.assert.strictEqual(logCallCount, 1) + done() + }) + } + ) + } + ) +}) + test('Should honor routerOptions.defaultRoute', async t => { t.plan(3) const fastify = Fastify({ diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 49de8b82269..ffb2f80c0ee 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -115,6 +115,7 @@ expectAssignable(fastify({ pluginTimeout: 1000 })) expectAssignable(fastify({ bodyLimit: 100 })) expectAssignable(fastify({ maxParamLength: 100 })) expectAssignable(fastify({ disableRequestLogging: true })) +expectAssignable(fastify({ disableRequestLogging: (req) => req.url?.includes('/health') ?? false })) expectAssignable(fastify({ requestIdLogLabel: 'request-id' })) expectAssignable(fastify({ onProtoPoisoning: 'error' })) expectAssignable(fastify({ onConstructorPoisoning: 'error' })) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 7e6bf658409..ce77e8b2bba 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -324,7 +324,7 @@ type InitialConfig = Readonly<{ https?: boolean | Readonly<{ allowHTTP1: boolean }>, ignoreTrailingSlash?: boolean, ignoreDuplicateSlashes?: boolean, - disableRequestLogging?: boolean, + disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean), maxParamLength?: number, onProtoPoisoning?: 'error' | 'remove' | 'ignore', onConstructorPoisoning?: 'error' | 'remove' | 'ignore', diff --git a/types/instance.d.ts b/types/instance.d.ts index f940ad10509..0fd1baafe13 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -595,7 +595,7 @@ export interface FastifyInstance< https?: boolean | Readonly<{ allowHTTP1: boolean }>, ignoreTrailingSlash?: boolean, ignoreDuplicateSlashes?: boolean, - disableRequestLogging?: boolean, + disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean), maxParamLength?: number, onProtoPoisoning?: ProtoAction, onConstructorPoisoning?: ConstructorAction, From 1350428d1538b62e1b3d830c529ba6b02d5d4f78 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Wed, 14 Jan 2026 16:07:17 +0100 Subject: [PATCH 1222/1295] docs(sponsor): add serpapi (#6443) Signed-off-by: Manuel Spigolon --- SPONSORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPONSORS.md b/SPONSORS.md index a215ead99e4..f9615f62504 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -9,7 +9,7 @@ or [GitHub Sponsors](https://github.com/sponsors/fastify)! ## Tier 4 -_Be the first!_ +- [SerpApi](http://serpapi.com/) ## Tier 3 From c89945df8b6ec3507ba0fc444d561bac010a949a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 15 Jan 2026 12:45:49 +0100 Subject: [PATCH 1223/1295] perf: use native WebStream API instead of Readable.fromWeb wrapper (#6444) --- examples/benchmark/webstream.js | 27 +++++++ lib/reply.js | 56 ++++++++++++- test/web-api.test.js | 136 ++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 examples/benchmark/webstream.js diff --git a/examples/benchmark/webstream.js b/examples/benchmark/webstream.js new file mode 100644 index 00000000000..7ed5fcd166b --- /dev/null +++ b/examples/benchmark/webstream.js @@ -0,0 +1,27 @@ +'use strict' + +const fastify = require('../../fastify')({ + logger: false +}) + +const payload = JSON.stringify({ hello: 'world' }) + +fastify.get('/', function (req, reply) { + const stream = new ReadableStream({ + start (controller) { + controller.enqueue(payload) + controller.close() + } + }) + return new Response(stream, { + status: 200, + headers: { + 'content-type': 'application/json; charset=utf-8' + } + }) +}) + +fastify.listen({ port: 3000 }, (err, address) => { + if (err) throw err + console.log(`Server listening on ${address}`) +}) diff --git a/lib/reply.js b/lib/reply.js index 5d4ddb636d0..c126616d5e4 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -1,7 +1,6 @@ 'use strict' const eos = require('node:stream').finished -const Readable = require('node:stream').Readable const { kFourOhFourContext, @@ -685,8 +684,59 @@ function sendWebStream (payload, res, reply) { if (payload.locked) { throw new FST_ERR_REP_READABLE_STREAM_LOCKED() } - const nodeStream = Readable.fromWeb(payload) - sendStream(nodeStream, res, reply) + + let sourceOpen = true + let errorLogged = false + const reader = payload.getReader() + + eos(res, function (err) { + if (sourceOpen) { + if (err != null && res.headersSent && !errorLogged) { + errorLogged = true + logStreamError(reply.log, err, res) + } + reader.cancel().catch(noop) + } + }) + + if (!res.headersSent) { + for (const key in reply[kReplyHeaders]) { + res.setHeader(key, reply[kReplyHeaders][key]) + } + } else { + reply.log.warn('response will send, but you shouldn\'t use res.writeHead in stream mode') + } + + function onRead (result) { + if (result.done) { + sourceOpen = false + sendTrailer(null, res, reply) + return + } + /* c8 ignore next 5 - race condition: eos handler typically fires first */ + if (res.destroyed) { + sourceOpen = false + reader.cancel().catch(noop) + return + } + res.write(result.value) + reader.read().then(onRead, onReadError) + } + + function onReadError (err) { + sourceOpen = false + if (res.headersSent || reply.request.raw.aborted === true) { + if (!errorLogged) { + errorLogged = true + logStreamError(reply.log, err, reply) + } + res.destroy() + } else { + onErrorHook(reply, err) + } + } + + reader.read().then(onRead, onReadError) } function sendStream (payload, res, reply) { diff --git a/test/web-api.test.js b/test/web-api.test.js index fb8476d7810..7a87105cb8a 100644 --- a/test/web-api.test.js +++ b/test/web-api.test.js @@ -5,6 +5,7 @@ const Fastify = require('../fastify') const fs = require('node:fs') const { Readable } = require('node:stream') const { fetch: undiciFetch } = require('undici') +const http = require('node:http') test('should response with a ReadableStream', async (t) => { t.plan(2) @@ -330,3 +331,138 @@ test('allow to pipe with undici.fetch', async (t) => { t.assert.strictEqual(response.statusCode, 200) t.assert.deepStrictEqual(response.json(), { ok: true }) }) + +test('WebStream error before headers sent should trigger error handler', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = new ReadableStream({ + start (controller) { + controller.error(new Error('stream error')) + } + }) + reply.send(stream) + }) + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 500) + t.assert.strictEqual(response.json().message, 'stream error') +}) + +test('WebStream error after headers sent should destroy response', (t, done) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function (request, reply) { + const stream = new ReadableStream({ + start (controller) { + controller.enqueue('hello') + }, + pull (controller) { + setTimeout(() => { + controller.error(new Error('stream error')) + }, 10) + } + }) + reply.header('content-type', 'text/plain').send(stream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + let finished = false + http.get(`http://localhost:${fastify.server.address().port}`, (res) => { + res.on('close', () => { + if (!finished) { + finished = true + t.assert.ok('response closed') + done() + } + }) + res.resume() + }) + }) +}) + +test('WebStream should cancel reader when response is destroyed', (t, done) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + let readerCancelled = false + + fastify.get('/', function (request, reply) { + const stream = new ReadableStream({ + start (controller) { + controller.enqueue('hello') + }, + pull (controller) { + return new Promise(() => {}) + }, + cancel () { + readerCancelled = true + } + }) + reply.header('content-type', 'text/plain').send(stream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const req = http.get(`http://localhost:${fastify.server.address().port}`, (res) => { + res.once('data', () => { + req.destroy() + setTimeout(() => { + t.assert.strictEqual(readerCancelled, true) + done() + }, 50) + }) + }) + }) +}) + +test('WebStream should warn when headers already sent', async (t) => { + t.plan(2) + + let warnCalled = false + const spyLogger = { + level: 'warn', + fatal: () => { }, + error: () => { }, + warn: (msg) => { + if (typeof msg === 'string' && msg.includes('use res.writeHead in stream mode')) { + warnCalled = true + } + }, + info: () => { }, + debug: () => { }, + trace: () => { }, + child: () => spyLogger + } + + const fastify = Fastify({ loggerInstance: spyLogger }) + t.after(() => fastify.close()) + + fastify.get('/', function (request, reply) { + reply.raw.writeHead(200, { 'content-type': 'text/plain' }) + const stream = new ReadableStream({ + start (controller) { + controller.enqueue('hello') + controller.close() + } + }) + reply.send(stream) + }) + + await fastify.listen({ port: 0 }) + + const response = await fetch(`http://localhost:${fastify.server.address().port}/`) + t.assert.strictEqual(response.status, 200) + t.assert.strictEqual(warnCalled, true) +}) From d863116fa709c66470112679f566273d0223db71 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 15 Jan 2026 14:02:21 +0100 Subject: [PATCH 1224/1295] Bumped v5.7.0 Signed-off-by: Matteo Collina --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f62fa0461fd..22e7c1f3c3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.6.2", + "version": "5.7.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 57aad569180eb67458562691ab51bbe3d7bd62c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:36:20 +0800 Subject: [PATCH 1225/1295] chore: Bump actions/checkout from 5 to 6 (#6434) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- .github/workflows/ci-alternative-runtime.yml | 6 +++--- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/citgm-package.yml | 4 ++-- .github/workflows/coverage-nix.yml | 2 +- .github/workflows/coverage-win.yml | 2 +- .../workflows/integration-alternative-runtimes.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/links-check.yml | 2 +- .github/workflows/lint-ecosystem-order.yml | 2 +- .github/workflows/md-lint.yml | 2 +- .github/workflows/package-manager-ci.yml | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml index 05e94fc9236..9100941f37a 100644 --- a/.github/workflows/ci-alternative-runtime.yml +++ b/.github/workflows/ci-alternative-runtime.yml @@ -37,7 +37,7 @@ jobs: node-version: 20 nsolid-version: 5 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -61,7 +61,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -88,7 +88,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a3bc2b88cf..fcd0befbb23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: contents: read steps: - name: Check out repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false @@ -50,7 +50,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -76,7 +76,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -125,7 +125,7 @@ jobs: os: [macos-latest, ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -153,7 +153,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -183,7 +183,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -212,7 +212,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Use Node.js diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index 2e36b77537a..5f81d7d64e9 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -57,7 +57,7 @@ jobs: contents: read steps: - name: Check out Fastify - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false @@ -87,7 +87,7 @@ jobs: const result = repositoryUrl.match( /.*\/([a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+)\.git/)[1] return result - name: Check out ${{inputs.package}} - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: ${{ steps.repository-url.outputs.result }} path: package diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml index 54e489c58d6..2814e1dc80a 100644 --- a/.github/workflows/coverage-nix.yml +++ b/.github/workflows/coverage-nix.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml index cdbb92ed663..85bd44370d9 100644 --- a/.github/workflows/coverage-win.yml +++ b/.github/workflows/coverage-win.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml index 3c6e1c222c0..bfba5975a89 100644 --- a/.github/workflows/integration-alternative-runtimes.yml +++ b/.github/workflows/integration-alternative-runtimes.yml @@ -34,7 +34,7 @@ jobs: node-version: 20 runtime: nsolid steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index b58d1bdb23b..c77b04bdf6e 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -30,7 +30,7 @@ jobs: pnpm-version: [8] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 8db783dc60c..9b976c2b09b 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -16,7 +16,7 @@ jobs: contents: read steps: - name: Check out repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml index 0932cd0d6e9..866f0e09f6a 100644 --- a/.github/workflows/lint-ecosystem-order.yml +++ b/.github/workflows/lint-ecosystem-order.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml index fbadd1d4833..f10d39589e0 100644 --- a/.github/workflows/md-lint.yml +++ b/.github/workflows/md-lint.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml index d3c2fbe6d9c..d0fa9d61932 100644 --- a/.github/workflows/package-manager-ci.yml +++ b/.github/workflows/package-manager-ci.yml @@ -22,7 +22,7 @@ jobs: pnpm-version: [8] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false From 153d78aaf95249e275e234185d3e2d1c770859c4 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Fri, 16 Jan 2026 09:18:33 +0100 Subject: [PATCH 1226/1295] fix: updated version in the fastify.js (#6446) --- fastify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastify.js b/fastify.js index 4d866c01126..64aff2f6cb9 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.6.2' +const VERSION = '5.7.0' const Avvio = require('avvio') const http = require('node:http') From 8eadc22b984e3f2496aa8c4552777b871517135b Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 16 Jan 2026 09:27:11 +0100 Subject: [PATCH 1227/1295] Bumped v5.7.1 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 64aff2f6cb9..b194e9ee37f 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.7.0' +const VERSION = '5.7.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 22e7c1f3c3f..fa6a9ebd9ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.7.0", + "version": "5.7.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 89c69476b9631c3052b5c30f3aa41920b5e6f49a Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:41:51 +0800 Subject: [PATCH 1228/1295] chore: npm ignore AI related files (#6447) * chore: npm ignore AI related files * chore: update .npmignore Co-authored-by: Matteo Collina Signed-off-by: KaKa <23028015+climba03003@users.noreply.github.com> * chore: ide setting Signed-off-by: Manuel Spigolon * chore: ignore log file Signed-off-by: Manuel Spigolon --------- Signed-off-by: KaKa <23028015+climba03003@users.noreply.github.com> Signed-off-by: Manuel Spigolon Co-authored-by: Matteo Collina Co-authored-by: Manuel Spigolon --- .npmignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.npmignore b/.npmignore index 4226655b38c..c2ed9cfe1f3 100644 --- a/.npmignore +++ b/.npmignore @@ -12,6 +12,12 @@ CONTRIBUTING.md EXPENSE_POLICY.md .clinic .gitpod.yml +.vscode/ +*.log + +# AI files +.claude/ +CLAUDE.md # test certification test/https/fastify.cert From 8b2d68d0743247a6817f99ea6b2db95197c8322d Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 16 Jan 2026 11:15:09 +0100 Subject: [PATCH 1229/1295] chore: update sponsor url (#6450) Signed-off-by: Manuel Spigolon --- SPONSORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPONSORS.md b/SPONSORS.md index f9615f62504..45912a8d0e1 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -9,7 +9,7 @@ or [GitHub Sponsors](https://github.com/sponsors/fastify)! ## Tier 4 -- [SerpApi](http://serpapi.com/) +- [SerpApi](https://serpapi.com/?utm_source=fastify) ## Tier 3 From 8319dfe1f2a5067a7fb610ab329feaef2be70719 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Fri, 16 Jan 2026 16:47:43 -0500 Subject: [PATCH 1230/1295] docs: add fastify-http-exceptions to Ecosystem.md (#6442) * add fastify-http-exceptions to Ecosystem.md * fix line length * proper alphabetical order --------- Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- docs/Guides/Ecosystem.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 2ca8346549b..0fedae5fc7a 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -427,6 +427,8 @@ section. context to take place per API call within the Fastify lifecycle of calls. - [`fastify-http-errors-enhanced`](https://github.com/ShogunPanda/fastify-http-errors-enhanced) An error handling plugin for Fastify that uses enhanced HTTP errors. +- [`fastify-http-exceptions`](https://github.com/bhouston/fastify-http-exceptions) + Typed HTTP status exceptions which are automatically converted into Fastify responses. - [`fastify-http2https`](https://github.com/lolo32/fastify-http2https) Redirect HTTP requests to HTTPS, both using the same port number, or different response on HTTP and HTTPS. @@ -746,7 +748,6 @@ middlewares into Fastify plugins - [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple and updated Typeorm plugin for use with Fastify. - #### [Community Tools](#community-tools) - [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows) From 358a4e95d882098c0fef2e4cb514e5aabe854c34 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Sat, 17 Jan 2026 21:30:51 +0800 Subject: [PATCH 1231/1295] docs: fix invalid shorten form schema example (#6448) --- .../Reference/Validation-and-Serialization.md | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 2eb3945d47b..2d1134da908 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -675,9 +675,12 @@ const schema = { content: { 'application/json': { schema: { - name: { type: 'string' }, - image: { type: 'string' }, - address: { type: 'string' } + type: 'object', + properties: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } } }, 'application/vnd.v1+json': { @@ -692,8 +695,11 @@ const schema = { content: { 'application/vnd.v2+json': { schema: { - fullName: { type: 'string' }, - phone: { type: 'string' } + type: 'object', + properties: { + fullName: { type: 'string' }, + phone: { type: 'string' } + } } } } @@ -703,7 +709,10 @@ const schema = { // */* is match-all content-type '*/*': { schema: { - desc: { type: 'string' } + type: 'object', + properties: { + desc: { type: 'string' } + } } } } From a676d7fcde507031aeeca4512da60aab117eb4bd Mon Sep 17 00:00:00 2001 From: "Stanislav (Stanley) Modrak" <44023416+smith558@users.noreply.github.com> Date: Sat, 17 Jan 2026 14:00:58 +0000 Subject: [PATCH 1232/1295] Simplify and tighten decorators example (#6451) Signed-off-by: Stanislav (Stanley) Modrak <44023416+smith558@users.noreply.github.com> --- docs/Guides/Plugins-Guide.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index 2732756c29e..f4fa5870985 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -204,12 +204,12 @@ of an *arrow function expression*. You can do the same for the `request` object: ```js -fastify.decorate('getHeader', (req, header) => { - return req.headers[header] +fastify.decorate('getBoolHeader', (req, name) => { + return req.headers[name] ?? false // We return `false` if header is missing }) fastify.addHook('preHandler', (request, reply, done) => { - request.isHappy = fastify.getHeader(request.raw, 'happy') + request.isHappy = fastify.getBoolHeader(request, 'happy') done() }) @@ -219,14 +219,14 @@ fastify.get('/happiness', (request, reply) => { ``` Again, it works, but it can be much better! ```js -fastify.decorateRequest('setHeader', function (header) { - this.isHappy = this.headers[header] +fastify.decorateRequest('setBoolHeader', function (name) { + this.isHappy = this.headers[name] ?? false }) fastify.decorateRequest('isHappy', false) // This will be added to the Request object prototype, yay speed! fastify.addHook('preHandler', (request, reply, done) => { - request.setHeader('happy') + request.setBoolHeader('happy') done() }) From d79fa759e2ba5837681bcf0e83220cbc1b7da711 Mon Sep 17 00:00:00 2001 From: "Stanislav (Stanley) Modrak" <44023416+smith558@users.noreply.github.com> Date: Sun, 18 Jan 2026 16:03:42 +0000 Subject: [PATCH 1233/1295] docs(fix): incorrect variable use (#6455) Signed-off-by: Stanislav (Stanley) Modrak <44023416+smith558@users.noreply.github.com> --- docs/Guides/Plugins-Guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index f4fa5870985..0d72a9740cb 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -337,11 +337,11 @@ fastify.register((instance, opts, done) => { } }) - fastify.get('/plugin1', {config: {useUtil: true}}, (request, reply) => { + instance.get('/plugin1', {config: {useUtil: true}}, (request, reply) => { reply.send(request) }) - fastify.get('/plugin2', (request, reply) => { + instance.get('/plugin2', (request, reply) => { reply.send(request) }) From 5c14e05670c80455b99286ee0b6eac05eabec831 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 20 Jan 2026 23:10:08 +0100 Subject: [PATCH 1234/1295] chore: update sponsor link (#6460) --- SPONSORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPONSORS.md b/SPONSORS.md index 45912a8d0e1..36346e172d3 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -17,7 +17,7 @@ or [GitHub Sponsors](https://github.com/sponsors/fastify)! - [Val Town, Inc.](https://opencollective.com/valtown) - [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) - [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship) -- [Lambdatest](https://www.lambdatest.com/) +- [TestMu AI](https://www.testmu.ai/) ## Tier 2 From 2af83d64b53a0a786141d93edb9ffefac8b6446a Mon Sep 17 00:00:00 2001 From: "Stanislav (Stanley) Modrak" <44023416+smith558@users.noreply.github.com> Date: Sat, 24 Jan 2026 13:11:54 +0000 Subject: [PATCH 1235/1295] fix: Fix MIT Licence file to conform to standard (#6464) --- LICENSE | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index e1a6f37b42c..0ebc970ba29 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,6 @@ MIT License -Copyright (c) 2016-present The Fastify Team - -The Fastify team members are listed at https://github.com/fastify/fastify#team -and in the README file. +Copyright (c) 2016-present The Fastify Team (members are listed in the README file) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From f4a6ac1d0321e9f1757c165a2d423ccbefe7c9c9 Mon Sep 17 00:00:00 2001 From: Sahachai Date: Mon, 26 Jan 2026 05:15:02 +0700 Subject: [PATCH 1236/1295] docs: move querystringParser example under routerOptions (#6463) --- docs/Reference/Server.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 7f217f72c2e..dfb00758b89 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -534,7 +534,9 @@ recommend using a custom parser to convert only the keys to lowercase. ```js const qs = require('qs') const fastify = require('fastify')({ - querystringParser: str => qs.parse(str) + routerOptions: { + querystringParser: str => qs.parse(str) + } }) ``` @@ -544,7 +546,9 @@ like the example below for case insensitive keys and values: ```js const querystring = require('fast-querystring') const fastify = require('fastify')({ - querystringParser: str => querystring.parse(str.toLowerCase()) + routerOptions: { + querystringParser: str => querystring.parse(str.toLowerCase()) + } }) ``` From 32d7b6add39ddf082d92579a58bea7018c5ac821 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Mon, 26 Jan 2026 06:08:02 -0500 Subject: [PATCH 1237/1295] chore: Updated content-type header parsing (#6414) * chore: Updated content-type header parsing * address feedback * refactor algorithm * Update lib/content-type-parser.js Co-authored-by: Manuel Spigolon Signed-off-by: James Sumners <321201+jsumners@users.noreply.github.com> * appease coverage --------- Signed-off-by: James Sumners <321201+jsumners@users.noreply.github.com> Co-authored-by: Manuel Spigolon --- lib/content-type-parser.js | 70 ++++++++------- lib/content-type.js | 152 +++++++++++++++++++++++++++++++++ lib/handle-request.js | 16 +++- package.json | 2 +- test/content-parser.test.js | 60 +++++-------- test/content-type.test.js | 103 +++++++++++++++++++++- test/custom-parser.0.test.js | 6 +- test/custom-parser.1.test.js | 35 -------- test/custom-parser.3.test.js | 2 +- test/schema-validation.test.js | 99 ++++++++++++++------- 10 files changed, 398 insertions(+), 147 deletions(-) create mode 100644 lib/content-type.js diff --git a/lib/content-type-parser.js b/lib/content-type-parser.js index 483e93703d4..f83f010a8b2 100644 --- a/lib/content-type-parser.js +++ b/lib/content-type-parser.js @@ -3,6 +3,7 @@ const { AsyncResource } = require('node:async_hooks') const { FifoMap: Fifo } = require('toad-cache') const { parse: secureJsonParse } = require('secure-json-parse') +const ContentType = require('./content-type') const { kDefaultJsonParse, kContentTypeParser, @@ -75,8 +76,13 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { this.customParsers.set('', parser) } else { if (contentTypeIsString) { - this.parserList.unshift(contentType) - this.customParsers.set(contentType, parser) + const ct = new ContentType(contentType) + if (ct.isValid === false) { + throw new FST_ERR_CTP_INVALID_TYPE() + } + const normalizedContentType = ct.toString() + this.parserList.unshift(normalizedContentType) + this.customParsers.set(normalizedContentType, parser) } else { validateRegExp(contentType) this.parserRegExpList.unshift(contentType) @@ -87,7 +93,7 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) { ContentTypeParser.prototype.hasParser = function (contentType) { if (typeof contentType === 'string') { - contentType = contentType.trim().toLowerCase() + contentType = new ContentType(contentType).toString() } else { if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() contentType = contentType.toString() @@ -97,45 +103,49 @@ ContentTypeParser.prototype.hasParser = function (contentType) { } ContentTypeParser.prototype.existingParser = function (contentType) { - if (contentType === 'application/json' && this.customParsers.has(contentType)) { - return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse] - } - if (contentType === 'text/plain' && this.customParsers.has(contentType)) { - return this.customParsers.get(contentType).fn !== defaultPlainTextParser + if (typeof contentType === 'string') { + const ct = new ContentType(contentType).toString() + if (contentType === 'application/json' && this.customParsers.has(contentType)) { + return this.customParsers.get(ct).fn !== this[kDefaultJsonParse] + } + if (contentType === 'text/plain' && this.customParsers.has(contentType)) { + return this.customParsers.get(ct).fn !== defaultPlainTextParser + } } return this.hasParser(contentType) } ContentTypeParser.prototype.getParser = function (contentType) { - let parser = this.customParsers.get(contentType) - if (parser !== undefined) return parser - parser = this.cache.get(contentType) + if (typeof contentType === 'string') { + contentType = new ContentType(contentType) + } + const ct = contentType.toString() + + let parser = this.cache.get(ct) if (parser !== undefined) return parser + parser = this.customParsers.get(ct) + if (parser !== undefined) { + this.cache.set(ct, parser) + return parser + } - const caseInsensitiveContentType = contentType.toLowerCase() - for (let i = 0; i !== this.parserList.length; ++i) { - const parserListItem = this.parserList[i] - if ( - caseInsensitiveContentType.slice(0, parserListItem.length) === parserListItem && - ( - caseInsensitiveContentType.length === parserListItem.length || - caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ || - caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */ || - caseInsensitiveContentType.charCodeAt(parserListItem.length) === 9 /* `\t` */ - ) - ) { - parser = this.customParsers.get(parserListItem) - this.cache.set(contentType, parser) - return parser - } + // We have conflicting desires across our test suite. In some cases, we + // expect to get a parser by just passing the media-type. In others, we expect + // to get a parser registered under the media-type while also providing + // parameters. And in yet others, we expect to register a parser under the + // media-type and have it apply to any request with a header that starts + // with that type. + parser = this.customParsers.get(contentType.mediaType) + if (parser !== undefined) { + return parser } for (let j = 0; j !== this.parserRegExpList.length; ++j) { const parserRegExp = this.parserRegExpList[j] - if (parserRegExp.test(contentType)) { + if (parserRegExp.test(ct)) { parser = this.customParsers.get(parserRegExp.toString()) - this.cache.set(contentType, parser) + this.cache.set(ct, parser) return parser } } @@ -154,7 +164,7 @@ ContentTypeParser.prototype.remove = function (contentType) { let parsers if (typeof contentType === 'string') { - contentType = contentType.trim().toLowerCase() + contentType = new ContentType(contentType).toString() parsers = this.parserList } else { if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE() diff --git a/lib/content-type.js b/lib/content-type.js new file mode 100644 index 00000000000..56f722fa333 --- /dev/null +++ b/lib/content-type.js @@ -0,0 +1,152 @@ +'use strict' + +/** + * keyValuePairsReg is used to split the parameters list into associated + * key value pairings. + * + * @see https://httpwg.org/specs/rfc9110.html#parameter + * @type {RegExp} + */ +const keyValuePairsReg = /([\w!#$%&'*+.^`|~-]+)=([^;]*)/gm + +/** + * typeNameReg is used to validate that the first part of the media-type + * does not use disallowed characters. + * + * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators + * @type {RegExp} + */ +const typeNameReg = /^[\w!#$%&'*+.^`|~-]+$/ + +/** + * subtypeNameReg is used to validate that the second part of the media-type + * does not use disallowed characters. + * + * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators + * @type {RegExp} + */ +const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*/ + +/** + * ContentType parses and represents the value of the content-type header. + * + * @see https://httpwg.org/specs/rfc9110.html#media.type + * @see https://httpwg.org/specs/rfc9110.html#parameter + */ +class ContentType { + #valid = false + #empty = true + #type = '' + #subtype = '' + #parameters = new Map() + #string + + constructor (headerValue) { + if (headerValue == null || headerValue === '' || headerValue === 'undefined') { + return + } + + let sepIdx = headerValue.indexOf(';') + if (sepIdx === -1) { + // The value is the simplest `type/subtype` variant. + sepIdx = headerValue.indexOf('/') + if (sepIdx === -1) { + // Got a string without the correct `type/subtype` format. + return + } + + const type = headerValue.slice(0, sepIdx).trimStart().toLowerCase() + const subtype = headerValue.slice(sepIdx + 1).trimEnd().toLowerCase() + + if ( + typeNameReg.test(type) === true && + subtypeNameReg.test(subtype) === true + ) { + this.#valid = true + this.#empty = false + this.#type = type + this.#subtype = subtype + } + + return + } + + // We have a `type/subtype; params=list...` header value. + const mediaType = headerValue.slice(0, sepIdx).toLowerCase() + const paramsList = headerValue.slice(sepIdx + 1).trim() + + sepIdx = mediaType.indexOf('/') + if (sepIdx === -1) { + // We got an invalid string like `something; params=list...`. + return + } + const type = mediaType.slice(0, sepIdx).trimStart() + const subtype = mediaType.slice(sepIdx + 1).trimEnd() + + if ( + typeNameReg.test(type) === false || + subtypeNameReg.test(subtype) === false + ) { + // Some portion of the media-type is using invalid characters. Therefore, + // the content-type header is invalid. + return + } + this.#type = type + this.#subtype = subtype + this.#valid = true + this.#empty = false + + let matches = keyValuePairsReg.exec(paramsList) + while (matches) { + const key = matches[1] + const value = matches[2] + if (value[0] === '"') { + if (value.at(-1) !== '"') { + this.#parameters.set(key, 'invalid quoted string') + matches = keyValuePairsReg.exec(paramsList) + continue + } + // We should probably verify the value matches a quoted string + // (https://httpwg.org/specs/rfc9110.html#rule.quoted-string) value. + // But we are not really doing much with the parameter values, so we + // are omitting that at this time. + this.#parameters.set(key, value.slice(1, value.length - 1)) + } else { + this.#parameters.set(key, value) + } + matches = keyValuePairsReg.exec(paramsList) + } + } + + get [Symbol.toStringTag] () { return 'ContentType' } + + get isEmpty () { return this.#empty } + + get isValid () { return this.#valid } + + get mediaType () { return `${this.#type}/${this.#subtype}` } + + get type () { return this.#type } + + get subtype () { return this.#subtype } + + get parameters () { return this.#parameters } + + toString () { + /* c8 ignore next: we don't need to verify the cache */ + if (this.#string) return this.#string + const parameters = [] + for (const [key, value] of this.#parameters.entries()) { + parameters.push(`${key}="${value}"`) + } + const result = [this.#type, '/', this.#subtype] + if (parameters.length > 0) { + result.push('; ') + result.push(parameters.join('; ')) + } + this.#string = result.join('') + return this.#string + } +} + +module.exports = ContentType diff --git a/lib/handle-request.js b/lib/handle-request.js index 12831e6a7d8..8d81d633603 100644 --- a/lib/handle-request.js +++ b/lib/handle-request.js @@ -1,9 +1,11 @@ 'use strict' const diagnostics = require('node:diagnostics_channel') +const ContentType = require('./content-type') +const wrapThenable = require('./wrap-thenable') const { validate: validateSchema } = require('./validation') const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks') -const wrapThenable = require('./wrap-thenable') +const { FST_ERR_CTP_INVALID_MEDIA_TYPE } = require('./errors') const { setErrorStatusCode } = require('./error-status') const { kReplyIsError, @@ -31,9 +33,9 @@ function handleRequest (err, request, reply) { if (this[kSupportedHTTPMethods].bodywith.has(method)) { const headers = request.headers - const contentType = headers['content-type'] + const ctHeader = headers['content-type'] - if (contentType === undefined) { + if (ctHeader === undefined) { const contentLength = headers['content-length'] const transferEncoding = headers['transfer-encoding'] const isEmptyBody = transferEncoding === undefined && @@ -49,7 +51,13 @@ function handleRequest (err, request, reply) { return } - request[kRouteContext].contentTypeParser.run(contentType, handler, request, reply) + const contentType = new ContentType(ctHeader) + if (contentType.isValid === false) { + reply[kReplyIsError] = true + reply.status(415).send(new FST_ERR_CTP_INVALID_MEDIA_TYPE()) + return + } + request[kRouteContext].contentTypeParser.run(contentType.toString(), handler, request, reply) return } diff --git a/package.json b/package.json index fa6a9ebd9ff..0d6b3aa6c4a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "benchmark:parser:error": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -H \"content-length:123\" -m POST localhost:3000/\"", "build:validation": "node build/build-error-serializer.js && node build/build-validation.js", "build:sync-version": "node build/sync-version.js", - "coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100 ", + "coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter", "coverage:ci-check-coverage": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100", "lint": "npm run lint:eslint", "lint:fix": "eslint --fix", diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 0f2319b6111..09ee39da4ac 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -55,6 +55,7 @@ test('getParser', async t => { fastify.addContentTypeParser(/^image\/.*/, first) fastify.addContentTypeParser(/^application\/.+\+xml/, second) fastify.addContentTypeParser('text/html', third) + fastify.addContentTypeParser('text/html; charset=utf-8', third) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('application/t+xml').fn, second) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/png').fn, first) @@ -66,14 +67,15 @@ test('getParser', async t => { }) await t.test('should return matching parser with caching /1', t => { - t.plan(6) + t.plan(7) const fastify = Fastify() fastify.addContentTypeParser('text/html', first) - t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 0) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first) t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first) @@ -81,20 +83,21 @@ test('getParser', async t => { }) await t.test('should return matching parser with caching /2', t => { - t.plan(8) + t.plan(9) const fastify = Fastify() fastify.addContentTypeParser('text/html', first) - t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 0) + t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/HTML').fn, first) t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first) - t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first) - t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2) + t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1) }) await t.test('should return matching parser with caching /3', t => { @@ -125,7 +128,7 @@ test('getParser', async t => { }) await t.test('should return parser that catches all if no other is set', t => { - t.plan(3) + t.plan(2) const fastify = Fastify() @@ -134,7 +137,6 @@ test('getParser', async t => { t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/gif').fn, first) t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, second) - t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text').fn, first) }) await t.test('should return undefined if no matching parser exist', t => { @@ -208,7 +210,7 @@ test('add', async t => { const fastify = Fastify() const contentTypeParser = fastify[keys.kContentTypeParser] - t.assert.ifError(contentTypeParser.add('test', {}, first)) + t.assert.ifError(contentTypeParser.add('test/type', {}, first)) t.assert.ifError(contentTypeParser.add(/test/, {}, first)) t.assert.throws( () => contentTypeParser.add({}, {}, first), @@ -557,7 +559,7 @@ test('content-type match parameters - regexp', async t => { const fastify = Fastify() fastify.removeAllContentTypeParsers() - fastify.addContentTypeParser(/application\/json; charset=utf8/, function (request, body, done) { + fastify.addContentTypeParser(/application\/json; charset="utf8"/, function (request, body, done) { t.assert.ok('should be called') done(null, body) }) @@ -697,37 +699,17 @@ test('content-type regexp list should be cloned when plugin override', async t = } }) -test('edge case content-type - ;', async t => { +test('content-type fail when not a valid type', async t => { t.plan(1) const fastify = Fastify() fastify.removeAllContentTypeParsers() - fastify.addContentTypeParser(';', function (request, body, done) { - t.assert.fail('should not be called') - done(null, body) - }) - - fastify.post('/', async () => { - return 'ok' - }) - - await fastify.inject({ - method: 'POST', - path: '/', - headers: { - 'content-type': 'application/json; foo=bar; charset=utf8' - }, - body: '' - }) - - await fastify.inject({ - method: 'POST', - path: '/', - headers: { - 'content-type': 'image/jpeg' - }, - body: '' - }) - - t.assert.ok('end') + try { + fastify.addContentTypeParser('type-only', function (request, body, done) { + t.assert.fail('shouldn\'t be called') + done(null, body) + }) + } catch (error) { + t.assert.equal(error.message, 'The content type should be a string or a RegExp') + } }) diff --git a/test/content-type.test.js b/test/content-type.test.js index ab9c45835e9..a2997e4eeda 100644 --- a/test/content-type.test.js +++ b/test/content-type.test.js @@ -1,6 +1,7 @@ 'use strict' -const { test } = require('node:test') +const { describe, test } = require('node:test') +const ContentType = require('../lib/content-type') const Fastify = require('..') test('should remove content-type for setErrorHandler', async t => { @@ -40,3 +41,103 @@ test('should remove content-type for setErrorHandler', async t => { t.assert.strictEqual(statusCode, 400) t.assert.strictEqual(body, JSON.stringify({ foo: 'bar' })) }) + +describe('ContentType class', () => { + test('returns empty instance for empty value', (t) => { + let found = new ContentType('') + t.assert.equal(found.isEmpty, true) + + found = new ContentType('undefined') + t.assert.equal(found.isEmpty, true) + + found = new ContentType() + t.assert.equal(found.isEmpty, true) + }) + + test('indicates media type is not correct format', (t) => { + let found = new ContentType('foo') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('foo /bar') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('foo/ bar') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('foo; param=1') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('foo/π; param=1') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + }) + + test('returns a plain media type instance', (t) => { + const found = new ContentType('Application/JSON') + t.assert.equal(found.mediaType, 'application/json') + t.assert.equal(found.type, 'application') + t.assert.equal(found.subtype, 'json') + t.assert.equal(found.parameters.size, 0) + }) + + test('handles empty parameters list', (t) => { + const found = new ContentType('Application/JSON ;') + t.assert.equal(found.isEmpty, false) + t.assert.equal(found.mediaType, 'application/json') + t.assert.equal(found.type, 'application') + t.assert.equal(found.subtype, 'json') + t.assert.equal(found.parameters.size, 0) + }) + + test('returns a media type instance with parameters', (t) => { + const found = new ContentType('Application/JSON ; charset=utf-8; foo=BaR;baz=" 42"') + t.assert.equal(found.isEmpty, false) + t.assert.equal(found.mediaType, 'application/json') + t.assert.equal(found.type, 'application') + t.assert.equal(found.subtype, 'json') + t.assert.equal(found.parameters.size, 3) + + const expected = [ + ['charset', 'utf-8'], + ['foo', 'BaR'], + ['baz', ' 42'] + ] + t.assert.deepStrictEqual( + Array.from(found.parameters.entries()), + expected + ) + + t.assert.equal( + found.toString(), + 'application/json; charset="utf-8"; foo="BaR"; baz=" 42"' + ) + }) + + test('skips invalid quoted string parameters', (t) => { + const found = new ContentType('Application/JSON ; charset=utf-8; foo=BaR;baz=" 42') + t.assert.equal(found.isEmpty, false) + t.assert.equal(found.mediaType, 'application/json') + t.assert.equal(found.type, 'application') + t.assert.equal(found.subtype, 'json') + t.assert.equal(found.parameters.size, 3) + + const expected = [ + ['charset', 'utf-8'], + ['foo', 'BaR'], + ['baz', 'invalid quoted string'] + ] + t.assert.deepStrictEqual( + Array.from(found.parameters.entries()), + expected + ) + + t.assert.equal( + found.toString(), + 'application/json; charset="utf-8"; foo="BaR"; baz="invalid quoted string"' + ) + }) +}) diff --git a/test/custom-parser.0.test.js b/test/custom-parser.0.test.js index 28831e3a6b2..a7649ff4f2b 100644 --- a/test/custom-parser.0.test.js +++ b/test/custom-parser.0.test.js @@ -354,7 +354,7 @@ test('catch all content type parser', async (t) => { method: 'POST', body: 'hello', headers: { - 'Content-Type': 'very-weird-content-type' + 'Content-Type': 'very-weird-content-type/foo' } }) @@ -363,7 +363,7 @@ test('catch all content type parser', async (t) => { t.assert.strictEqual(await result2.text(), 'hello') }) -test('catch all content type parser should not interfere with other conte type parsers', async (t) => { +test('catch all content type parser should not interfere with other content type parsers', async (t) => { t.plan(6) const fastify = Fastify() @@ -404,7 +404,7 @@ test('catch all content type parser should not interfere with other conte type p method: 'POST', body: 'hello', headers: { - 'Content-Type': 'very-weird-content-type' + 'Content-Type': 'very-weird-content-type/foo' } }) diff --git a/test/custom-parser.1.test.js b/test/custom-parser.1.test.js index c0ac1adad5c..ea0fe31ceb0 100644 --- a/test/custom-parser.1.test.js +++ b/test/custom-parser.1.test.js @@ -89,41 +89,6 @@ test('Should get the body as string /1', async (t) => { t.assert.strictEqual(await result.text(), 'hello world') }) -test('Should get the body as string /2', async (t) => { - t.plan(4) - const fastify = Fastify() - - fastify.post('/', (req, reply) => { - reply.send(req.body) - }) - - fastify.addContentTypeParser('text/plain/test', { parseAs: 'string' }, function (req, body, done) { - t.assert.ok('called') - t.assert.ok(typeof body === 'string') - try { - const plainText = body - done(null, plainText) - } catch (err) { - err.statusCode = 400 - done(err, undefined) - } - }) - - const fastifyServer = await fastify.listen({ port: 0 }) - t.after(() => fastify.close()) - - const result = await fetch(fastifyServer, { - method: 'POST', - body: 'hello world', - headers: { - 'Content-Type': ' text/plain/test ' - } - }) - - t.assert.strictEqual(result.status, 200) - t.assert.strictEqual(await result.text(), 'hello world') -}) - test('Should get the body as buffer', async (t) => { t.plan(4) const fastify = Fastify() diff --git a/test/custom-parser.3.test.js b/test/custom-parser.3.test.js index b359637f441..b8758fcbad6 100644 --- a/test/custom-parser.3.test.js +++ b/test/custom-parser.3.test.js @@ -189,7 +189,7 @@ test('catch all content type parser should not interfere with content type parse const assertions = [ { body: '{"myKey":"myValue"}', contentType: 'application/json', expected: JSON.stringify({ myKey: 'myValue' }) }, - { body: 'body', contentType: 'very-weird-content-type', expected: 'body' }, + { body: 'body', contentType: 'very-weird-content-type/foo', expected: 'body' }, { body: 'my text', contentType: 'text/html', expected: 'my texthtml' } ] diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index cde8063e111..86010a5bd90 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -1416,7 +1416,7 @@ test('Schema validation will not be bypass by different content type', async t = t.after(() => fastify.close()) const address = fastify.listeningOrigin - const correct1 = await fetch(address, { + let found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1424,10 +1424,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ foo: 'string' }) }) - t.assert.strictEqual(correct1.status, 200) - await correct1.bytes() + t.assert.strictEqual(found.status, 200) + await found.bytes() - const correct2 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1435,10 +1435,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ foo: 'string' }) }) - t.assert.strictEqual(correct2.status, 200) - await correct2.bytes() + t.assert.strictEqual(found.status, 200) + await found.bytes() - const correct3 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1446,10 +1446,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ foo: 'string' }) }) - t.assert.strictEqual(correct2.status, 200) - await correct3.bytes() + t.assert.strictEqual(found.status, 200) + await found.bytes() - const invalid1 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1457,10 +1457,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid1.status, 400) - t.assert.strictEqual((await invalid1.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') - const invalid2 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1468,10 +1468,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid2.status, 400) - t.assert.strictEqual((await invalid2.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') - const invalid3 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1479,10 +1479,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid3.status, 400) - t.assert.strictEqual((await invalid3.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') - const invalid4 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1490,10 +1490,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid4.status, 400) - t.assert.strictEqual((await invalid4.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') - const invalid5 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1501,10 +1501,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid5.status, 400) - t.assert.strictEqual((await invalid5.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') - const invalid6 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1512,10 +1512,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid6.status, 400) - t.assert.strictEqual((await invalid6.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') - const invalid7 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1523,10 +1523,10 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid7.status, 400) - t.assert.strictEqual((await invalid7.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') - const invalid8 = await fetch(address, { + found = await fetch(address, { method: 'POST', url: '/', headers: { @@ -1534,6 +1534,39 @@ test('Schema validation will not be bypass by different content type', async t = }, body: JSON.stringify({ invalid: 'string' }) }) - t.assert.strictEqual(invalid8.status, 400) - t.assert.strictEqual((await invalid8.json()).code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn\ta' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn\ta; charset=utf-8' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/ json' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') }) From e1e4fe75910c36e1b9ebb87026ca16c053a9c1f2 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Mon, 26 Jan 2026 06:11:52 -0500 Subject: [PATCH 1238/1295] v5.7.2 --- fastify.js | 2 +- lib/error-serializer.js | 34 ++++++++++++++++++++++++---------- package.json | 2 +- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/fastify.js b/fastify.js index b194e9ee37f..736603e95b7 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.7.1' +const VERSION = '5.7.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/lib/error-serializer.js b/lib/error-serializer.js index 71fb87b9ce1..c52d8a0d2fa 100644 --- a/lib/error-serializer.js +++ b/lib/error-serializer.js @@ -13,6 +13,20 @@ module.exports = function anonymous(validator,serializer ) { + +const { + asString, + asNumber, + asBoolean, + asDateTime, + asDate, + asTime, + asUnsafeString +} = serializer + +const asInteger = serializer.asInteger.bind(serializer) + + const JSON_STR_BEGIN_OBJECT = '{' const JSON_STR_END_OBJECT = '}' const JSON_STR_BEGIN_ARRAY = '[' @@ -43,7 +57,7 @@ let addComma = false if (value !== undefined) { !addComma && (addComma = true) || (json += JSON_STR_COMMA) json += "\"statusCode\":" - json += serializer.asNumber(value) + json += asNumber(value) } value = obj["code"] @@ -57,12 +71,12 @@ let addComma = false } else if (value instanceof Date) { json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE } else if (value instanceof RegExp) { - json += serializer.asString(value.source) + json += asString(value.source) } else { - json += serializer.asString(value.toString()) + json += asString(value.toString()) } } else { - json += serializer.asString(value) + json += asString(value) } } @@ -78,12 +92,12 @@ let addComma = false } else if (value instanceof Date) { json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE } else if (value instanceof RegExp) { - json += serializer.asString(value.source) + json += asString(value.source) } else { - json += serializer.asString(value.toString()) + json += asString(value.toString()) } } else { - json += serializer.asString(value) + json += asString(value) } } @@ -99,12 +113,12 @@ let addComma = false } else if (value instanceof Date) { json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE } else if (value instanceof RegExp) { - json += serializer.asString(value.source) + json += asString(value.source) } else { - json += serializer.asString(value.toString()) + json += asString(value.toString()) } } else { - json += serializer.asString(value) + json += asString(value) } } diff --git a/package.json b/package.json index 0d6b3aa6c4a..20c41611b97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.7.1", + "version": "5.7.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From b48826f07beaa925809c5e347d67a7e2502eb156 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 26 Jan 2026 13:08:52 +0100 Subject: [PATCH 1239/1295] docs: update Reply.send() documentation for string serialization (#6466) --- docs/Reference/Reply.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index ed21f9756ec..572f12748b0 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -671,9 +671,20 @@ fastify.get('/json', options, function (request, reply) { If you pass a string to `send` without a `Content-Type`, it will be sent as `text/plain; charset=utf-8`. If you set the `Content-Type` header and pass a string to `send`, it will be serialized with the custom serializer if one is -set, otherwise, it will be sent unmodified (unless the `Content-Type` header is -set to `application/json; charset=utf-8`, in which case it will be -JSON-serialized like an object — see the section above). +set, otherwise, it will be sent unmodified. + +> **Note:** Even when the `Content-Type` header is set to `application/json`, +> strings are sent unmodified by default. To serialize a string as JSON, you +> must set a custom serializer: + +```js +fastify.get('/json-string', async function (request, reply) { + reply + .type('application/json; charset=utf-8') + .serializer(JSON.stringify) + .send('Hello') // Returns "Hello" (JSON-encoded string) +}) +``` ```js fastify.get('/json', options, function (request, reply) { reply.send('plain string') From 17172c40506604fd15f8e5f17299b671d4b58686 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 30 Jan 2026 23:43:42 +0100 Subject: [PATCH 1240/1295] Ignore agents config files (#6474) --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 021407e223f..84ba3d43f0e 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,11 @@ out.tap test/https/fastify.cert test/https/fastify.key /test/types/import.js + +# Agents files +CLAUDE.md +AGENTS.md +.agents/ +.agent +.claude +.pi From d98ce2a0c030e18d27d93ed000788f95944ed910 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 31 Jan 2026 09:20:37 +0100 Subject: [PATCH 1241/1295] docs: update vulnerability reporting to use GitHub Security (#6475) --- SECURITY.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 4dd4f7af96d..0e6ad2b8f15 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -46,10 +46,11 @@ explicitly enabled via configuration options ## Reporting vulnerabilities Individuals who find potential vulnerabilities in Fastify are invited to -complete a vulnerability report via the dedicated pages: +complete a vulnerability report via the GitHub Security page: -1. [HackerOne](https://hackerone.com/fastify) -2. [GitHub Security Advisory](https://github.com/fastify/fastify/security/advisories/new) +https://github.com/fastify/fastify/security/advisories/new + +Note: Our [HackerOne](https://hackerone.com/fastify) program is now closed. ### Strict measures when reporting vulnerabilities From eb11156396f6a5fedaceed0140aed2b7f026be37 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Feb 2026 18:52:56 +0100 Subject: [PATCH 1242/1295] Merge commit from fork --- lib/reply.js | 16 +++++++++- test/web-api.test.js | 73 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/lib/reply.js b/lib/reply.js index c126616d5e4..d36becec478 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -687,6 +687,7 @@ function sendWebStream (payload, res, reply) { let sourceOpen = true let errorLogged = false + let waitingDrain = false const reader = payload.getReader() eos(res, function (err) { @@ -719,7 +720,20 @@ function sendWebStream (payload, res, reply) { reader.cancel().catch(noop) return } - res.write(result.value) + const shouldContinue = res.write(result.value) + if (shouldContinue === false) { + waitingDrain = true + res.once('drain', onDrain) + return + } + reader.read().then(onRead, onReadError) + } + + function onDrain () { + if (!waitingDrain || !sourceOpen || res.destroyed) { + return + } + waitingDrain = false reader.read().then(onRead, onReadError) } diff --git a/test/web-api.test.js b/test/web-api.test.js index 7a87105cb8a..fa24638acb3 100644 --- a/test/web-api.test.js +++ b/test/web-api.test.js @@ -6,6 +6,7 @@ const fs = require('node:fs') const { Readable } = require('node:stream') const { fetch: undiciFetch } = require('undici') const http = require('node:http') +const { setTimeout: sleep } = require('node:timers/promises') test('should response with a ReadableStream', async (t) => { t.plan(2) @@ -427,6 +428,78 @@ test('WebStream should cancel reader when response is destroyed', (t, done) => { }) }) +test('WebStream should respect backpressure', async (t) => { + t.plan(3) + + const fastify = Fastify() + t.after(() => fastify.close()) + + let drainEmittedAt = 0 + let secondWriteAt = 0 + let resolveSecondWrite + const secondWrite = new Promise((resolve) => { + resolveSecondWrite = resolve + }) + + fastify.get('/', function (request, reply) { + const raw = reply.raw + const originalWrite = raw.write.bind(raw) + const bufferedChunks = [] + let wroteFirstChunk = false + + raw.once('drain', () => { + for (const bufferedChunk of bufferedChunks) { + originalWrite(bufferedChunk) + } + }) + + raw.write = function (chunk, encoding, cb) { + if (!wroteFirstChunk) { + wroteFirstChunk = true + bufferedChunks.push(Buffer.from(chunk)) + sleep(100).then(() => { + drainEmittedAt = Date.now() + raw.emit('drain') + }) + if (typeof cb === 'function') { + cb() + } + return false + } + if (!secondWriteAt) { + secondWriteAt = Date.now() + resolveSecondWrite() + } + return originalWrite(chunk, encoding, cb) + } + + const stream = new ReadableStream({ + start (controller) { + controller.enqueue(Buffer.from('chunk-1')) + }, + pull (controller) { + controller.enqueue(Buffer.from('chunk-2')) + controller.close() + } + }) + + reply.header('content-type', 'text/plain').send(stream) + }) + + await fastify.listen({ port: 0 }) + + const response = await undiciFetch(`http://localhost:${fastify.server.address().port}/`) + const bodyPromise = response.text() + + await secondWrite + await sleep(120) + const body = await bodyPromise + + t.assert.strictEqual(response.status, 200) + t.assert.strictEqual(body, 'chunk-1chunk-2') + t.assert.ok(secondWriteAt >= drainEmittedAt) +}) + test('WebStream should warn when headers already sent', async (t) => { t.plan(2) From 49468eddb7c59e07fb95183afbf03498fccac99e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Feb 2026 18:56:22 +0100 Subject: [PATCH 1243/1295] Bumped v5.7.3 Signed-off-by: Matteo Collina --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 20c41611b97..dd36b728d9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.7.2", + "version": "5.7.3", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 4682a78381fcb7b9c382cc734295cfc926e581e4 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Feb 2026 19:22:45 +0100 Subject: [PATCH 1244/1295] Bumped v5.7.4 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 736603e95b7..995b8636adc 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.7.2' +const VERSION = '5.7.4' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index dd36b728d9c..f9bea84ef48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.7.3", + "version": "5.7.4", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From d0c2f451ea16b9e611a160f72a088f395c373b02 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Feb 2026 21:24:53 +0100 Subject: [PATCH 1245/1295] docs(request): add host security warning references (#6476) --- docs/Reference/Request.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index f1fadfa3cc9..64aa43af3e9 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -25,6 +25,11 @@ Request is a core Fastify object containing the following fields: enabled). For HTTP/2 compatibility, it returns `:authority` if no host header exists. The host header may return an empty string if `requireHostHeader` is `false`, not provided with HTTP/1.0, or removed by schema validation. + ⚠️ Security: this value comes from client-controlled headers; only trust it + when you control proxy behavior and have validated or allow-listed hosts. + No additional validation is performed beyond RFC parsing (see + [RFC 9110, section 7.2](https://www.rfc-editor.org/rfc/rfc9110#section-7.2) and + [RFC 3986, section 3.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2)). - `hostname` - The hostname derived from the `host` property of the incoming request. - `port` - The port from the `host` property, which may refer to the port the server is listening on. From 96c40c054fca48864c67dc4cd0eac0cb0167c4d8 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 7 Feb 2026 11:45:31 +0000 Subject: [PATCH 1246/1295] docs: fix note style (#6487) * docs: fix note style * Update Hooks.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Frazer Smith * Update docs/Reference/Reply.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Frazer Smith * Update docs/Reference/Logging.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Frazer Smith --------- Signed-off-by: Frazer Smith Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/Guides/Fluent-Schema.md | 3 +- docs/Guides/Migration-Guide-V4.md | 17 +++---- docs/Reference/ContentTypeParser.md | 3 +- docs/Reference/Decorators.md | 6 ++- docs/Reference/Hooks.md | 44 ++++++++++++------- docs/Reference/Logging.md | 16 ++++--- docs/Reference/Middleware.md | 5 ++- docs/Reference/Reply.md | 23 ++++++---- docs/Reference/Request.md | 5 ++- docs/Reference/Routes.md | 12 +++-- docs/Reference/Server.md | 8 ++-- docs/Reference/TypeScript.md | 18 +++++--- .../Reference/Validation-and-Serialization.md | 4 +- 13 files changed, 104 insertions(+), 60 deletions(-) diff --git a/docs/Guides/Fluent-Schema.md b/docs/Guides/Fluent-Schema.md index 954e7242af8..a1f10cdfe6c 100644 --- a/docs/Guides/Fluent-Schema.md +++ b/docs/Guides/Fluent-Schema.md @@ -122,5 +122,6 @@ const schema = { body: bodyJsonSchema } fastify.post('/the/url', { schema }, handler) ``` -> ℹ️ Note: You can mix up the `$ref-way` and the `replace-way` +> ℹ️ Note: +> You can mix up the `$ref-way` and the `replace-way` > when using `fastify.addSchema`. diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 34a820acc7e..3dd4f23a911 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -83,8 +83,8 @@ If you need to use middleware, use continue to be maintained. However, it is strongly recommended that you migrate to Fastify's [hooks](../Reference/Hooks.md). -> **Note**: Codemod remove `app.use()` with: -> +> ℹ️ Note: +> Codemod remove `app.use()` with: > ```bash > npx codemod@latest fastify/4/remove-app-use > ``` @@ -94,8 +94,8 @@ However, it is strongly recommended that you migrate to Fastify's [hooks](../Ref If you previously used the `reply.res` attribute to access the underlying Request object you will now need to use `reply.raw`. -> **Note**: Codemod `reply.res` to `reply.raw` with: -> +> ℹ️ Note: +> Codemod `reply.res` to `reply.raw` with: > ```bash > npx codemod@latest fastify/4/reply-raw-access > ``` @@ -146,8 +146,9 @@ As a result, if you specify an `onRoute` hook in a plugin you should now either: done(); }); ``` -> **Note**: Codemod synchronous route definitions with: -> + +> ℹ️ Note: +> Codemod synchronous route definitions with: > ```bash > npx codemod@latest fastify/4/wrap-routes-plugin > ``` @@ -176,8 +177,8 @@ As a result, if you specify an `onRoute` hook in a plugin you should now either: }); ``` -> **Note**: Codemod 'await register(...)' with: -> +> ℹ️ Note: +> Codemod 'await register(...)' with: > ```bash > npx codemod@latest fastify/4/await-register-calls > ``` diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index 1352f65a5f4..c2b05e59e44 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -152,7 +152,8 @@ fastify.addContentTypeParser('text/xml', function (request, payload, done) { }) ``` -> ℹ️ Note: `function(req, done)` and `async function(req)` are +> ℹ️ Note: +> `function(req, done)` and `async function(req)` are > still supported but deprecated. #### Body Parser diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md index 80878d19c76..5d57691c066 100644 --- a/docs/Reference/Decorators.md +++ b/docs/Reference/Decorators.md @@ -399,7 +399,8 @@ fastify.register(async function (fastify) { }) ``` -> ℹ️ Note: For TypeScript users, `getDecorator` supports generic type parameters. +> ℹ️ Note: +> For TypeScript users, `getDecorator` supports generic type parameters. > See the [TypeScript documentation](/docs/Reference/TypeScript.md) for > advanced typing examples. @@ -429,6 +430,7 @@ fastify.addHook('preHandler', async (req, reply) => { }) ``` -> ℹ️ Note: For TypeScript users, see the +> ℹ️ Note: +> For TypeScript users, see the > [TypeScript documentation](/docs/Reference/TypeScript.md) for advanced > typing examples using `setDecorator`. diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index ebc86f62172..107b32c5b99 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -34,7 +34,8 @@ are Request/Reply hooks and application hooks: - [Using Hooks to Inject Custom Properties](#using-hooks-to-inject-custom-properties) - [Diagnostics Channel Hooks](#diagnostics-channel-hooks) -> ℹ️ Note: The `done` callback is not available when using `async`/`await` or +> ℹ️ Note: +> The `done` callback is not available when using `async`/`await` or > returning a `Promise`. If you do invoke a `done` callback in this situation > unexpected behavior may occur, e.g. duplicate invocation of handlers. @@ -68,7 +69,8 @@ fastify.addHook('onRequest', async (request, reply) => { }) ``` -> ℹ️ Note: In the [onRequest](#onrequest) hook, `request.body` will always be +> ℹ️ Note: +> In the [onRequest](#onrequest) hook, `request.body` will always be > `undefined`, because the body parsing happens before the > [preValidation](#prevalidation) hook. @@ -98,16 +100,19 @@ fastify.addHook('preParsing', async (request, reply, payload) => { }) ``` -> ℹ️ Note: In the [preParsing](#preparsing) hook, `request.body` will always be +> ℹ️ Note: +> In the [preParsing](#preparsing) hook, `request.body` will always be > `undefined`, because the body parsing happens before the > [preValidation](#prevalidation) hook. -> ℹ️ Note: You should also add a `receivedEncodedLength` property to the +> ℹ️ Note: +> You should also add a `receivedEncodedLength` property to the > returned stream. This property is used to correctly match the request payload > with the `Content-Length` header value. Ideally, this property should be updated > on each received chunk. -> ℹ️ Note: The size of the returned stream is checked to not exceed the limit +> ℹ️ Note: +> The size of the returned stream is checked to not exceed the limit > set in [`bodyLimit`](./Server.md#bodylimit) option. ### preValidation @@ -166,7 +171,8 @@ fastify.addHook('preSerialization', async (request, reply, payload) => { }) ``` -> ℹ️ Note: The hook is NOT called if the payload is a `string`, a `Buffer`, a +> ℹ️ Note: +> The hook is NOT called if the payload is a `string`, a `Buffer`, a > `stream`, or `null`. ### onError @@ -192,7 +198,8 @@ an exception. This hook will be executed before the [Custom Error Handler set by `setErrorHandler`](./Server.md#seterrorhandler). -> ℹ️ Note: Unlike the other hooks, passing an error to the `done` function is not +> ℹ️ Note: +> Unlike the other hooks, passing an error to the `done` function is not > supported. ### onSend @@ -229,7 +236,8 @@ fastify.addHook('onSend', (request, reply, payload, done) => { > to `0`, whereas the `Content-Length` header will not be set if the payload is > `null`. -> ℹ️ Note: If you change the payload, you may only change it to a `string`, a +> ℹ️ Note: +> If you change the payload, you may only change it to a `string`, a > `Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`. @@ -252,7 +260,8 @@ The `onResponse` hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example, to gather statistics. -> ℹ️ Note: Setting `disableRequestLogging` to `true` will disable any error log +> ℹ️ Note: +> Setting `disableRequestLogging` to `true` will disable any error log > inside the `onResponse` hook. In this case use `try - catch` to log errors. ### onTimeout @@ -294,7 +303,8 @@ The `onRequestAbort` hook is executed when a client closes the connection before the entire request has been processed. Therefore, you will not be able to send data to the client. -> ℹ️ Note: Client abort detection is not completely reliable. +> ℹ️ Note: +> Client abort detection is not completely reliable. > See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md) ### Manage Errors from a hook @@ -448,8 +458,9 @@ fastify.addHook('onListen', async function () { }) ``` -> ℹ️ Note: This hook will not run when the server is started using -> fastify.inject()` or `fastify.ready()`. +> ℹ️ Note: +> This hook will not run when the server is started using +> `fastify.inject()` or `fastify.ready()`. ### onClose @@ -572,7 +583,8 @@ This hook can be useful if you are developing a plugin that needs to know when a plugin context is formed, and you want to operate in that specific context, thus this hook is encapsulated. -> ℹ️ Note: This hook will not be called if a plugin is wrapped inside +> ℹ️ Note: +> This hook will not be called if a plugin is wrapped inside > [`fastify-plugin`](https://github.com/fastify/fastify-plugin). ```js fastify.decorate('data', []) @@ -770,7 +782,8 @@ fastify.route({ }) ``` -> ℹ️ Note: Both options also accept an array of functions. +> ℹ️ Note: +> Both options also accept an array of functions. ## Using Hooks to Inject Custom Properties @@ -857,7 +870,8 @@ channel.subscribe(function ({ fastify }) { }) ``` -> ℹ️ Note: The TracingChannel class API is currently experimental and may undergo +> ℹ️ Note: +> The TracingChannel class API is currently experimental and may undergo > breaking changes even in semver-patch releases of Node.js. Five other events are published on a per-request basis following the diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index d7ca2caad48..32420e86fe5 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -107,7 +107,8 @@ value is used; otherwise, a new incremental ID is generated. See Fastify Factory [`requestIdHeader`](./Server.md#factory-request-id-header) and Fastify Factory [`genReqId`](./Server.md#genreqid) for customization options. -> ⚠ Warning: enabling `requestIdHeader` allows any callers to set `reqId` to a +> ⚠ Warning: +> Enabling `requestIdHeader` allows any callers to set `reqId` to a > value of their choosing. > No validation is performed on `requestIdHeader`. @@ -161,7 +162,8 @@ const fastify = require('fastify')({ }); ``` -> ℹ️ Note: In some cases, the [`Reply`](./Reply.md) object passed to the `res` +> ℹ️ Note: +> In some cases, the [`Reply`](./Reply.md) object passed to the `res` > serializer cannot be fully constructed. When writing a custom `res` > serializer, check for the existence of any properties on `reply` aside from > `statusCode`, which is always present. For example, verify the existence of @@ -188,9 +190,10 @@ const fastify = require('fastify')({ }); ``` -> ℹ️ Note: The body cannot be serialized inside a `req` method because the -request is serialized when the child logger is created. At that time, the body -is not yet parsed. +> ℹ️ Note: +> The body cannot be serialized inside a `req` method because the +> request is serialized when the child logger is created. At that time, the body +> is not yet parsed. See the following approach to log `req.body`: @@ -203,7 +206,8 @@ app.addHook('preHandler', function (req, reply, done) { }) ``` -> ℹ️ Note: Ensure serializers never throw errors, as this can cause the Node +> ℹ️ Note: +> Ensure serializers never throw errors, as this can cause the Node > process to exit. See the > [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) for more > information. diff --git a/docs/Reference/Middleware.md b/docs/Reference/Middleware.md index 5d4d7b03cfc..e92a0c58c48 100644 --- a/docs/Reference/Middleware.md +++ b/docs/Reference/Middleware.md @@ -50,7 +50,8 @@ that already has the Fastify [Request](./Request.md#request) and To run middleware under certain paths, pass the path as the first parameter to `use`. -> ℹ️ Note: This does not support routes with parameters +> ℹ️ Note: +> This does not support routes with parameters > (e.g. `/user/:id/comments`) and wildcards are not supported in multiple paths. ```js @@ -75,4 +76,4 @@ Fastify offers alternatives to commonly used middleware, such as [`@fastify/cors`](https://github.com/fastify/fastify-cors) for [`cors`](https://github.com/expressjs/cors), and [`@fastify/static`](https://github.com/fastify/fastify-static) for -[`serve-static`](https://github.com/expressjs/serve-static). \ No newline at end of file +[`serve-static`](https://github.com/expressjs/serve-static). diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md index 572f12748b0..e950ee8a509 100644 --- a/docs/Reference/Reply.md +++ b/docs/Reference/Reply.md @@ -152,7 +152,8 @@ fastify.get('/', async function (req, rep) { Sets a response header. If the value is omitted or undefined, it is coerced to `''`. -> ℹ️ Note: The header's value must be properly encoded using +> ℹ️ Note: +> The header's value must be properly encoded using > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) > or similar modules such as > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid characters @@ -261,11 +262,13 @@ requires heavy resources to be sent after the `data`, for example, `Server-Timing` and `Etag`. It can ensure the client receives the response data as soon as possible. -> ℹ️ Note: The header `Transfer-Encoding: chunked` will be added once you use +> ℹ️ Note: +> The header `Transfer-Encoding: chunked` will be added once you use > the trailer. It is a hard requirement for using trailer in Node.js. -> ℹ️ Note: Any error passed to `done` callback will be ignored. If you interested -> in the error, you can turn on `debug` level logging.* +> ℹ️ Note: +> Any error passed to `done` callback will be ignored. If you are interested +> in the error, you can turn on `debug` level logging. ```js reply.trailer('server-timing', function() { @@ -315,7 +318,8 @@ reply.getTrailer('server-timing') // undefined Redirects a request to the specified URL, the status code is optional, default to `302` (if status code is not already set by calling `code`). -> ℹ️ Note: The input URL must be properly encoded using +> ℹ️ Note: +> The input URL must be properly encoded using > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) > or similar modules such as > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid URLs will @@ -673,7 +677,8 @@ If you pass a string to `send` without a `Content-Type`, it will be sent as string to `send`, it will be serialized with the custom serializer if one is set, otherwise, it will be sent unmodified. -> **Note:** Even when the `Content-Type` header is set to `application/json`, +> ℹ️ Note: +> Even when the `Content-Type` header is set to `application/json`, > strings are sent unmodified by default. To serialize a string as JSON, you > must set a custom serializer: @@ -838,7 +843,8 @@ automatically create an error structured as the following: You can add custom properties to the Error object, such as `headers`, that will be used to enhance the HTTP response. -> ℹ️ Note: If you are passing an error to `send` and the statusCode is less than +> ℹ️ Note: +> If you are passing an error to `send` and the statusCode is less than > 400, Fastify will automatically set it at 500. Tip: you can simplify errors by using the @@ -886,7 +892,8 @@ fastify.get('/', { If you want to customize error handling, check out [`setErrorHandler`](./Server.md#seterrorhandler) API. -> ℹ️ Note: you are responsible for logging when customizing the error handler. +> ℹ️ Note: +> You are responsible for logging when customizing the error handler. API: diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 64aa43af3e9..96e9b3db79e 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -25,7 +25,7 @@ Request is a core Fastify object containing the following fields: enabled). For HTTP/2 compatibility, it returns `:authority` if no host header exists. The host header may return an empty string if `requireHostHeader` is `false`, not provided with HTTP/1.0, or removed by schema validation. - ⚠️ Security: this value comes from client-controlled headers; only trust it + ⚠ Security: this value comes from client-controlled headers; only trust it when you control proxy behavior and have validated or allow-listed hosts. No additional validation is performed beyond RFC parsing (see [RFC 9110, section 7.2](https://www.rfc-editor.org/rfc/rfc9110#section-7.2) and @@ -89,7 +89,8 @@ This operation adds new values to the request headers, accessible via For performance reasons, `Symbol('fastify.RequestAcceptVersion')` may be added to headers on `not found` routes. -> ℹ️ Note: Schema validation may mutate the `request.headers` and +> ℹ️ Note: +> Schema validation may mutate the `request.headers` and > `request.raw.headers` objects, causing the headers to become empty. ```js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 8dc0937e22d..6c1dd98c91b 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -138,7 +138,8 @@ fastify.route(options) * `reply` is defined in [Reply](./Reply.md). -> ℹ️ Note: The documentation for `onRequest`, `preParsing`, `preValidation`, +> ℹ️ Note: +> The documentation for `onRequest`, `preParsing`, `preValidation`, > `preHandler`, `preSerialization`, `onSend`, and `onResponse` is detailed in > [Hooks](./Hooks.md). To send a response before the request is handled by the > `handler`, see [Respond to a request from @@ -234,7 +235,8 @@ const opts = { fastify.get('/', opts) ``` -> ℹ️ Note: Specifying the handler in both `options` and as the third parameter to +> ℹ️ Note: +> Specifying the handler in both `options` and as the third parameter to > the shortcut method throws a duplicate `handler` error. ### Url building @@ -403,7 +405,8 @@ This approach supports both `callback-style` and `async-await` with minimal trade-off. However, it is recommended to use only one style for consistent error handling within your application. -> ℹ️ Note: Every async function returns a promise by itself. +> ℹ️ Note: +> Every async function returns a promise by itself. ### Route Prefixing @@ -636,7 +639,8 @@ has a version set, and will prefer a versioned route to a non-versioned route for the same path. Advanced version ranges and pre-releases currently are not supported. -> **Note:** using this feature can degrade the router’s performance. +> ℹ️ Note: +> Using this feature can degrade the router's performance. ```js fastify.route({ diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index dfb00758b89..f6b59ceb52a 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -427,7 +427,8 @@ const fastify = require('fastify')({ }) ``` -> ⚠ Warning: enabling this allows any callers to set `reqId` to a +> ⚠ Warning: +> Enabling this allows any callers to set `reqId` to a > value of their choosing. > No validation is performed on `requestIdHeader`. @@ -865,7 +866,7 @@ const fastify = require('fastify')({ }) ``` -> **Note** +> ℹ️ Note: > The `req` and `res` objects passed to `defaultRoute` are the raw Node.js > `IncomingMessage` and `ServerResponse` instances. They do **not** expose the > Fastify-specific methods available on `FastifyRequest`/`FastifyReply` (for @@ -1029,7 +1030,8 @@ fastify.get('/dev', async (request, reply) => { * **Default:** `true` -> ⚠ **Warning:** This option will be set to `false` by default +> ⚠ Warning: +> This option will be set to `false` by default > in the next major release. When set to `false`, it prevents `setErrorHandler` from being called diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index e98f065f7b4..13ca957ba80 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -58,8 +58,9 @@ in a blank http Fastify server. or use one of the [recommended ones](https://github.com/tsconfig/bases#node-14-tsconfigjson). -*Note: Set `target` property in `tsconfig.json` to `es2017` or greater to avoid -[FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.* +> ℹ️ Note: +> Set `target` property in `tsconfig.json` to `es2017` or greater to avoid +> [FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning. 4. Create an `index.ts` file - this will contain the server code 5. Add the following code block to your file: @@ -1621,8 +1622,9 @@ previous hook was `preValidation`, the next hook will be `preSerialization`. `preSerialization` is the fifth hook to be executed in the request lifecycle. The previous hook was `preHandler`, the next hook will be `onSend`. -Note: the hook is NOT called if the payload is a string, a Buffer, a stream or -null. +> ℹ️ Note: +> The hook is NOT called if the payload is a string, a Buffer, +> a stream, or null. ##### fastify.onSendHookHandler< OnSendPayload, [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: OnSendPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\ | void @@ -1632,8 +1634,9 @@ You can change the payload with the `onSend` hook. It is the sixth hook to be executed in the request lifecycle. The previous hook was `preSerialization`, the next hook will be `onResponse`. -Note: If you change the payload, you may only change it to a string, a Buffer, a -stream, or null. +> ℹ️ Note: +> If you change the payload, you may only change it to a string, +> a Buffer, a stream, or null. ##### fastify.onResponseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void @@ -1679,7 +1682,8 @@ created. The hook will be executed before the registered code. This hook can be useful if you are developing a plugin that needs to know when a plugin context is formed, and you want to operate in that specific context. -Note: This hook will not be called if a plugin is wrapped inside fastify-plugin. +> ℹ️ Note: +> This hook will not be called if a plugin is wrapped inside fastify-plugin. ##### fastify.onCloseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 2d1134da908..1a1d8ee7dd8 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -439,7 +439,9 @@ fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { return ajv.compile(schema) }) ``` -> ℹ️ Note: When using a custom validator instance, add schemas to the validator + +> ℹ️ Note: +> When using a custom validator instance, add schemas to the validator > instead of Fastify. Fastify's `addSchema` method will not recognize the custom > validator. From 3937f93a220247a0e150272dd7e64267008db597 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 7 Feb 2026 16:03:05 +0100 Subject: [PATCH 1247/1295] chore: rename deploy website ci (#6492) --- .github/workflows/website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 99ed11f65d1..8b79fd67318 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -20,6 +20,6 @@ jobs: steps: - name: Build website run: | - gh workflow run ci-cd.yml -R fastify/website + gh workflow run deploy-website.yml -R fastify/website env: GH_TOKEN: ${{ secrets.GHA_WEBSITE_FINE_TOKEN }} From ae8ab65a5001facac11943a7fee5bfe27a617a53 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 8 Feb 2026 10:28:47 +0100 Subject: [PATCH 1248/1295] chore: support pino v9 and v10 (#6496) --- .github/workflows/ci.yml | 44 +++++++++++++++++++++++ package.json | 2 +- test/web-api.test.js | 75 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcd0befbb23..61b81d07a89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -204,10 +204,54 @@ jobs: env: NODE_OPTIONS: no-network-family-autoselection + test-pino-compat: + name: Test pino compatibility + needs: + - lint + - coverage-nix + - coverage-win + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + matrix: + pino-version: ['^9', '^10'] + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: package.json + check-latest: true + + - name: Install dependencies + run: | + npm install --ignore-scripts + + - name: Install pino ${{ matrix.pino-version }} + run: | + npm install --ignore-scripts --no-save pino@${{ matrix.pino-version }} + + - name: Run unit tests + run: | + npm run unit + + - name: Run typescript tests + run: | + npm run test:typescript + env: + NODE_OPTIONS: no-network-family-autoselection + package: needs: - test-typescript - test-unit + - test-pino-compat runs-on: ubuntu-latest permissions: contents: read diff --git a/package.json b/package.json index f9bea84ef48..ceb8d404cce 100644 --- a/package.json +++ b/package.json @@ -212,7 +212,7 @@ "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", - "pino": "^10.1.0", + "pino": "^9.14.0 || ^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", diff --git a/test/web-api.test.js b/test/web-api.test.js index fa24638acb3..e4fd574ce70 100644 --- a/test/web-api.test.js +++ b/test/web-api.test.js @@ -500,6 +500,81 @@ test('WebStream should respect backpressure', async (t) => { t.assert.ok(secondWriteAt >= drainEmittedAt) }) +test('WebStream should stop reading on drain after response destroy', async (t) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + let cancelCalled = false + let resolveCancel + const cancelPromise = new Promise((resolve) => { + resolveCancel = resolve + }) + + fastify.get('/', function (request, reply) { + const raw = reply.raw + const originalWrite = raw.write.bind(raw) + let firstWrite = true + + raw.write = function (chunk, encoding, cb) { + if (firstWrite) { + firstWrite = false + if (typeof cb === 'function') { + cb() + } + queueMicrotask(() => { + raw.destroy() + raw.emit('drain') + }) + return false + } + return originalWrite(chunk, encoding, cb) + } + + const stream = new ReadableStream({ + start (controller) { + controller.enqueue(Buffer.from('chunk-1')) + }, + pull (controller) { + controller.enqueue(Buffer.from('chunk-2')) + controller.close() + }, + cancel () { + cancelCalled = true + resolveCancel() + } + }) + + reply.header('content-type', 'text/plain').send(stream) + }) + + await new Promise((resolve, reject) => { + fastify.listen({ port: 0 }, err => { + if (err) return reject(err) + resolve() + }) + }) + + await new Promise((resolve, reject) => { + const req = http.get(`http://localhost:${fastify.server.address().port}/`, (res) => { + res.once('close', resolve) + res.resume() + }) + req.once('error', (err) => { + if (err.code === 'ECONNRESET') { + resolve() + } else { + reject(err) + } + }) + }) + + await cancelPromise + t.assert.ok(true, 'response interrupted as expected') + t.assert.strictEqual(cancelCalled, true) +}) + test('WebStream should warn when headers already sent', async (t) => { t.plan(2) From 00f202f35d4a33fe45044995023ef1679200e559 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 8 Feb 2026 11:40:52 +0100 Subject: [PATCH 1249/1295] chore: update logger types and fix TODO comment (#6470) --- test/types/logger.test-d.ts | 35 +++++++++++++++++------------------ test/types/register.test-d.ts | 4 ++-- test/types/reply.test-d.ts | 4 ++-- test/types/request.test-d.ts | 6 +++--- types/logger.d.ts | 2 +- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index e084a46e9d1..04fc88bcad4 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -2,38 +2,37 @@ import { expectAssignable, expectDeprecated, expectError, expectNotAssignable, e import fastify, { FastifyLogFn, LogLevel, - FastifyLoggerInstance, + FastifyBaseLogger, FastifyRequest, - FastifyReply, - FastifyBaseLogger + FastifyReply } from '../../fastify' import { Server, IncomingMessage, ServerResponse } from 'node:http' import * as fs from 'node:fs' import P from 'pino' -import { ResSerializerReply } from '../../types/logger' +import { FastifyLoggerInstance, ResSerializerReply } from '../../types/logger' -expectType(fastify().log) +expectType(fastify().log) class Foo {} ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(logLevel => { expectType( - fastify().log[logLevel as LogLevel] + fastify().log[logLevel as LogLevel] ) expectType( - fastify().log[logLevel as LogLevel]('') + fastify().log[logLevel as LogLevel]('') ) expectType( - fastify().log[logLevel as LogLevel]({}) + fastify().log[logLevel as LogLevel]({}) ) expectType( - fastify().log[logLevel as LogLevel]({ foo: 'bar' }) + fastify().log[logLevel as LogLevel]({ foo: 'bar' }) ) expectType( - fastify().log[logLevel as LogLevel](new Error()) + fastify().log[logLevel as LogLevel](new Error()) ) expectType( - fastify().log[logLevel as LogLevel](new Foo()) + fastify().log[logLevel as LogLevel](new Foo()) ) }) @@ -109,7 +108,7 @@ ServerResponse } }) -expectType(serverWithLogOptions.log) +expectType(serverWithLogOptions.log) const serverWithFileOption = fastify< Server, @@ -122,7 +121,7 @@ ServerResponse } }) -expectType(serverWithFileOption.log) +expectType(serverWithFileOption.log) const serverAutoInferringTypes = fastify({ logger: { @@ -266,11 +265,11 @@ expectDeprecated({} as FastifyLoggerInstance) const childParent = fastify().log // we test different option variant here -expectType(childParent.child({}, { level: 'info' })) -expectType(childParent.child({}, { level: 'silent' })) -expectType(childParent.child({}, { redact: ['pass', 'pin'] })) -expectType(childParent.child({}, { serializers: { key: () => {} } })) -expectType(childParent.child({}, { level: 'info', redact: ['pass', 'pin'], serializers: { key: () => {} } })) +expectType(childParent.child({}, { level: 'info' })) +expectType(childParent.child({}, { level: 'silent' })) +expectType(childParent.child({}, { redact: ['pass', 'pin'] })) +expectType(childParent.child({}, { serializers: { key: () => {} } })) +expectType(childParent.child({}, { level: 'info', redact: ['pass', 'pin'], serializers: { key: () => {} } })) // no option pass expectError(childParent.child()) diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index 0bcf5307a5e..a763a679407 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -1,7 +1,7 @@ import { expectAssignable, expectError, expectType } from 'tsd' import { IncomingMessage, Server, ServerResponse } from 'node:http' import { Http2Server, Http2ServerRequest, Http2ServerResponse } from 'node:http2' -import fastify, { FastifyInstance, FastifyError, FastifyLoggerInstance, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions, RawServerDefault } from '../../fastify' +import fastify, { FastifyInstance, FastifyError, FastifyBaseLogger, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions, RawServerDefault } from '../../fastify' const testPluginCallback: FastifyPluginCallback = function (instance, opts, done) { } const testPluginAsync: FastifyPluginAsync = async function (instance, opts) { } @@ -97,7 +97,7 @@ type ServerWithTypeProvider = FastifyInstance< Server, IncomingMessage, ServerResponse, - FastifyLoggerInstance, + FastifyBaseLogger, TestTypeProvider > const testPluginWithTypeProvider: FastifyPluginCallback< diff --git a/test/types/reply.test-d.ts b/test/types/reply.test-d.ts index f66e298d6e4..ebad0649581 100644 --- a/test/types/reply.test-d.ts +++ b/test/types/reply.test-d.ts @@ -2,7 +2,7 @@ import { Buffer } from 'node:buffer' import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { FastifyContextConfig, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' import { FastifyInstance } from '../../types/instance' -import { FastifyLoggerInstance } from '../../types/logger' +import { FastifyBaseLogger } from '../../types/logger' import { ResolveReplyTypeWithRouteGeneric } from '../../types/reply' import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' import { ContextConfigDefault, RawReplyDefaultExpression, RawServerDefault } from '../../types/utils' @@ -12,7 +12,7 @@ type DefaultFastifyReplyWithCode = FastifyReply(reply.raw) - expectType(reply.log) + expectType(reply.log) expectType>(reply.request) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.status) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 437112ca8c7..dda0337a2d1 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -15,7 +15,7 @@ import fastify, { SafePromiseLike } from '../../fastify' import { FastifyInstance } from '../../types/instance' -import { FastifyLoggerInstance } from '../../types/logger' +import { FastifyBaseLogger } from '../../types/logger' import { FastifyReply } from '../../types/reply' import { FastifyRequest, RequestRouteOptions } from '../../types/request' import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' @@ -56,7 +56,7 @@ type CustomRequest = FastifyRequest<{ type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' type ExpectedGetValidationFunction = (input: { [key: string]: unknown }) => boolean -interface CustomLoggerInterface extends FastifyLoggerInstance { +interface CustomLoggerInterface extends FastifyBaseLogger { foo: FastifyLogFn; // custom severity logger method } @@ -85,7 +85,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.query) expectType(request.id) - expectType(request.log) + expectType(request.log) expectType(request.socket) expectType(request.validationError) expectType(request.server) diff --git a/types/logger.d.ts b/types/logger.d.ts index c9f64804644..7ed40fef2cf 100644 --- a/types/logger.d.ts +++ b/types/logger.d.ts @@ -28,7 +28,7 @@ export interface FastifyBaseLogger extends Pick Date: Sun, 8 Feb 2026 11:41:06 +0100 Subject: [PATCH 1250/1295] refactor(test-types): replace deprecated FastifyPlugin with FastifyPluginAsync in dummy-plugin (#6472) --- test/types/dummy-plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/types/dummy-plugin.ts b/test/types/dummy-plugin.ts index 72aaef8805c..e3c8210a7b3 100644 --- a/test/types/dummy-plugin.ts +++ b/test/types/dummy-plugin.ts @@ -1,9 +1,9 @@ -import { FastifyPlugin } from '../../fastify' +import { FastifyPluginAsync } from '../../fastify' export interface DummyPluginOptions { foo?: number } -declare const DummyPlugin: FastifyPlugin +declare const DummyPlugin: FastifyPluginAsync export default DummyPlugin From f02eaef672348e9d8f82598042fe5a5fe7e59f7b Mon Sep 17 00:00:00 2001 From: droppingbeans Date: Mon, 9 Feb 2026 03:50:21 -0600 Subject: [PATCH 1251/1295] Fix markdown typo in README.md (#6491) Correct '__Method:__:' to '__Method__:' (closing bold marker should come before colon, not after) Fixes #6490 Co-authored-by: droppingbeans Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2974d4bffc4..bc8007bece6 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ If you learn best by reading code, explore the official [demo](https://github.co __Machine:__ EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD. -__Method:__: `autocannon -c 100 -d 40 -p 10 localhost:3000` * 2, taking the +__Method__: `autocannon -c 100 -d 40 -p 10 localhost:3000` * 2, taking the second average | Framework | Version | Router? | Requests/sec | From 8b3acf25a52f56a17c994f5433fca028f095ada4 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 11 Feb 2026 12:39:09 +0100 Subject: [PATCH 1252/1295] test: cover non-numeric content-length client error path (#6500) --- test/request-error.test.js | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/request-error.test.js b/test/request-error.test.js index fd5a5cdd5d9..817dcd7a23f 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -325,6 +325,47 @@ test('default clientError replies with bad request on reused keep-alive connecti }) }) +test('non-numeric content-length is rejected before Fastify body parsing', (t, done) => { + t.plan(3) + + let response = '' + + const fastify = Fastify({ + bodyLimit: 1, + keepAliveTimeout: 100 + }) + + fastify.post('/', () => { + t.assert.fail('handler should not be called') + }) + + fastify.listen({ port: 0 }, function (err) { + t.assert.ifError(err) + t.after(() => fastify.close()) + + const client = connect(fastify.server.address().port) + + client.on('data', chunk => { + response += chunk.toString('utf-8') + }) + + client.on('end', () => { + t.assert.match(response, /^HTTP\/1.1 400 Bad Request/) + t.assert.match(response, /"message":"Client Error"/) + done() + }) + + client.resume() + client.write('POST / HTTP/1.1\r\n') + client.write('Host: example.com\r\n') + client.write('Content-Type: text/plain\r\n') + client.write('Content-Length: abc\r\n') + client.write('Connection: close\r\n') + client.write('\r\n') + client.write('x'.repeat(32)) + }) +}) + test('request.routeOptions.method is an uppercase string /1', async t => { t.plan(3) const fastify = Fastify() From 2837be64267dca1ed4069111398401feded29f90 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 12 Feb 2026 14:41:33 +0100 Subject: [PATCH 1253/1295] chore: remove tests-checker workflow (#6481) --- .github/tests_checker.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .github/tests_checker.yml diff --git a/.github/tests_checker.yml b/.github/tests_checker.yml deleted file mode 100644 index 7d96d55bf97..00000000000 --- a/.github/tests_checker.yml +++ /dev/null @@ -1,9 +0,0 @@ -comment: | - Hello! Thank you for contributing! - It appears that you have changed the framework code, but the tests that verify your change are missing. Could you please add them? - -fileExtensions: - - '.ts' - - '.js' - -testDir: 'test' From cf1fac48ce8bf5ac2543b7dab3ac4abea2072f66 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 14 Feb 2026 00:27:41 +0100 Subject: [PATCH 1254/1295] ci: remove stale.yml file (#6504) --- .github/stale.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 5130bd817b1..00000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 15 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - "discussion" - - "feature request" - - "bug" - - "help wanted" - - "plugin suggestion" - - "good first issue" - - "never stale" -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false From 7c11089bc369c17752361ec0bc9d7f06905b2799 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Fri, 13 Feb 2026 23:35:26 +0000 Subject: [PATCH 1255/1295] docs(security): remove hackerone references; change note style (#6501) --- SECURITY.md | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 0e6ad2b8f15..c94da1a35ea 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -46,11 +46,11 @@ explicitly enabled via configuration options ## Reporting vulnerabilities Individuals who find potential vulnerabilities in Fastify are invited to -complete a vulnerability report via the GitHub Security page: +complete a vulnerability report via the +[GitHub Security page](https://github.com/fastify/fastify/security/advisories/new). -https://github.com/fastify/fastify/security/advisories/new - -Note: Our [HackerOne](https://hackerone.com/fastify) program is now closed. +> ℹ️ Note: +> Fastify's [HackerOne](https://hackerone.com/fastify) program is now closed. ### Strict measures when reporting vulnerabilities @@ -61,7 +61,7 @@ reported vulnerabilities: * Avoid creating new "informative" reports. Only create new reports on a vulnerability if you are absolutely sure this should be tagged as an actual vulnerability. Third-party vendors and individuals are - tracking any new vulnerabilities reported in HackerOne or GitHub and will flag + tracking any new vulnerabilities reported on GitHub and will flag them as such for their customers (think about snyk, npm audit, ...). * Security reports should never be created and triaged by the same person. If you are creating a report for a vulnerability that you found, or on @@ -106,9 +106,6 @@ Triaging should include updating issue fields: * Asset - set/create the module affected by the report * Severity - TBD, currently left empty -Reference: [HackerOne: Submitting -Reports](https://docs.hackerone.com/hackers/submitting-reports.html) - ### Correction follow-up **Delay:** 90 days @@ -136,20 +133,14 @@ The report's vulnerable versions upper limit should be set to: Within 90 days after the triage date, the vulnerability must be made public. **Severity**: Vulnerability severity is assessed using [CVSS -v.3](https://www.first.org/cvss/user-guide). More information can be found on -[HackerOne documentation](https://docs.hackerone.com/hackers/severity.html) +v.3](https://www.first.org/cvss/user-guide). If the package maintainer is actively developing a patch, an additional delay can be added with the approval of the security team and the individual who reported the vulnerability. -At this point, a CVE should be requested through the selected platform through -the UI, which should include the Report ID and a summary. - -Within HackerOne, this is handled through a "public disclosure request". - -Reference: [HackerOne: -Disclosure](https://docs.hackerone.com/hackers/disclosure.html) +At this point, a CVE should be requested via GitHub Security Advisories using +the web UI, and the request should include the Report ID and a summary. ### Secondary Contact From 33e670500607f73f25724eaeb4788166f8e1d121 Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sun, 15 Feb 2026 11:03:36 +0100 Subject: [PATCH 1256/1295] chore: rename @sinclair/typebox to typebox (#6494) --- docs/Reference/Type-Providers.md | 10 +++++----- docs/Reference/TypeScript.md | 6 +++--- package.json | 2 +- test/types/type-provider.test-d.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md index fbc6e6dfc7c..566442bbe9b 100644 --- a/docs/Reference/Type-Providers.md +++ b/docs/Reference/Type-Providers.md @@ -63,13 +63,13 @@ server.get('/route', { The following sets up a TypeBox Type Provider: ```bash -$ npm i @fastify/type-provider-typebox +$ npm i typebox @fastify/type-provider-typebox ``` ```typescript import fastify from 'fastify' import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox' -import { Type } from '@sinclair/typebox' +import { Type } from 'typebox' const server = fastify().withTypeProvider() @@ -109,7 +109,7 @@ Example: import Fastify from 'fastify' import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox' import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' -import { Type } from '@sinclair/typebox' +import { Type } from 'typebox' const fastify = Fastify() @@ -159,7 +159,7 @@ with several scopes, as shown below: ```ts import Fastify from 'fastify' import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox' -import { Type } from '@sinclair/typebox' +import { Type } from 'typebox' const server = Fastify().withTypeProvider() @@ -221,7 +221,7 @@ server.listen({ port: 3000 }) ```ts // routes.ts -import { Type } from '@sinclair/typebox' +import { Type } from 'typebox' import { FastifyInstance, FastifyBaseLogger, diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 13ca957ba80..5e2aaef91c0 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -217,7 +217,7 @@ providers. #### TypeBox -A useful library for building types and a schema at once is [TypeBox](https://www.npmjs.com/package/@sinclair/typebox). +A useful library for building types and a schema at once is [TypeBox](https://www.npmjs.com/package/typebox). With TypeBox you define your schema within your code and use them directly as types or schemas as you need them. @@ -227,14 +227,14 @@ can do it as follows: 1. Install `typebox` in your project. ```bash - npm i @sinclair/typebox + npm i typebox ``` 2. Define the schema you need with `Type` and create the respective type with `Static`. ```typescript - import { Static, Type } from '@sinclair/typebox' + import { Static, Type } from 'typebox' export const User = Type.Object({ name: Type.String(), diff --git a/package.json b/package.json index ceb8d404cce..851be2d5169 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,6 @@ ], "devDependencies": { "@jsumners/line-reporter": "^1.0.1", - "@sinclair/typebox": "^0.34.13", "@sinonjs/fake-timers": "^11.2.2", "@stylistic/eslint-plugin": "^5.1.0", "@stylistic/eslint-plugin-js": "^4.1.0", @@ -197,6 +196,7 @@ "proxyquire": "^2.1.3", "split2": "^4.2.0", "tsd": "^0.33.0", + "typebox": "^1.0.81", "typescript": "~5.9.2", "undici": "^7.11.0", "vary": "^1.1.2", diff --git a/test/types/type-provider.test-d.ts b/test/types/type-provider.test-d.ts index bff7b944d17..6cf04359225 100644 --- a/test/types/type-provider.test-d.ts +++ b/test/types/type-provider.test-d.ts @@ -9,7 +9,7 @@ import fastify, { } from '../../fastify' import { expectAssignable, expectError, expectType } from 'tsd' import { IncomingHttpHeaders } from 'node:http' -import { Type, TSchema, Static } from '@sinclair/typebox' +import { Type, TSchema, Static } from 'typebox' import { FromSchema, JSONSchema } from 'json-schema-to-ts' const server = fastify() From 1bcba2c0b450426f4baf85fd3aa800dcbd2a652d Mon Sep 17 00:00:00 2001 From: Umar Gora Date: Sun, 15 Feb 2026 10:15:44 +0000 Subject: [PATCH 1257/1295] ci(links-check): add external link checker using linkinator-action (#6386) --- .github/workflows/links-check.yml | 13 +++++++++++-- docs/Guides/Contributing.md | 2 +- docs/Guides/Serverless.md | 2 +- docs/Reference/LTS.md | 2 +- docs/Reference/Logging.md | 2 +- docs/Reference/Routes.md | 4 ++-- docs/Reference/Server.md | 2 +- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index 9b976c2b09b..b7cf6265f2d 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -4,7 +4,8 @@ on: pull_request: paths: - 'docs/**' - - '*.md' + - '**/*.md' + - '.github/workflows/links-check.yml' permissions: contents: read @@ -24,7 +25,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2.7.0 + uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # 2.7.0 with: fail: true # As external links behavior is not predictable, we check only internal links @@ -33,3 +34,11 @@ jobs: args: --offline --verbose --no-progress './**/*.md' env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: External Link Checker + uses: JustinBeckwith/linkinator-action@f62ba0c110a76effb2ee6022cc6ce4ab161085e3 # 2.4.0 + with: + paths: "*.md **/*.md" + retry: true + redirects: error + linksToSkip: "https://github.com/orgs/fastify/.*" diff --git a/docs/Guides/Contributing.md b/docs/Guides/Contributing.md index 081823ff615..afea9ace780 100644 --- a/docs/Guides/Contributing.md +++ b/docs/Guides/Contributing.md @@ -117,7 +117,7 @@ mkdir -p /Applications/VSCodeFastify/code-portable-data/{user-data,extensions} Before continuing, we need to add the `code` command to your terminal's `PATH`. To do so, we will [manually add VSCode to the -`PATH`](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line). +`PATH`](https://code.visualstudio.com/docs/setup/mac#_launch-vs-code-from-the-command-line). As outlined in that document, the instructions vary depending on your default shell, so you should follow the instructions in that guide as relates to your preferred shell. However, we will tweak them slightly by defining an alias diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md index 9302dbef348..f6e3428cf65 100644 --- a/docs/Guides/Serverless.md +++ b/docs/Guides/Serverless.md @@ -601,4 +601,4 @@ https://vercel.com/templates/backend/fastify-on-vercel). [Fluid compute](https://vercel.com/docs/functions/fluid-compute) currently requires an explicit opt-in. Learn more about enabling Fluid compute [here]( -https://vercel.com/docs/functions/fluid-compute#how-to-enable-fluid-compute). \ No newline at end of file +https://vercel.com/docs/fluid-compute#enabling-fluid-compute). \ No newline at end of file diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index f163543f7e8..70984581c63 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -67,7 +67,7 @@ For more information, see their [Never Ending Support][hd-link] service. Fastify uses GitHub Actions for CI testing, please refer to [GitHub's documentation regarding workflow -runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources) +runners](https://docs.github.com/en/actions/reference/runners/github-hosted-runners#supported-runners-and-hardware-resources) for further details on what the latest virtual environment is in relation to the YAML workflow labels below: diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md index 32420e86fe5..94b7722bb74 100644 --- a/docs/Reference/Logging.md +++ b/docs/Reference/Logging.md @@ -65,7 +65,7 @@ fastify.log.info('Something important happened!'); #### Passing Logger Options To pass options to the logger, provide them to Fastify. See the -[Pino documentation](https://github.com/pinojs/pino/blob/master/docs/api.md#options) +[Pino documentation](https://github.com/pinojs/pino/blob/main/docs/api.md#options) for available options. To specify a file destination, use: ```js diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 6c1dd98c91b..f677cf6a887 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -507,7 +507,7 @@ See the `prefixTrailingSlash` route option above to change this behavior. Different log levels can be set for routes in Fastify by passing the `logLevel` option to the plugin or route with the desired -[value](https://github.com/pinojs/pino/blob/master/docs/api.md#level-string). +[value](https://github.com/pinojs/pino/blob/main/docs/api.md#level-string). Be aware that setting `logLevel` at the plugin level also affects [`setNotFoundHandler`](./Server.md#setnotfoundhandler) and @@ -536,7 +536,7 @@ Fastify Logger, accessible with `fastify.log`.* In some contexts, logging a large object may waste resources. Define custom -[`serializers`](https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object) +[`serializers`](https://github.com/pinojs/pino/blob/main/docs/api.md#serializers-object) and attach them in the appropriate context. ```js diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index f6b59ceb52a..fe70d5c1738 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1393,7 +1393,7 @@ different ways to define a name (in order). Example: `pluginFn[Symbol.for('fastify.display-name')] = "Custom Name"` 3. If you `module.exports` a plugin the filename is used. 4. If you use a regular [function - declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Defining_functions) + declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#defining_functions) the function name is used. *Fallback*: The first two lines of your plugin will represent the plugin name. From 37c708858c34c3bb6c9e26ebe261464b2c4e934b Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Thu, 19 Feb 2026 07:49:53 +0100 Subject: [PATCH 1258/1295] chore: upgrade borp to v1.0.0 (#6510) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 851be2d5169..8bf904df256 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "autocannon": "^8.0.0", - "borp": "^0.21.0", + "borp": "^1.0.0", "branch-comparer": "^1.1.0", "concurrently": "^9.1.2", "cross-env": "^10.0.0", From 3653e7b92719061c6c2dfb4497d2d5b936509309 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 20 Feb 2026 16:58:09 +0100 Subject: [PATCH 1259/1295] docs: Add OpenJS CNA reference to SECURITY.md (#6516) Signed-off-by: Matteo Collina --- SECURITY.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index c94da1a35ea..fb011bd3a1a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -49,6 +49,11 @@ Individuals who find potential vulnerabilities in Fastify are invited to complete a vulnerability report via the [GitHub Security page](https://github.com/fastify/fastify/security/advisories/new). +Do not assign or request a CVE directly. +CVE assignment is handled by the Fastify Security Team. +Fastify falls under the [OpenJS CNA](https://cna.openjsf.org/). +A CVE will be assigned as part of our responsible disclosure process. + > ℹ️ Note: > Fastify's [HackerOne](https://hackerone.com/fastify) program is now closed. @@ -139,15 +144,12 @@ If the package maintainer is actively developing a patch, an additional delay can be added with the approval of the security team and the individual who reported the vulnerability. -At this point, a CVE should be requested via GitHub Security Advisories using -the web UI, and the request should include the Report ID and a summary. - ### Secondary Contact If you do not receive an acknowledgment of your report within 6 business days, or if you cannot find a private security contact for the project, you may -contact the OpenJS Foundation CNA at `security@lists.openjsf.org` for -assistance. +contact the OpenJS Foundation CNA at (or +`security@lists.openjsf.org`) for assistance. The CNA can help ensure your report is properly acknowledged, assist with coordinating disclosure timelines, and assign CVEs when necessary. This is a From b4db85b665ac99a809c633b7564d49c8a4c2c5c9 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 20 Feb 2026 23:08:12 +0100 Subject: [PATCH 1260/1295] fix: avoid mutating shared routerOptions across instances (#6515) --- lib/route.js | 4 +++- test/router-options.test.js | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/route.js b/lib/route.js index 9eb46f0b343..80c4294522c 100644 --- a/lib/route.js +++ b/lib/route.js @@ -614,7 +614,9 @@ function runPreParsing (err, request, reply) { } function buildRouterOptions (options, defaultOptions) { - const routerOptions = options.routerOptions || Object.create(null) + const routerOptions = options.routerOptions == null + ? Object.create(null) + : Object.assign(Object.create(null), options.routerOptions) const usedDeprecatedOptions = routerKeys.filter(key => Object.hasOwn(options, key)) diff --git a/test/router-options.test.js b/test/router-options.test.js index cdb9d3898ce..0da3ec4193e 100644 --- a/test/router-options.test.js +++ b/test/router-options.test.js @@ -1059,8 +1059,50 @@ test('Should support extra find-my-way options', async t => { } }) + t.after(() => fastify.close()) + await fastify.ready() // Ensure the option is preserved after validation t.assert.strictEqual(typeof fastify.initialConfig.routerOptions.buildPrettyMeta, 'function') }) + +test('Should allow reusing a routerOptions object across instances', async t => { + t.plan(1) + + const options = { + routerOptions: { + maxParamLength: 2048 + } + } + + const app1 = Fastify(options) + const app2 = Fastify(options) + + t.after(() => Promise.all([ + app1.close(), + app2.close() + ])) + + const response = await app2.inject('/not-found') + t.assert.strictEqual(response.statusCode, 404) +}) + +test('Should not mutate user-provided routerOptions object', async t => { + t.plan(4) + + const routerOptions = { + maxParamLength: 2048 + } + const options = { routerOptions } + + const app = Fastify(options) + t.after(() => app.close()) + + await app.ready() + + t.assert.deepStrictEqual(Object.keys(routerOptions), ['maxParamLength']) + t.assert.strictEqual(routerOptions.maxParamLength, 2048) + t.assert.strictEqual(routerOptions.defaultRoute, undefined) + t.assert.strictEqual(routerOptions.onBadUrl, undefined) +}) From c4a6e78e5d7727b0d4323be75623f7815869b5da Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 20 Feb 2026 23:08:27 +0100 Subject: [PATCH 1261/1295] fix(types): accept async route hooks in shorthand options (#6514) --- test/types/route.test-d.ts | 6 +++++ types/route.d.ts | 55 +++++++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index e60dfb9ac23..e1acdeb1240 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -53,6 +53,12 @@ const routeHandlerWithReturnValue: RouteHandlerMethod = function (request, reply return reply.send() } +const asyncPreHandler = async (request: FastifyRequest) => { + expectType(request) +} + +fastify().get('/', { preHandler: asyncPreHandler }, async () => 'this is an example') + fastify().get( '/', { config: { foo: 'bar', bar: 100, includeMessage: true } }, diff --git a/types/route.d.ts b/types/route.d.ts index 463185647b7..ed942c692d8 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -1,7 +1,18 @@ import { FastifyError } from '@fastify/error' import { ConstraintStrategy } from 'find-my-way' import { FastifyContextConfig } from './context' -import { onErrorMetaHookHandler, onRequestAbortMetaHookHandler, onRequestMetaHookHandler, onResponseMetaHookHandler, onSendMetaHookHandler, onTimeoutMetaHookHandler, preHandlerMetaHookHandler, preParsingMetaHookHandler, preSerializationMetaHookHandler, preValidationMetaHookHandler } from './hooks' +import { + onErrorHookHandler, + onRequestAbortHookHandler, + onRequestHookHandler, + onResponseHookHandler, + onSendHookHandler, + onTimeoutHookHandler, + preHandlerHookHandler, + preParsingHookHandler, + preSerializationHookHandler, + preValidationHookHandler +} from './hooks' import { FastifyInstance } from './instance' import { FastifyBaseLogger, FastifyChildLoggerFactory, LogLevel } from './logger' import { FastifyReply, ReplyGenericInterface } from './reply' @@ -34,6 +45,8 @@ export interface RouteConstraint { /** * Route shorthand options for the various shorthand methods */ +type RouteShorthandHook any> = (...args: Parameters) => void | Promise + export interface RouteShorthandOptions< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, @@ -65,26 +78,26 @@ export interface RouteShorthandOptions< schemaErrorFormatter?: SchemaErrorFormatter; // hooks - onRequest?: onRequestMetaHookHandler, TypeProvider, Logger> - | onRequestMetaHookHandler, TypeProvider, Logger>[]; - preParsing?: preParsingMetaHookHandler, TypeProvider, Logger> - | preParsingMetaHookHandler, TypeProvider, Logger>[]; - preValidation?: preValidationMetaHookHandler, TypeProvider, Logger> - | preValidationMetaHookHandler, TypeProvider, Logger>[]; - preHandler?: preHandlerMetaHookHandler, TypeProvider, Logger> - | preHandlerMetaHookHandler, TypeProvider, Logger>[]; - preSerialization?: preSerializationMetaHookHandler, TypeProvider, Logger> - | preSerializationMetaHookHandler, TypeProvider, Logger>[]; - onSend?: onSendMetaHookHandler, TypeProvider, Logger> - | onSendMetaHookHandler, TypeProvider, Logger>[]; - onResponse?: onResponseMetaHookHandler, TypeProvider, Logger> - | onResponseMetaHookHandler, TypeProvider, Logger>[]; - onTimeout?: onTimeoutMetaHookHandler, TypeProvider, Logger> - | onTimeoutMetaHookHandler, TypeProvider, Logger>[]; - onError?: onErrorMetaHookHandler, TypeProvider, Logger> - | onErrorMetaHookHandler, TypeProvider, Logger>[]; - onRequestAbort?: onRequestAbortMetaHookHandler, TypeProvider, Logger> - | onRequestAbortMetaHookHandler, TypeProvider, Logger>[]; + onRequest?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + preParsing?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + preValidation?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + preHandler?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + preSerialization?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onSend?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onResponse?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onTimeout?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onError?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onRequestAbort?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; } /** * Route handler method declaration. From 53eea2c6c391b328bb0464afe6aae023114c81fb Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Sun, 22 Feb 2026 11:38:47 +0200 Subject: [PATCH 1262/1295] docs: Improve shutdown lifecycle documentation (#6517) --- docs/Reference/Hooks.md | 58 ++++++++++++++++++++++++++++++++----- docs/Reference/Lifecycle.md | 12 +++++++- docs/Reference/Server.md | 57 ++++++++++++++++++++++++++++++++++-- 3 files changed, 116 insertions(+), 11 deletions(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 107b32c5b99..9e8201e59d8 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -465,10 +465,12 @@ fastify.addHook('onListen', async function () { ### onClose -Triggered when `fastify.close()` is invoked to stop the server, after all in-flight -HTTP requests have been completed. -It is useful when [plugins](./Plugins.md) need a "shutdown" event, for example, -to close an open connection to a database. +Triggered when `fastify.close()` is invoked to stop the server. By the time +`onClose` hooks execute, the HTTP server has already stopped listening, all +in-flight HTTP requests have been completed, and connections have been drained. +This makes `onClose` the safe place for [plugins](./Plugins.md) to release +resources such as database connection pools, as no new requests will +arrive. The hook function takes the Fastify instance as a first argument, and a `done` callback for synchronous hook functions. @@ -486,13 +488,41 @@ fastify.addHook('onClose', async (instance) => { }) ``` +#### Execution order + +When multiple `onClose` hooks are registered across plugins, child-plugin hooks +execute before parent-plugin hooks. This means a database plugin's `onClose` +hook will run before the root-level `onClose` hooks: + +```js +fastify.register(function dbPlugin (instance, opts, done) { + instance.addHook('onClose', async (instance) => { + // Runs first — close the database pool + await instance.db.close() + }) + done() +}) + +fastify.addHook('onClose', async (instance) => { + // Runs second — after child plugins have cleaned up +}) +``` + +See [`close`](./Server.md#close) for the full shutdown lifecycle. + ### preClose -Triggered when `fastify.close()` is invoked to stop the server, before all in-flight -HTTP requests have been completed. -It is useful when [plugins](./Plugins.md) have set up some state attached -to the HTTP server that would prevent the server to close. +Triggered when `fastify.close()` is invoked to stop the server. At this +point the server is already rejecting new requests with `503` (when +[`return503OnClosing`](./Server.md#factory-return-503-on-closing) is `true`), +but the HTTP server has not yet stopped listening and in-flight requests are +still being processed. + +It is useful when [plugins](./Plugins.md) have set up state attached to the HTTP +server that would prevent the server from closing, such as open WebSocket +connections or Server-Sent Events streams that must be explicitly terminated for +`server.close()` to complete. _It is unlikely you will need to use this hook_, use the [`onClose`](#onclose) for the most common case. @@ -510,6 +540,18 @@ fastify.addHook('preClose', async () => { }) ``` +For example, closing WebSocket connections during shutdown: + +```js +fastify.addHook('preClose', async () => { + // Close all WebSocket connections so that server.close() can complete. + // Without this, open connections would keep the server alive. + for (const ws of activeWebSockets) { + ws.close(1001, 'Server shutting down') + } +}) +``` + ### onRoute diff --git a/docs/Reference/Lifecycle.md b/docs/Reference/Lifecycle.md index a1b130b73c0..e245635c1a9 100644 --- a/docs/Reference/Lifecycle.md +++ b/docs/Reference/Lifecycle.md @@ -81,4 +81,14 @@ submitted, the data flow is as follows: - The [reply serializer](./Server.md#setreplyserializer) if set - The [serializer compiler](./Server.md#setserializercompiler) if a JSON schema is set for the HTTP status code -- The default `JSON.stringify` function \ No newline at end of file +- The default `JSON.stringify` function + +## Shutdown Lifecycle + + +When [`fastify.close()`](./Server.md#close) is called, the server goes through a +graceful shutdown sequence involving +[`preClose`](./Hooks.md#pre-close) hooks, connection draining, and +[`onClose`](./Hooks.md#on-close) hooks. See the +[`close`](./Server.md#close) method documentation for the full step-by-step +lifecycle. \ No newline at end of file diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index fe70d5c1738..5a4b864d552 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -567,8 +567,14 @@ define it before the `GET` route. + Default: `true` -Returns 503 after calling `close` server method. If `false`, the server routes -the incoming request as usual. +When `true`, any request arriving after [`close`](#close) has been called will +receive a `503 Service Unavailable` response with `Connection: close` header +(HTTP/1.1). This lets load balancers detect that the server is shutting down and +stop routing traffic to it. + +When `false`, requests arriving during the closing phase are routed and +processed normally. They will still receive a `Connection: close` header so that +clients do not attempt to reuse the connection. ### `ajv` @@ -1333,6 +1339,53 @@ fastify.close().then(() => { }) ``` +##### Shutdown lifecycle + +When `fastify.close()` is called, the following steps happen in order: + +1. The server is flagged as **closing**. New incoming requests receive a + `Connection: close` header (HTTP/1.1) and are handled according to + [`return503OnClosing`](#factory-return-503-on-closing). +2. [`preClose`](./Hooks.md#pre-close) hooks execute. The server is still + processing in-flight requests at this point. +3. **Connection draining** based on the + [`forceCloseConnections`](#forcecloseconnections) option: + - `"idle"` — idle keep-alive connections are closed; in-flight requests + continue. + - `true` — all persistent connections are destroyed immediately. + - `false` — no forced closure; idle connections remain open until they time + out naturally (see [`keepAliveTimeout`](#keepalivetimeout)). +4. The HTTP server **stops accepting** new TCP connections + (`server.close()`). Node.js waits for all in-flight requests to complete + before invoking the callback. +5. [`onClose`](./Hooks.md#on-close) hooks execute. All in-flight requests have + completed and the server is no longer listening. +6. The `close` callback (or the returned Promise) resolves. + +``` +fastify.close() called + │ + ├─▶ closing = true (new requests receive 503) + │ + ├─▶ preClose hooks + │ (in-flight requests still active) + │ + ├─▶ Connection draining (forceCloseConnections) + │ + ├─▶ server.close() + │ (waits for in-flight requests to complete) + │ + ├─▶ onClose hooks + │ (server stopped, all requests done) + │ + └─▶ close callback / Promise resolves +``` + +> ℹ️ Note: +> Upgraded connections (such as WebSocket) are not tracked by the HTTP +> server and will prevent `server.close()` from completing. Close them +> explicitly in a [`preClose`](./Hooks.md#pre-close) hook. + #### decorate* From c970ed48d3e436f392d08d9e379f632409472743 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 22 Feb 2026 22:48:53 +0200 Subject: [PATCH 1263/1295] chore: remove unused `tsconfig.eslint.json` (#6524) --- types/tsconfig.eslint.json | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 types/tsconfig.eslint.json diff --git a/types/tsconfig.eslint.json b/types/tsconfig.eslint.json deleted file mode 100644 index f499b804d1f..00000000000 --- a/types/tsconfig.eslint.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "lib": [ "es2015" ], - "module": "commonjs", - "noEmit": true, - "strict": true - }, - "include": [ - "../test/types/**/*.test-d.ts", - "./**/*.d.ts" - ] -} From f376f6082b5ddfe5ea96fdd3d98a66377b6bb4c2 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Thu, 26 Feb 2026 00:55:39 +0200 Subject: [PATCH 1264/1295] feat: First-class support for handler-level timeouts (#6521) --- build/build-validation.js | 2 + docs/Reference/Errors.md | 5 + docs/Reference/Hooks.md | 5 + docs/Reference/Lifecycle.md | 5 + docs/Reference/Request.md | 10 + docs/Reference/Routes.md | 5 + docs/Reference/Server.md | 66 +++ fastify.d.ts | 1 + fastify.js | 4 +- lib/config-validator.js | 620 ++++++++++++++------------ lib/context.js | 7 +- lib/errors.js | 11 + lib/reply.js | 24 +- lib/request.js | 19 +- lib/route.js | 45 +- lib/symbols.js | 4 + test/handler-timeout.test.js | 367 +++++++++++++++ test/internals/errors.test.js | 2 +- test/internals/initial-config.test.js | 2 + test/types/fastify.test-d.ts | 1 + test/types/request.test-d.ts | 1 + types/errors.d.ts | 2 + types/request.d.ts | 2 + types/route.d.ts | 1 + 24 files changed, 906 insertions(+), 305 deletions(-) create mode 100644 test/handler-timeout.test.js diff --git a/build/build-validation.js b/build/build-validation.js index c252478a396..d20213a79bc 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -28,6 +28,7 @@ const defaultInitOptions = { forceCloseConnections: undefined, // keep-alive connections maxRequestsPerSocket: 0, // no limit requestTimeout: 0, // no limit + handlerTimeout: 0, // no timeout (disabled by default) bodyLimit: 1024 * 1024, // 1 MiB caseSensitive: true, allowUnsafeRegex: false, @@ -72,6 +73,7 @@ const schema = { }, maxRequestsPerSocket: { type: 'integer', default: defaultInitOptions.maxRequestsPerSocket, nullable: true }, requestTimeout: { type: 'integer', default: defaultInitOptions.requestTimeout }, + handlerTimeout: { type: 'integer', default: defaultInitOptions.handlerTimeout }, bodyLimit: { type: 'integer', default: defaultInitOptions.bodyLimit }, caseSensitive: { type: 'boolean', default: defaultInitOptions.caseSensitive }, allowUnsafeRegex: { type: 'boolean', default: defaultInitOptions.allowUnsafeRegex }, diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md index 9d592277686..f1e66374752 100644 --- a/docs/Reference/Errors.md +++ b/docs/Reference/Errors.md @@ -84,6 +84,9 @@ - [FST_ERR_ROUTE_METHOD_NOT_SUPPORTED](#fst_err_route_method_not_supported) - [FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED](#fst_err_route_body_validation_schema_not_supported) - [FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT](#fst_err_route_body_limit_option_not_int) + - [FST_ERR_HANDLER_TIMEOUT](#fst_err_handler_timeout) + + - [FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT](#fst_err_route_handler_timeout_option_not_int) - [FST_ERR_ROUTE_REWRITE_NOT_STR](#fst_err_route_rewrite_not_str) - [FST_ERR_REOPENED_CLOSE_SERVER](#fst_err_reopened_close_server) - [FST_ERR_REOPENED_SERVER](#fst_err_reopened_server) @@ -356,6 +359,8 @@ Below is a table with all the error codes used by Fastify. | FST_ERR_ROUTE_METHOD_NOT_SUPPORTED | Method is not supported for the route. | Use a supported method. | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED | Body validation schema route is not supported. | Use a different different method for the route. | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT | `bodyLimit` option must be an integer. | Use an integer for the `bodyLimit` option. | [#4554](https://github.com/fastify/fastify/pull/4554) | +| FST_ERR_HANDLER_TIMEOUT | Request timed out. | Increase the `handlerTimeout` option or optimize the handler. | - | +| FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT | `handlerTimeout` option must be a positive integer. | Use a positive integer for the `handlerTimeout` option. | - | | FST_ERR_ROUTE_REWRITE_NOT_STR | `rewriteUrl` needs to be of type `string`. | Use a string for the `rewriteUrl`. | [#4554](https://github.com/fastify/fastify/pull/4554) | | FST_ERR_REOPENED_CLOSE_SERVER | Fastify has already been closed and cannot be reopened. | - | [#2415](https://github.com/fastify/fastify/pull/2415) | | FST_ERR_REOPENED_SERVER | Fastify is already listening. | - | [#2415](https://github.com/fastify/fastify/pull/2415) | diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 9e8201e59d8..24ecee84cd3 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -284,6 +284,11 @@ service (if the `connectionTimeout` property is set on the Fastify instance). The `onTimeout` hook is executed when a request is timed out and the HTTP socket has been hung up. Therefore, you will not be able to send data to the client. +> Note: The `onTimeout` hook is triggered by socket-level timeouts set via +> `connectionTimeout`. For application-level per-route timeouts, see the +> [`handlerTimeout`](./Server.md#factory-handler-timeout) option which uses +> `request.signal` for cooperative cancellation. + ### onRequestAbort ```js diff --git a/docs/Reference/Lifecycle.md b/docs/Reference/Lifecycle.md index e245635c1a9..1202759f46c 100644 --- a/docs/Reference/Lifecycle.md +++ b/docs/Reference/Lifecycle.md @@ -41,6 +41,11 @@ Incoming Request └─▶ onResponse Hook ``` +When `handlerTimeout` is configured, a timer starts after routing. If the +response is not sent within the allowed time, `request.signal` is aborted and +a 503 error is sent. The timer is cleared when the response finishes or when +`reply.hijack()` is called. See [`handlerTimeout`](./Server.md#factory-handler-timeout). + Before or during the `User Handler`, `reply.hijack()` can be called to: - Prevent Fastify from running subsequent hooks and the user handler - Prevent Fastify from sending the response automatically diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 96e9b3db79e..4931853d3cb 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -40,12 +40,22 @@ Request is a core Fastify object containing the following fields: case of internal re-routing. - `is404` - `true` if request is being handled by 404 handler, `false` otherwise. - `socket` - The underlying connection of the incoming request. +- `signal` - An `AbortSignal` that aborts when the handler timeout + fires or the client disconnects. Created lazily on first access, so + there is zero overhead when not used. When + [`handlerTimeout`](./Server.md#factory-handler-timeout) is configured, + the signal is pre-created and also aborts on timeout. Pass it to + `fetch()`, database queries, or any API accepting a `signal` option + for cooperative cancellation. On timeout, `signal.reason` is the + `FST_ERR_HANDLER_TIMEOUT` error; on client disconnect it is a generic + `AbortError`. Check `signal.reason.code` to distinguish the two cases. - `context` - Deprecated, use `request.routeOptions.config` instead. A Fastify internal object. Do not use or modify it directly. It is useful to access one special key: - `context.config` - The route [`config`](./Routes.md#routes-config) object. - `routeOptions` - The route [`option`](./Routes.md#routes-options) object. - `bodyLimit` - Either server limit or route limit. + - `handlerTimeout` - The handler timeout configured for this route. - `config` - The [`config`](./Routes.md#routes-config) object for this route. - `method` - The HTTP method for the route. - `url` - The path of the URL to match this route. diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index f677cf6a887..298ea245f16 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -115,6 +115,11 @@ fastify.route(options) larger than this number of bytes. Must be an integer. You may also set this option globally when first creating the Fastify instance with `fastify(options)`. Defaults to `1048576` (1 MiB). +* `handlerTimeout`: maximum number of milliseconds for the route's full + lifecycle. Overrides the server-level + [`handlerTimeout`](./Server.md#factory-handler-timeout). Must be a positive + integer. When the timeout fires, `request.signal` is aborted and a 503 error + is sent through the error handler (which can be customized per-route). * `logLevel`: set log level for this route. See below. * `logSerializers`: set serializers to log for this route. * `config`: object used to store custom configuration. diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 5a4b864d552..28a7e6873f9 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -224,6 +224,71 @@ in front. > ℹ️ Note: > At the time of writing, only node >= v14.11.0 supports this option +### `handlerTimeout` + + ++ Default: `0` (no timeout) + +Defines the maximum number of milliseconds allowed for processing a request +through the entire route lifecycle (from routing through onRequest, parsing, +validation, handler execution, and serialization). If the response is not sent +within this time, a `503 Service Unavailable` error is returned and +`request.signal` is aborted. + +Unlike `connectionTimeout` and `requestTimeout` (which operate at the socket +level), `handlerTimeout` is an application-level timeout that works correctly +with HTTP keep-alive connections. It can be overridden per-route via +[route options](./Routes.md#routes-options). When set at both levels, the +route-level value takes precedence. Routes without an explicit `handlerTimeout` +inherit the server default. Once a server-level timeout is set, individual +routes cannot opt out of it — they can only override it with a different +positive integer. + +The timeout is **cooperative**: when it fires, Fastify sends the 503 error +response, but the handler's async work continues to run. Use +[`request.signal`](./Request.md) to detect cancellation and stop ongoing work +(database queries, HTTP requests, etc.). APIs that accept a `signal` option +(`fetch()`, database drivers, `stream.pipeline()`) will cancel automatically. + +The timeout error (`FST_ERR_HANDLER_TIMEOUT`) is sent through the route's +[error handler](./Routes.md#routes-options), which can be customized per-route +to change the status code or response body. + +When `reply.hijack()` is called, the timeout timer is cleared — the handler +takes full responsibility for the response lifecycle. + +> ℹ️ Note: +> `handlerTimeout` does not apply to 404 handlers or custom not-found handlers +> set via `setNotFoundHandler()`, as they bypass the route handler lifecycle. + +```js +const fastify = require('fastify')({ + handlerTimeout: 10000 // 10s default for all routes +}) + +// Override per-route +fastify.get('/slow-report', { handlerTimeout: 120000 }, async (request) => { + // Use request.signal for cooperative cancellation + const data = await db.query(longQuery, { signal: request.signal }) + return data +}) + +// Customize the timeout response +fastify.get('/custom-timeout', { + handlerTimeout: 5000, + errorHandler: (error, request, reply) => { + if (error.code === 'FST_ERR_HANDLER_TIMEOUT') { + reply.code(504).send({ error: 'Gateway Timeout' }) + } else { + reply.send(error) + } + } +}, async (request) => { + const result = await externalService.call({ signal: request.signal }) + return result +}) +``` + ### `bodyLimit` @@ -2228,6 +2293,7 @@ initial options passed down by the user to the Fastify instance. The properties that can currently be exposed are: - connectionTimeout - keepAliveTimeout +- handlerTimeout - bodyLimit - caseSensitive - http2 diff --git a/fastify.d.ts b/fastify.d.ts index 548772091ae..6e74460a50f 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -119,6 +119,7 @@ declare namespace fastify { requestTimeout?: number, pluginTimeout?: number, bodyLimit?: number, + handlerTimeout?: number, maxParamLength?: number, disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean), exposeHeadRoutes?: boolean, diff --git a/fastify.js b/fastify.js index 995b8636adc..67f6c9f90fd 100644 --- a/fastify.js +++ b/fastify.js @@ -32,7 +32,8 @@ const { kKeepAliveConnections, kChildLoggerFactory, kGenReqId, - kErrorHandlerAlreadySet + kErrorHandlerAlreadySet, + kHandlerTimeout } = require('./lib/symbols.js') const { createServer } = require('./lib/server') @@ -147,6 +148,7 @@ function fastify (serverOptions) { [kChildren]: [], [kServerBindings]: [], [kBodyLimit]: options.bodyLimit, + [kHandlerTimeout]: options.handlerTimeout, [kRoutePrefix]: '', [kLogLevel]: '', [kLogSerializers]: null, diff --git a/lib/config-validator.js b/lib/config-validator.js index 4ce5787b074..825737b1a52 100644 --- a/lib/config-validator.js +++ b/lib/config-validator.js @@ -3,7 +3,7 @@ "use strict"; module.exports = validate10; module.exports.default = validate10; -const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":true,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; +const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"handlerTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":true,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}}; const func2 = Object.prototype.hasOwnProperty; const pattern0 = new RegExp("idle", "u"); @@ -24,6 +24,9 @@ data.maxRequestsPerSocket = 0; if(data.requestTimeout === undefined){ data.requestTimeout = 0; } +if(data.handlerTimeout === undefined){ +data.handlerTimeout = 0; +} if(data.bodyLimit === undefined){ data.bodyLimit = 1048576; } @@ -297,7 +300,7 @@ data["requestTimeout"] = coerced5; } var valid0 = _errs15 === errors; if(valid0){ -let data5 = data.bodyLimit; +let data5 = data.handlerTimeout; const _errs17 = errors; if(!(((typeof data5 == "number") && (!(data5 % 1) && !isNaN(data5))) && (isFinite(data5)))){ let dataType6 = typeof data5; @@ -308,45 +311,44 @@ if(dataType6 === "boolean" || data5 === null coerced6 = +data5; } else { -validate10.errors = [{instancePath:instancePath+"/bodyLimit",schemaPath:"#/properties/bodyLimit/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/handlerTimeout",schemaPath:"#/properties/handlerTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced6 !== undefined){ data5 = coerced6; if(data !== undefined){ -data["bodyLimit"] = coerced6; +data["handlerTimeout"] = coerced6; } } } var valid0 = _errs17 === errors; if(valid0){ -let data6 = data.caseSensitive; +let data6 = data.bodyLimit; const _errs19 = errors; -if(typeof data6 !== "boolean"){ +if(!(((typeof data6 == "number") && (!(data6 % 1) && !isNaN(data6))) && (isFinite(data6)))){ +let dataType7 = typeof data6; let coerced7 = undefined; if(!(coerced7 !== undefined)){ -if(data6 === "false" || data6 === 0 || data6 === null){ -coerced7 = false; -} -else if(data6 === "true" || data6 === 1){ -coerced7 = true; +if(dataType7 === "boolean" || data6 === null + || (dataType7 === "string" && data6 && data6 == +data6 && !(data6 % 1))){ +coerced7 = +data6; } else { -validate10.errors = [{instancePath:instancePath+"/caseSensitive",schemaPath:"#/properties/caseSensitive/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/bodyLimit",schemaPath:"#/properties/bodyLimit/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced7 !== undefined){ data6 = coerced7; if(data !== undefined){ -data["caseSensitive"] = coerced7; +data["bodyLimit"] = coerced7; } } } var valid0 = _errs19 === errors; if(valid0){ -let data7 = data.allowUnsafeRegex; +let data7 = data.caseSensitive; const _errs21 = errors; if(typeof data7 !== "boolean"){ let coerced8 = undefined; @@ -358,21 +360,20 @@ else if(data7 === "true" || data7 === 1){ coerced8 = true; } else { -validate10.errors = [{instancePath:instancePath+"/allowUnsafeRegex",schemaPath:"#/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/caseSensitive",schemaPath:"#/properties/caseSensitive/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced8 !== undefined){ data7 = coerced8; if(data !== undefined){ -data["allowUnsafeRegex"] = coerced8; +data["caseSensitive"] = coerced8; } } } var valid0 = _errs21 === errors; if(valid0){ -if(data.http2 !== undefined){ -let data8 = data.http2; +let data8 = data.allowUnsafeRegex; const _errs23 = errors; if(typeof data8 !== "boolean"){ let coerced9 = undefined; @@ -384,43 +385,69 @@ else if(data8 === "true" || data8 === 1){ coerced9 = true; } else { -validate10.errors = [{instancePath:instancePath+"/http2",schemaPath:"#/properties/http2/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/allowUnsafeRegex",schemaPath:"#/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced9 !== undefined){ data8 = coerced9; if(data !== undefined){ -data["http2"] = coerced9; +data["allowUnsafeRegex"] = coerced9; } } } var valid0 = _errs23 === errors; +if(valid0){ +if(data.http2 !== undefined){ +let data9 = data.http2; +const _errs25 = errors; +if(typeof data9 !== "boolean"){ +let coerced10 = undefined; +if(!(coerced10 !== undefined)){ +if(data9 === "false" || data9 === 0 || data9 === null){ +coerced10 = false; +} +else if(data9 === "true" || data9 === 1){ +coerced10 = true; +} +else { +validate10.errors = [{instancePath:instancePath+"/http2",schemaPath:"#/properties/http2/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +return false; +} +} +if(coerced10 !== undefined){ +data9 = coerced10; +if(data !== undefined){ +data["http2"] = coerced10; +} +} +} +var valid0 = _errs25 === errors; } else { var valid0 = true; } if(valid0){ if(data.https !== undefined){ -let data9 = data.https; -const _errs25 = errors; -const _errs26 = errors; -let valid2 = true; +let data10 = data.https; const _errs27 = errors; const _errs28 = errors; +let valid2 = true; const _errs29 = errors; const _errs30 = errors; +const _errs31 = errors; +const _errs32 = errors; let valid4 = false; let passing1 = null; -const _errs31 = errors; -if(typeof data9 !== "boolean"){ -let coerced10 = undefined; -if(!(coerced10 !== undefined)){ -if(data9 === "false" || data9 === 0 || data9 === null){ -coerced10 = false; +const _errs33 = errors; +if(typeof data10 !== "boolean"){ +let coerced11 = undefined; +if(!(coerced11 !== undefined)){ +if(data10 === "false" || data10 === 0 || data10 === null){ +coerced11 = false; } -else if(data9 === "true" || data9 === 1){ -coerced10 = true; +else if(data10 === "true" || data10 === 1){ +coerced11 = true; } else { const err4 = {}; @@ -433,24 +460,24 @@ vErrors.push(err4); errors++; } } -if(coerced10 !== undefined){ -data9 = coerced10; +if(coerced11 !== undefined){ +data10 = coerced11; if(data !== undefined){ -data["https"] = coerced10; +data["https"] = coerced11; } } } -var _valid2 = _errs31 === errors; +var _valid2 = _errs33 === errors; if(_valid2){ valid4 = true; passing1 = 0; } -const _errs33 = errors; -if(data9 !== null){ -let coerced11 = undefined; -if(!(coerced11 !== undefined)){ -if(data9 === "" || data9 === 0 || data9 === false){ -coerced11 = null; +const _errs35 = errors; +if(data10 !== null){ +let coerced12 = undefined; +if(!(coerced12 !== undefined)){ +if(data10 === "" || data10 === 0 || data10 === false){ +coerced12 = null; } else { const err5 = {}; @@ -463,14 +490,14 @@ vErrors.push(err5); errors++; } } -if(coerced11 !== undefined){ -data9 = coerced11; +if(coerced12 !== undefined){ +data10 = coerced12; if(data !== undefined){ -data["https"] = coerced11; +data["https"] = coerced12; } } } -var _valid2 = _errs33 === errors; +var _valid2 = _errs35 === errors; if(_valid2 && valid4){ valid4 = false; passing1 = [passing1, 1]; @@ -480,11 +507,11 @@ if(_valid2){ valid4 = true; passing1 = 1; } -const _errs35 = errors; -if(errors === _errs35){ -if(data9 && typeof data9 == "object" && !Array.isArray(data9)){ +const _errs37 = errors; +if(errors === _errs37){ +if(data10 && typeof data10 == "object" && !Array.isArray(data10)){ let missing0; -if((data9.allowHTTP1 === undefined) && (missing0 = "allowHTTP1")){ +if((data10.allowHTTP1 === undefined) && (missing0 = "allowHTTP1")){ const err6 = {}; if(vErrors === null){ vErrors = [err6]; @@ -495,23 +522,23 @@ vErrors.push(err6); errors++; } else { -const _errs37 = errors; -for(const key1 in data9){ +const _errs39 = errors; +for(const key1 in data10){ if(!(key1 === "allowHTTP1")){ -delete data9[key1]; +delete data10[key1]; } } -if(_errs37 === errors){ -if(data9.allowHTTP1 !== undefined){ -let data10 = data9.allowHTTP1; -if(typeof data10 !== "boolean"){ -let coerced12 = undefined; -if(!(coerced12 !== undefined)){ -if(data10 === "false" || data10 === 0 || data10 === null){ -coerced12 = false; +if(_errs39 === errors){ +if(data10.allowHTTP1 !== undefined){ +let data11 = data10.allowHTTP1; +if(typeof data11 !== "boolean"){ +let coerced13 = undefined; +if(!(coerced13 !== undefined)){ +if(data11 === "false" || data11 === 0 || data11 === null){ +coerced13 = false; } -else if(data10 === "true" || data10 === 1){ -coerced12 = true; +else if(data11 === "true" || data11 === 1){ +coerced13 = true; } else { const err7 = {}; @@ -524,10 +551,10 @@ vErrors.push(err7); errors++; } } -if(coerced12 !== undefined){ -data10 = coerced12; -if(data9 !== undefined){ -data9["allowHTTP1"] = coerced12; +if(coerced13 !== undefined){ +data11 = coerced13; +if(data10 !== undefined){ +data10["allowHTTP1"] = coerced13; } } } @@ -546,7 +573,7 @@ vErrors.push(err8); errors++; } } -var _valid2 = _errs35 === errors; +var _valid2 = _errs37 === errors; if(_valid2 && valid4){ valid4 = false; passing1 = [passing1, 2]; @@ -569,17 +596,17 @@ vErrors.push(err9); errors++; } else { -errors = _errs30; +errors = _errs32; if(vErrors !== null){ -if(_errs30){ -vErrors.length = _errs30; +if(_errs32){ +vErrors.length = _errs32; } else { vErrors = null; } } } -var valid3 = _errs29 === errors; +var valid3 = _errs31 === errors; if(valid3){ const err10 = {}; if(vErrors === null){ @@ -591,30 +618,30 @@ vErrors.push(err10); errors++; } else { -errors = _errs28; +errors = _errs30; if(vErrors !== null){ -if(_errs28){ -vErrors.length = _errs28; +if(_errs30){ +vErrors.length = _errs30; } else { vErrors = null; } } } -var _valid1 = _errs27 === errors; -errors = _errs26; +var _valid1 = _errs29 === errors; +errors = _errs28; if(vErrors !== null){ -if(_errs26){ -vErrors.length = _errs26; +if(_errs28){ +vErrors.length = _errs28; } else { vErrors = null; } } if(_valid1){ -const _errs40 = errors; +const _errs42 = errors; data["https"] = true; -var _valid1 = _errs40 === errors; +var _valid1 = _errs42 === errors; valid2 = _valid1; } if(!valid2){ @@ -629,38 +656,13 @@ errors++; validate10.errors = vErrors; return false; } -var valid0 = _errs25 === errors; +var valid0 = _errs27 === errors; } else { var valid0 = true; } if(valid0){ -let data11 = data.ignoreTrailingSlash; -const _errs41 = errors; -if(typeof data11 !== "boolean"){ -let coerced13 = undefined; -if(!(coerced13 !== undefined)){ -if(data11 === "false" || data11 === 0 || data11 === null){ -coerced13 = false; -} -else if(data11 === "true" || data11 === 1){ -coerced13 = true; -} -else { -validate10.errors = [{instancePath:instancePath+"/ignoreTrailingSlash",schemaPath:"#/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; -return false; -} -} -if(coerced13 !== undefined){ -data11 = coerced13; -if(data !== undefined){ -data["ignoreTrailingSlash"] = coerced13; -} -} -} -var valid0 = _errs41 === errors; -if(valid0){ -let data12 = data.ignoreDuplicateSlashes; +let data12 = data.ignoreTrailingSlash; const _errs43 = errors; if(typeof data12 !== "boolean"){ let coerced14 = undefined; @@ -672,70 +674,69 @@ else if(data12 === "true" || data12 === 1){ coerced14 = true; } else { -validate10.errors = [{instancePath:instancePath+"/ignoreDuplicateSlashes",schemaPath:"#/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/ignoreTrailingSlash",schemaPath:"#/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced14 !== undefined){ data12 = coerced14; if(data !== undefined){ -data["ignoreDuplicateSlashes"] = coerced14; +data["ignoreTrailingSlash"] = coerced14; } } } var valid0 = _errs43 === errors; if(valid0){ -let data13 = data.maxParamLength; +let data13 = data.ignoreDuplicateSlashes; const _errs45 = errors; -if(!(((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13))) && (isFinite(data13)))){ -let dataType15 = typeof data13; +if(typeof data13 !== "boolean"){ let coerced15 = undefined; if(!(coerced15 !== undefined)){ -if(dataType15 === "boolean" || data13 === null - || (dataType15 === "string" && data13 && data13 == +data13 && !(data13 % 1))){ -coerced15 = +data13; +if(data13 === "false" || data13 === 0 || data13 === null){ +coerced15 = false; +} +else if(data13 === "true" || data13 === 1){ +coerced15 = true; } else { -validate10.errors = [{instancePath:instancePath+"/maxParamLength",schemaPath:"#/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/ignoreDuplicateSlashes",schemaPath:"#/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced15 !== undefined){ data13 = coerced15; if(data !== undefined){ -data["maxParamLength"] = coerced15; +data["ignoreDuplicateSlashes"] = coerced15; } } } var valid0 = _errs45 === errors; if(valid0){ -let data14 = data.onProtoPoisoning; +let data14 = data.maxParamLength; const _errs47 = errors; -if(typeof data14 !== "string"){ +if(!(((typeof data14 == "number") && (!(data14 % 1) && !isNaN(data14))) && (isFinite(data14)))){ let dataType16 = typeof data14; let coerced16 = undefined; if(!(coerced16 !== undefined)){ -if(dataType16 == "number" || dataType16 == "boolean"){ -coerced16 = "" + data14; -} -else if(data14 === null){ -coerced16 = ""; +if(dataType16 === "boolean" || data14 === null + || (dataType16 === "string" && data14 && data14 == +data14 && !(data14 % 1))){ +coerced16 = +data14; } else { -validate10.errors = [{instancePath:instancePath+"/onProtoPoisoning",schemaPath:"#/properties/onProtoPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/maxParamLength",schemaPath:"#/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced16 !== undefined){ data14 = coerced16; if(data !== undefined){ -data["onProtoPoisoning"] = coerced16; +data["maxParamLength"] = coerced16; } } } var valid0 = _errs47 === errors; if(valid0){ -let data15 = data.onConstructorPoisoning; +let data15 = data.onProtoPoisoning; const _errs49 = errors; if(typeof data15 !== "string"){ let dataType17 = typeof data15; @@ -748,56 +749,82 @@ else if(data15 === null){ coerced17 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/onConstructorPoisoning",schemaPath:"#/properties/onConstructorPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; +validate10.errors = [{instancePath:instancePath+"/onProtoPoisoning",schemaPath:"#/properties/onProtoPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } if(coerced17 !== undefined){ data15 = coerced17; if(data !== undefined){ -data["onConstructorPoisoning"] = coerced17; +data["onProtoPoisoning"] = coerced17; } } } var valid0 = _errs49 === errors; if(valid0){ -let data16 = data.pluginTimeout; +let data16 = data.onConstructorPoisoning; const _errs51 = errors; -if(!(((typeof data16 == "number") && (!(data16 % 1) && !isNaN(data16))) && (isFinite(data16)))){ +if(typeof data16 !== "string"){ let dataType18 = typeof data16; let coerced18 = undefined; if(!(coerced18 !== undefined)){ -if(dataType18 === "boolean" || data16 === null - || (dataType18 === "string" && data16 && data16 == +data16 && !(data16 % 1))){ -coerced18 = +data16; +if(dataType18 == "number" || dataType18 == "boolean"){ +coerced18 = "" + data16; +} +else if(data16 === null){ +coerced18 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/pluginTimeout",schemaPath:"#/properties/pluginTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/onConstructorPoisoning",schemaPath:"#/properties/onConstructorPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } if(coerced18 !== undefined){ data16 = coerced18; if(data !== undefined){ -data["pluginTimeout"] = coerced18; +data["onConstructorPoisoning"] = coerced18; } } } var valid0 = _errs51 === errors; if(valid0){ -let data17 = data.requestIdHeader; +let data17 = data.pluginTimeout; const _errs53 = errors; -const _errs54 = errors; -let valid6 = false; -const _errs55 = errors; -if(typeof data17 !== "boolean"){ +if(!(((typeof data17 == "number") && (!(data17 % 1) && !isNaN(data17))) && (isFinite(data17)))){ +let dataType19 = typeof data17; let coerced19 = undefined; if(!(coerced19 !== undefined)){ -if(data17 === "false" || data17 === 0 || data17 === null){ -coerced19 = false; +if(dataType19 === "boolean" || data17 === null + || (dataType19 === "string" && data17 && data17 == +data17 && !(data17 % 1))){ +coerced19 = +data17; +} +else { +validate10.errors = [{instancePath:instancePath+"/pluginTimeout",schemaPath:"#/properties/pluginTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +return false; +} +} +if(coerced19 !== undefined){ +data17 = coerced19; +if(data !== undefined){ +data["pluginTimeout"] = coerced19; } -else if(data17 === "true" || data17 === 1){ -coerced19 = true; +} +} +var valid0 = _errs53 === errors; +if(valid0){ +let data18 = data.requestIdHeader; +const _errs55 = errors; +const _errs56 = errors; +let valid6 = false; +const _errs57 = errors; +if(typeof data18 !== "boolean"){ +let coerced20 = undefined; +if(!(coerced20 !== undefined)){ +if(data18 === "false" || data18 === 0 || data18 === null){ +coerced20 = false; +} +else if(data18 === "true" || data18 === 1){ +coerced20 = true; } else { const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; @@ -810,26 +837,26 @@ vErrors.push(err12); errors++; } } -if(coerced19 !== undefined){ -data17 = coerced19; +if(coerced20 !== undefined){ +data18 = coerced20; if(data !== undefined){ -data["requestIdHeader"] = coerced19; +data["requestIdHeader"] = coerced20; } } } -var _valid3 = _errs55 === errors; +var _valid3 = _errs57 === errors; valid6 = valid6 || _valid3; if(!valid6){ -const _errs57 = errors; -if(typeof data17 !== "string"){ -let dataType20 = typeof data17; -let coerced20 = undefined; -if(!(coerced20 !== undefined)){ -if(dataType20 == "number" || dataType20 == "boolean"){ -coerced20 = "" + data17; +const _errs59 = errors; +if(typeof data18 !== "string"){ +let dataType21 = typeof data18; +let coerced21 = undefined; +if(!(coerced21 !== undefined)){ +if(dataType21 == "number" || dataType21 == "boolean"){ +coerced21 = "" + data18; } -else if(data17 === null){ -coerced20 = ""; +else if(data18 === null){ +coerced21 = ""; } else { const err13 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"}; @@ -842,14 +869,14 @@ vErrors.push(err13); errors++; } } -if(coerced20 !== undefined){ -data17 = coerced20; +if(coerced21 !== undefined){ +data18 = coerced21; if(data !== undefined){ -data["requestIdHeader"] = coerced20; +data["requestIdHeader"] = coerced21; } } } -var _valid3 = _errs57 === errors; +var _valid3 = _errs59 === errors; valid6 = valid6 || _valid3; } if(!valid6){ @@ -865,94 +892,69 @@ validate10.errors = vErrors; return false; } else { -errors = _errs54; +errors = _errs56; if(vErrors !== null){ -if(_errs54){ -vErrors.length = _errs54; +if(_errs56){ +vErrors.length = _errs56; } else { vErrors = null; } } } -var valid0 = _errs53 === errors; -if(valid0){ -let data18 = data.requestIdLogLabel; -const _errs59 = errors; -if(typeof data18 !== "string"){ -let dataType21 = typeof data18; -let coerced21 = undefined; -if(!(coerced21 !== undefined)){ -if(dataType21 == "number" || dataType21 == "boolean"){ -coerced21 = "" + data18; -} -else if(data18 === null){ -coerced21 = ""; -} -else { -validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}]; -return false; -} -} -if(coerced21 !== undefined){ -data18 = coerced21; -if(data !== undefined){ -data["requestIdLogLabel"] = coerced21; -} -} -} -var valid0 = _errs59 === errors; +var valid0 = _errs55 === errors; if(valid0){ -let data19 = data.http2SessionTimeout; +let data19 = data.requestIdLogLabel; const _errs61 = errors; -if(!(((typeof data19 == "number") && (!(data19 % 1) && !isNaN(data19))) && (isFinite(data19)))){ +if(typeof data19 !== "string"){ let dataType22 = typeof data19; let coerced22 = undefined; if(!(coerced22 !== undefined)){ -if(dataType22 === "boolean" || data19 === null - || (dataType22 === "string" && data19 && data19 == +data19 && !(data19 % 1))){ -coerced22 = +data19; +if(dataType22 == "number" || dataType22 == "boolean"){ +coerced22 = "" + data19; +} +else if(data19 === null){ +coerced22 = ""; } else { -validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } if(coerced22 !== undefined){ data19 = coerced22; if(data !== undefined){ -data["http2SessionTimeout"] = coerced22; +data["requestIdLogLabel"] = coerced22; } } } var valid0 = _errs61 === errors; if(valid0){ -let data20 = data.exposeHeadRoutes; +let data20 = data.http2SessionTimeout; const _errs63 = errors; -if(typeof data20 !== "boolean"){ +if(!(((typeof data20 == "number") && (!(data20 % 1) && !isNaN(data20))) && (isFinite(data20)))){ +let dataType23 = typeof data20; let coerced23 = undefined; if(!(coerced23 !== undefined)){ -if(data20 === "false" || data20 === 0 || data20 === null){ -coerced23 = false; -} -else if(data20 === "true" || data20 === 1){ -coerced23 = true; +if(dataType23 === "boolean" || data20 === null + || (dataType23 === "string" && data20 && data20 == +data20 && !(data20 % 1))){ +coerced23 = +data20; } else { -validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced23 !== undefined){ data20 = coerced23; if(data !== undefined){ -data["exposeHeadRoutes"] = coerced23; +data["http2SessionTimeout"] = coerced23; } } } var valid0 = _errs63 === errors; if(valid0){ -let data21 = data.useSemicolonDelimiter; +let data21 = data.exposeHeadRoutes; const _errs65 = errors; if(typeof data21 !== "boolean"){ let coerced24 = undefined; @@ -964,65 +966,65 @@ else if(data21 === "true" || data21 === 1){ coerced24 = true; } else { -validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced24 !== undefined){ data21 = coerced24; if(data !== undefined){ -data["useSemicolonDelimiter"] = coerced24; +data["exposeHeadRoutes"] = coerced24; } } } var valid0 = _errs65 === errors; if(valid0){ -if(data.routerOptions !== undefined){ -let data22 = data.routerOptions; +let data22 = data.useSemicolonDelimiter; const _errs67 = errors; -if(errors === _errs67){ -if(data22 && typeof data22 == "object" && !Array.isArray(data22)){ -if(data22.ignoreTrailingSlash === undefined){ -data22.ignoreTrailingSlash = false; -} -if(data22.ignoreDuplicateSlashes === undefined){ -data22.ignoreDuplicateSlashes = false; -} -if(data22.maxParamLength === undefined){ -data22.maxParamLength = 100; -} -if(data22.allowUnsafeRegex === undefined){ -data22.allowUnsafeRegex = false; -} -if(data22.useSemicolonDelimiter === undefined){ -data22.useSemicolonDelimiter = false; -} -let data23 = data22.ignoreTrailingSlash; -const _errs70 = errors; -if(typeof data23 !== "boolean"){ +if(typeof data22 !== "boolean"){ let coerced25 = undefined; if(!(coerced25 !== undefined)){ -if(data23 === "false" || data23 === 0 || data23 === null){ +if(data22 === "false" || data22 === 0 || data22 === null){ coerced25 = false; } -else if(data23 === "true" || data23 === 1){ +else if(data22 === "true" || data22 === 1){ coerced25 = true; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced25 !== undefined){ -data23 = coerced25; -if(data22 !== undefined){ -data22["ignoreTrailingSlash"] = coerced25; +data22 = coerced25; +if(data !== undefined){ +data["useSemicolonDelimiter"] = coerced25; } } } -var valid7 = _errs70 === errors; -if(valid7){ -let data24 = data22.ignoreDuplicateSlashes; +var valid0 = _errs67 === errors; +if(valid0){ +if(data.routerOptions !== undefined){ +let data23 = data.routerOptions; +const _errs69 = errors; +if(errors === _errs69){ +if(data23 && typeof data23 == "object" && !Array.isArray(data23)){ +if(data23.ignoreTrailingSlash === undefined){ +data23.ignoreTrailingSlash = false; +} +if(data23.ignoreDuplicateSlashes === undefined){ +data23.ignoreDuplicateSlashes = false; +} +if(data23.maxParamLength === undefined){ +data23.maxParamLength = 100; +} +if(data23.allowUnsafeRegex === undefined){ +data23.allowUnsafeRegex = false; +} +if(data23.useSemicolonDelimiter === undefined){ +data23.useSemicolonDelimiter = false; +} +let data24 = data23.ignoreTrailingSlash; const _errs72 = errors; if(typeof data24 !== "boolean"){ let coerced26 = undefined; @@ -1034,69 +1036,69 @@ else if(data24 === "true" || data24 === 1){ coerced26 = true; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreDuplicateSlashes",schemaPath:"#/properties/routerOptions/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced26 !== undefined){ data24 = coerced26; -if(data22 !== undefined){ -data22["ignoreDuplicateSlashes"] = coerced26; +if(data23 !== undefined){ +data23["ignoreTrailingSlash"] = coerced26; } } } var valid7 = _errs72 === errors; if(valid7){ -let data25 = data22.maxParamLength; +let data25 = data23.ignoreDuplicateSlashes; const _errs74 = errors; -if(!(((typeof data25 == "number") && (!(data25 % 1) && !isNaN(data25))) && (isFinite(data25)))){ -let dataType27 = typeof data25; +if(typeof data25 !== "boolean"){ let coerced27 = undefined; if(!(coerced27 !== undefined)){ -if(dataType27 === "boolean" || data25 === null - || (dataType27 === "string" && data25 && data25 == +data25 && !(data25 % 1))){ -coerced27 = +data25; +if(data25 === "false" || data25 === 0 || data25 === null){ +coerced27 = false; +} +else if(data25 === "true" || data25 === 1){ +coerced27 = true; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/maxParamLength",schemaPath:"#/properties/routerOptions/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreDuplicateSlashes",schemaPath:"#/properties/routerOptions/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced27 !== undefined){ data25 = coerced27; -if(data22 !== undefined){ -data22["maxParamLength"] = coerced27; +if(data23 !== undefined){ +data23["ignoreDuplicateSlashes"] = coerced27; } } } var valid7 = _errs74 === errors; if(valid7){ -let data26 = data22.allowUnsafeRegex; +let data26 = data23.maxParamLength; const _errs76 = errors; -if(typeof data26 !== "boolean"){ +if(!(((typeof data26 == "number") && (!(data26 % 1) && !isNaN(data26))) && (isFinite(data26)))){ +let dataType28 = typeof data26; let coerced28 = undefined; if(!(coerced28 !== undefined)){ -if(data26 === "false" || data26 === 0 || data26 === null){ -coerced28 = false; -} -else if(data26 === "true" || data26 === 1){ -coerced28 = true; +if(dataType28 === "boolean" || data26 === null + || (dataType28 === "string" && data26 && data26 == +data26 && !(data26 % 1))){ +coerced28 = +data26; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/allowUnsafeRegex",schemaPath:"#/properties/routerOptions/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/maxParamLength",schemaPath:"#/properties/routerOptions/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}]; return false; } } if(coerced28 !== undefined){ data26 = coerced28; -if(data22 !== undefined){ -data22["allowUnsafeRegex"] = coerced28; +if(data23 !== undefined){ +data23["maxParamLength"] = coerced28; } } } var valid7 = _errs76 === errors; if(valid7){ -let data27 = data22.useSemicolonDelimiter; +let data27 = data23.allowUnsafeRegex; const _errs78 = errors; if(typeof data27 !== "boolean"){ let coerced29 = undefined; @@ -1108,18 +1110,43 @@ else if(data27 === "true" || data27 === 1){ coerced29 = true; } else { -validate10.errors = [{instancePath:instancePath+"/routerOptions/useSemicolonDelimiter",schemaPath:"#/properties/routerOptions/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +validate10.errors = [{instancePath:instancePath+"/routerOptions/allowUnsafeRegex",schemaPath:"#/properties/routerOptions/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; return false; } } if(coerced29 !== undefined){ data27 = coerced29; -if(data22 !== undefined){ -data22["useSemicolonDelimiter"] = coerced29; +if(data23 !== undefined){ +data23["allowUnsafeRegex"] = coerced29; } } } var valid7 = _errs78 === errors; +if(valid7){ +let data28 = data23.useSemicolonDelimiter; +const _errs80 = errors; +if(typeof data28 !== "boolean"){ +let coerced30 = undefined; +if(!(coerced30 !== undefined)){ +if(data28 === "false" || data28 === 0 || data28 === null){ +coerced30 = false; +} +else if(data28 === "true" || data28 === 1){ +coerced30 = true; +} +else { +validate10.errors = [{instancePath:instancePath+"/routerOptions/useSemicolonDelimiter",schemaPath:"#/properties/routerOptions/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}]; +return false; +} +} +if(coerced30 !== undefined){ +data28 = coerced30; +if(data23 !== undefined){ +data23["useSemicolonDelimiter"] = coerced30; +} +} +} +var valid7 = _errs80 === errors; } } } @@ -1130,49 +1157,49 @@ validate10.errors = [{instancePath:instancePath+"/routerOptions",schemaPath:"#/p return false; } } -var valid0 = _errs67 === errors; +var valid0 = _errs69 === errors; } else { var valid0 = true; } if(valid0){ if(data.constraints !== undefined){ -let data28 = data.constraints; -const _errs80 = errors; -if(errors === _errs80){ -if(data28 && typeof data28 == "object" && !Array.isArray(data28)){ -for(const key2 in data28){ -let data29 = data28[key2]; -const _errs83 = errors; -if(errors === _errs83){ +let data29 = data.constraints; +const _errs82 = errors; +if(errors === _errs82){ if(data29 && typeof data29 == "object" && !Array.isArray(data29)){ +for(const key2 in data29){ +let data30 = data29[key2]; +const _errs85 = errors; +if(errors === _errs85){ +if(data30 && typeof data30 == "object" && !Array.isArray(data30)){ let missing1; -if(((((data29.name === undefined) && (missing1 = "name")) || ((data29.storage === undefined) && (missing1 = "storage"))) || ((data29.validate === undefined) && (missing1 = "validate"))) || ((data29.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){ +if(((((data30.name === undefined) && (missing1 = "name")) || ((data30.storage === undefined) && (missing1 = "storage"))) || ((data30.validate === undefined) && (missing1 = "validate"))) || ((data30.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}]; return false; } else { -if(data29.name !== undefined){ -let data30 = data29.name; -if(typeof data30 !== "string"){ -let dataType30 = typeof data30; -let coerced30 = undefined; -if(!(coerced30 !== undefined)){ -if(dataType30 == "number" || dataType30 == "boolean"){ -coerced30 = "" + data30; +if(data30.name !== undefined){ +let data31 = data30.name; +if(typeof data31 !== "string"){ +let dataType31 = typeof data31; +let coerced31 = undefined; +if(!(coerced31 !== undefined)){ +if(dataType31 == "number" || dataType31 == "boolean"){ +coerced31 = "" + data31; } -else if(data30 === null){ -coerced30 = ""; +else if(data31 === null){ +coerced31 = ""; } else { validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}]; return false; } } -if(coerced30 !== undefined){ -data30 = coerced30; -if(data29 !== undefined){ -data29["name"] = coerced30; +if(coerced31 !== undefined){ +data31 = coerced31; +if(data30 !== undefined){ +data30["name"] = coerced31; } } } @@ -1184,7 +1211,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/ return false; } } -var valid8 = _errs83 === errors; +var valid8 = _errs85 === errors; if(!valid8){ break; } @@ -1195,7 +1222,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro return false; } } -var valid0 = _errs80 === errors; +var valid0 = _errs82 === errors; } else { var valid0 = true; @@ -1224,6 +1251,7 @@ var valid0 = true; } } } +} else { validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}]; return false; @@ -1234,5 +1262,5 @@ return errors === 0; } -module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false,"allowErrorHandlerOverride":true,"routerOptions":{"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"allowUnsafeRegex":false,"useSemicolonDelimiter":false}} +module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"handlerTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false,"allowErrorHandlerOverride":true,"routerOptions":{"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"allowUnsafeRegex":false,"useSemicolonDelimiter":false}} /* c8 ignore stop */ diff --git a/lib/context.js b/lib/context.js index d6d1f6aa90d..7a3a991315d 100644 --- a/lib/context.js +++ b/lib/context.js @@ -14,7 +14,8 @@ const { kContentTypeParser, kRouteByFastify, kRequestCacheValidateFns, - kReplyCacheSerializeFns + kReplyCacheSerializeFns, + kHandlerTimeout } = require('./symbols.js') // Object that holds the context of every request @@ -37,7 +38,8 @@ function Context ({ exposeHeadRoute, prefixTrailingSlash, server, - isFastify + isFastify, + handlerTimeout }) { this.schema = schema this.handler = handler @@ -78,6 +80,7 @@ function Context ({ this.validatorCompiler = validatorCompiler || null this.serializerCompiler = serializerCompiler || null + this.handlerTimeout = handlerTimeout || server[kHandlerTimeout] || 0 this.server = server } diff --git a/lib/errors.js b/lib/errors.js index 53c14abfae1..670442b92bb 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -419,6 +419,17 @@ const codes = { 500, TypeError ), + FST_ERR_HANDLER_TIMEOUT: createError( + 'FST_ERR_HANDLER_TIMEOUT', + "Request timed out after %s ms on route '%s'", + 503 + ), + FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT: createError( + 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT', + "'handlerTimeout' option must be an integer > 0. Got '%s'", + 500, + TypeError + ), FST_ERR_ROUTE_REWRITE_NOT_STR: createError( 'FST_ERR_ROUTE_REWRITE_NOT_STR', 'Rewrite url for "%s" needs to be of type "string" but received "%s"', diff --git a/lib/reply.js b/lib/reply.js index d36becec478..56f5e1eaeb5 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -21,7 +21,10 @@ const { kReplyCacheSerializeFns, kSchemaController, kOptions, - kRouteContext + kRouteContext, + kTimeoutTimer, + kOnAbort, + kRequestSignal } = require('./symbols.js') const { onSendHookRunner, @@ -121,6 +124,15 @@ Reply.prototype.writeEarlyHints = function (hints, callback) { Reply.prototype.hijack = function () { this[kReplyHijacked] = true + // Clear handler timeout and signal — hijacked replies manage their own lifecycle + if (this.request[kRequestSignal]) { + clearTimeout(this.request[kTimeoutTimer]) + this.request[kTimeoutTimer] = null + if (this.request[kOnAbort]) { + this.request.raw.removeListener('close', this.request[kOnAbort]) + this.request[kOnAbort] = null + } + } return this } @@ -892,6 +904,16 @@ function setupResponseListeners (reply) { const ctx = reply[kRouteContext] + // Clean up handler timeout / signal resources + if (reply.request[kRequestSignal]) { + clearTimeout(reply.request[kTimeoutTimer]) + reply.request[kTimeoutTimer] = null + if (reply.request[kOnAbort]) { + reply.request.raw.removeListener('close', reply.request[kOnAbort]) + reply.request[kOnAbort] = null + } + } + if (ctx && ctx.onResponse !== null) { onResponseHookRunner( ctx.onResponse, diff --git a/lib/request.js b/lib/request.js index c9640fdcc47..f1f4a5e1338 100644 --- a/lib/request.js +++ b/lib/request.js @@ -11,7 +11,9 @@ const { kOptions, kRequestCacheValidateFns, kRouteContext, - kRequestOriginalUrl + kRequestOriginalUrl, + kRequestSignal, + kOnAbort } = require('./symbols') const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors') const decorators = require('./decorate') @@ -183,6 +185,7 @@ Object.defineProperties(Request.prototype, { method: context.config?.method, url: context.config?.url, bodyLimit: (routeLimit || serverLimit), + handlerTimeout: context.handlerTimeout, attachValidation: context.attachValidation, logLevel: context.logLevel, exposeHeadRoute: context.exposeHeadRoute, @@ -206,6 +209,20 @@ Object.defineProperties(Request.prototype, { return this.raw.socket } }, + signal: { + get () { + let ac = this[kRequestSignal] + if (ac) return ac.signal + ac = new AbortController() + this[kRequestSignal] = ac + const onAbort = () => { + if (!ac.signal.aborted) ac.abort() + } + this.raw.on('close', onAbort) + this[kOnAbort] = onAbort + return ac.signal + } + }, ip: { get () { if (this.socket) { diff --git a/lib/route.js b/lib/route.js index 80c4294522c..8e90bda061a 100644 --- a/lib/route.js +++ b/lib/route.js @@ -26,6 +26,8 @@ const { FST_ERR_ROUTE_METHOD_INVALID, FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED, FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT, + FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT, + FST_ERR_HANDLER_TIMEOUT, FST_ERR_HOOK_INVALID_ASYNC_HANDLER } = require('./errors') @@ -46,7 +48,10 @@ const { kHasBeenDecorated, kRequestAcceptVersion, kRouteByFastify, - kRouteContext + kRouteContext, + kRequestSignal, + kTimeoutTimer, + kOnAbort } = require('./symbols.js') const { buildErrorHandler } = require('./error-handler') const { createChildLogger } = require('./logger-factory.js') @@ -217,6 +222,7 @@ function buildRouting (options) { } validateBodyLimitOption(opts.bodyLimit) + validateHandlerTimeoutOption(opts.handlerTimeout) const shouldExposeHead = opts.exposeHeadRoute ?? globalExposeHeadRoutes @@ -346,7 +352,8 @@ function buildRouting (options) { exposeHeadRoute: shouldExposeHead, prefixTrailingSlash: (opts.prefixTrailingSlash || 'both'), server: this, - isFastify + isFastify, + handlerTimeout: opts.handlerTimeout }) const headHandler = router.findRoute('HEAD', opts.url, constraints) @@ -517,7 +524,32 @@ function buildRouting (options) { childLogger.info({ req: request }, 'incoming request') } - if (hasLogger === true || context.onResponse !== null) { + // Handler timeout setup — only when configured (zero overhead otherwise) + const handlerTimeout = context.handlerTimeout + if (handlerTimeout > 0) { + const ac = new AbortController() + request[kRequestSignal] = ac + + request[kTimeoutTimer] = setTimeout(() => { + if (!reply.sent) { + const err = new FST_ERR_HANDLER_TIMEOUT(handlerTimeout, context.config?.url) + ac.abort(err) + reply[kReplyIsError] = true + reply.send(err) + } + }, handlerTimeout) + + const onAbort = () => { + if (!ac.signal.aborted) { + ac.abort() + } + clearTimeout(request[kTimeoutTimer]) + } + req.on('close', onAbort) + request[kOnAbort] = onAbort + } + + if (hasLogger === true || context.onResponse !== null || handlerTimeout > 0) { setupResponseListeners(reply) } @@ -596,6 +628,13 @@ function validateBodyLimitOption (bodyLimit) { } } +function validateHandlerTimeoutOption (handlerTimeout) { + if (handlerTimeout === undefined) return + if (!Number.isInteger(handlerTimeout) || handlerTimeout <= 0) { + throw new FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT(handlerTimeout) + } +} + function runPreParsing (err, request, reply) { if (reply.sent === true) return if (err != null) { diff --git a/lib/symbols.js b/lib/symbols.js index 510ac39c7c1..a3ad9d88a14 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -33,6 +33,10 @@ const keys = { kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'), kRequestCacheValidateFns: Symbol('fastify.request.cache.validateFns'), kRequestOriginalUrl: Symbol('fastify.request.originalUrl'), + kRequestSignal: Symbol('fastify.request.signal'), + kHandlerTimeout: Symbol('fastify.handlerTimeout'), + kTimeoutTimer: Symbol('fastify.request.timeoutTimer'), + kOnAbort: Symbol('fastify.request.onAbort'), // 404 kFourOhFour: Symbol('fastify.404'), kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'), diff --git a/test/handler-timeout.test.js b/test/handler-timeout.test.js new file mode 100644 index 00000000000..60ac3847714 --- /dev/null +++ b/test/handler-timeout.test.js @@ -0,0 +1,367 @@ +'use strict' + +const { test } = require('node:test') +const net = require('node:net') +const Fastify = require('..') +const { Readable } = require('node:stream') +const { kTimeoutTimer, kOnAbort } = require('../lib/symbols') + +// --- Option validation --- + +test('server-level handlerTimeout defaults to 0 in initialConfig', t => { + t.plan(1) + const fastify = Fastify() + t.assert.strictEqual(fastify.initialConfig.handlerTimeout, 0) +}) + +test('server-level handlerTimeout: 5000 is accepted and exposed in initialConfig', t => { + t.plan(1) + const fastify = Fastify({ handlerTimeout: 5000 }) + t.assert.strictEqual(fastify.initialConfig.handlerTimeout, 5000) +}) + +test('route-level handlerTimeout rejects invalid values', async t => { + const fastify = Fastify() + + t.assert.throws(() => { + fastify.get('/a', { handlerTimeout: 'fast' }, async () => 'ok') + }, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' }) + + t.assert.throws(() => { + fastify.get('/b', { handlerTimeout: -1 }, async () => 'ok') + }, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' }) + + t.assert.throws(() => { + fastify.get('/c', { handlerTimeout: 1.5 }, async () => 'ok') + }, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' }) + + t.assert.throws(() => { + fastify.get('/d', { handlerTimeout: 0 }, async () => 'ok') + }, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' }) +}) + +// --- Lazy signal without handlerTimeout --- + +test('when handlerTimeout is 0 (default), request.signal is lazily created', async t => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/', async (request) => { + const signal = request.signal + t.assert.ok(signal instanceof AbortSignal) + t.assert.strictEqual(signal.aborted, false) + return { ok: true } + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('client disconnect aborts lazily created signal (no handlerTimeout)', async t => { + t.plan(1) + + const fastify = Fastify() + let signalAborted = false + + fastify.get('/', async (request) => { + await new Promise((resolve) => { + request.signal.addEventListener('abort', () => { + signalAborted = true + resolve() + }) + }) + return 'should not reach' + }) + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const address = fastify.server.address() + await new Promise((resolve) => { + const client = net.connect(address.port, () => { + client.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + setTimeout(() => { + client.destroy() + setTimeout(resolve, 100) + }, 50) + }) + }) + + t.assert.strictEqual(signalAborted, true) +}) + +// --- Basic timeout behavior --- + +test('slow handler returns 503 with FST_ERR_HANDLER_TIMEOUT', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 50 }, async () => { + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 503) + t.assert.strictEqual(JSON.parse(res.payload).code, 'FST_ERR_HANDLER_TIMEOUT') +}) + +test('fast handler completes normally with 200', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 5000 }, async () => { + return { hello: 'world' } + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) +}) + +// --- Per-route override --- + +test('route-level handlerTimeout overrides server default', async t => { + t.plan(4) + const fastify = Fastify({ handlerTimeout: 5000 }) + + fastify.get('/slow', { handlerTimeout: 50 }, async () => { + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + fastify.get('/fast', async () => { + return { ok: true } + }) + + const resSlow = await fastify.inject({ method: 'GET', url: '/slow' }) + t.assert.strictEqual(resSlow.statusCode, 503) + t.assert.strictEqual(JSON.parse(resSlow.payload).code, 'FST_ERR_HANDLER_TIMEOUT') + + const resFast = await fastify.inject({ method: 'GET', url: '/fast' }) + t.assert.strictEqual(resFast.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(resFast.payload), { ok: true }) +}) + +// --- request.signal behavior --- + +test('request.signal is an AbortSignal when handlerTimeout > 0', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 5000 }, async (request) => { + t.assert.ok(request.signal instanceof AbortSignal) + t.assert.strictEqual(request.signal.aborted, false) + return 'ok' + }) + + await fastify.inject({ method: 'GET', url: '/' }) +}) + +test('request.signal aborts when timeout fires with reason', async t => { + t.plan(2) + const fastify = Fastify() + + let signalReason = null + fastify.get('/', { handlerTimeout: 50 }, async (request) => { + request.signal.addEventListener('abort', () => { + signalReason = request.signal.reason + }) + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + await fastify.inject({ method: 'GET', url: '/' }) + t.assert.ok(signalReason !== null) + t.assert.strictEqual(signalReason.code, 'FST_ERR_HANDLER_TIMEOUT') +}) + +// --- Streaming response --- + +test('streaming response: timer clears when response finishes', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.get('/', { handlerTimeout: 5000 }, async (request, reply) => { + const stream = new Readable({ + read () { + this.push('hello') + this.push(null) + } + }) + reply.type('text/plain').send(stream) + return reply + }) + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const address = fastify.server.address() + const res = await fetch(`http://localhost:${address.port}/`) + t.assert.strictEqual(res.status, 200) +}) + +// --- SSE with reply.hijack() --- + +test('reply.hijack() clears timeout timer', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.get('/', { handlerTimeout: 100 }, async (request, reply) => { + reply.hijack() + // Write after the original timeout would have fired + await new Promise(resolve => setTimeout(resolve, 200)) + reply.raw.writeHead(200, { 'Content-Type': 'text/plain' }) + reply.raw.end('hijacked response') + }) + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const address = fastify.server.address() + const res = await fetch(`http://localhost:${address.port}/`) + t.assert.strictEqual(res.status, 200) +}) + +// --- Error handler integration --- + +test('route-level errorHandler receives FST_ERR_HANDLER_TIMEOUT', async t => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/', { + handlerTimeout: 50, + errorHandler: (error, request, reply) => { + t.assert.strictEqual(error.code, 'FST_ERR_HANDLER_TIMEOUT') + reply.code(504).send({ custom: 'timeout' }) + } + }, async () => { + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 504) + t.assert.deepStrictEqual(JSON.parse(res.payload), { custom: 'timeout' }) +}) + +// --- Timer cleanup / no leaks --- + +test('timer is cleaned up after fast response (no leak)', async t => { + t.plan(3) + const fastify = Fastify() + + let capturedRequest + fastify.get('/', { handlerTimeout: 60000 }, async (request) => { + capturedRequest = request + return 'fast' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) + // Timer and listener should be cleaned up + t.assert.strictEqual(capturedRequest[kTimeoutTimer], null) + t.assert.strictEqual(capturedRequest[kOnAbort], null) +}) + +// --- routeOptions exposure --- + +test('request.routeOptions.handlerTimeout reflects configured value', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 3000 }, async (request) => { + t.assert.strictEqual(request.routeOptions.handlerTimeout, 3000) + return 'ok' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('request.routeOptions.handlerTimeout reflects server default', async t => { + t.plan(2) + const fastify = Fastify({ handlerTimeout: 7000 }) + + fastify.get('/', async (request) => { + t.assert.strictEqual(request.routeOptions.handlerTimeout, 7000) + return 'ok' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) +}) + +// --- Client disconnect aborts signal --- + +test('client disconnect aborts request.signal', async t => { + t.plan(1) + + const fastify = Fastify() + let signalAborted = false + + fastify.get('/', { handlerTimeout: 5000 }, async (request) => { + await new Promise((resolve) => { + request.signal.addEventListener('abort', () => { + signalAborted = true + resolve() + }) + }) + return 'should not reach' + }) + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const address = fastify.server.address() + await new Promise((resolve) => { + const client = net.connect(address.port, () => { + client.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + setTimeout(() => { + client.destroy() + // Give the server time to process the close event + setTimeout(resolve, 100) + }, 50) + }) + }) + + t.assert.strictEqual(signalAborted, true) +}) + +// --- Race: handler completes just as timeout fires --- + +test('no double-send when handler completes near timeout boundary', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 50 }, async (request, reply) => { + // Respond just before timeout + await new Promise(resolve => setTimeout(resolve, 40)) + reply.send({ ok: true }) + return reply + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + // Should get either 200 or 503 depending on race, but never crash + t.assert.ok(res.statusCode === 200 || res.statusCode === 503) + // Verify response is valid JSON regardless of which won the race + t.assert.ok(JSON.parse(res.payload)) +}) + +// --- Server default inherited by routes --- + +test('routes inherit server-level handlerTimeout', async t => { + t.plan(3) + const fastify = Fastify({ handlerTimeout: 50 }) + + fastify.get('/', async (request) => { + // Verify the signal is present (inherited from server default) + t.assert.ok(request.signal instanceof AbortSignal) + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 503) + t.assert.strictEqual(JSON.parse(res.payload).code, 'FST_ERR_HANDLER_TIMEOUT') +}) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 2499b380f18..9b0a8409754 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -5,7 +5,7 @@ const errors = require('../../lib/errors') const { readFileSync } = require('node:fs') const { resolve } = require('node:path') -const expectedErrors = 86 +const expectedErrors = 88 test(`should expose ${expectedErrors} errors`, t => { t.plan(1) diff --git a/test/internals/initial-config.test.js b/test/internals/initial-config.test.js index c513c0035bd..5cd5229aea4 100644 --- a/test/internals/initial-config.test.js +++ b/test/internals/initial-config.test.js @@ -34,6 +34,7 @@ test('without options passed to Fastify, initialConfig should expose default val keepAliveTimeout: 72000, maxRequestsPerSocket: 0, requestTimeout: 0, + handlerTimeout: 0, bodyLimit: 1024 * 1024, caseSensitive: true, allowUnsafeRegex: false, @@ -273,6 +274,7 @@ test('Should not have issues when passing stream options to Pino.js', (t, done) keepAliveTimeout: 72000, maxRequestsPerSocket: 0, requestTimeout: 0, + handlerTimeout: 0, bodyLimit: 1024 * 1024, caseSensitive: true, allowUnsafeRegex: false, diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index ffb2f80c0ee..91fd4a69304 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -113,6 +113,7 @@ expectAssignable(fastify({ forceCloseConnections: true })) expectAssignable(fastify({ keepAliveTimeout: 1000 })) expectAssignable(fastify({ pluginTimeout: 1000 })) expectAssignable(fastify({ bodyLimit: 100 })) +expectAssignable(fastify({ handlerTimeout: 5000 })) expectAssignable(fastify({ maxParamLength: 100 })) expectAssignable(fastify({ disableRequestLogging: true })) expectAssignable(fastify({ disableRequestLogging: (req) => req.url?.includes('/health') ?? false })) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index dda0337a2d1..8e8267dc3ae 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -87,6 +87,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.id) expectType(request.log) expectType(request.socket) + expectType(request.signal) expectType(request.validationError) expectType(request.server) expectAssignable<(httpPart: HTTPRequestPart) => ExpectedGetValidationFunction>(request.getValidationFunction) diff --git a/types/errors.d.ts b/types/errors.d.ts index 777f97b0f74..13c5cf406b6 100644 --- a/types/errors.d.ts +++ b/types/errors.d.ts @@ -75,6 +75,8 @@ export type FastifyErrorCodes = Record< 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED' | 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED' | 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT' | + 'FST_ERR_HANDLER_TIMEOUT' | + 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' | 'FST_ERR_ROUTE_REWRITE_NOT_STR' | 'FST_ERR_REOPENED_CLOSE_SERVER' | 'FST_ERR_REOPENED_SERVER' | diff --git a/types/request.d.ts b/types/request.d.ts index 0ee81d3ecd0..e7229b5909c 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -25,6 +25,7 @@ export interface RequestRouteOptions> readonly is404: boolean; readonly socket: RawRequest['socket']; + readonly signal: AbortSignal; getValidationFunction(httpPart: HTTPRequestPart): ValidationFunction getValidationFunction(schema: { [key: string]: any }): ValidationFunction diff --git a/types/route.d.ts b/types/route.d.ts index ed942c692d8..e12fa007fe9 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -64,6 +64,7 @@ export interface RouteShorthandOptions< validatorCompiler?: FastifySchemaCompiler>; serializerCompiler?: FastifySerializerCompiler>; bodyLimit?: number; + handlerTimeout?: number; logLevel?: LogLevel; config?: FastifyContextConfig & ContextConfig; constraints?: RouteConstraint, From f9c63991d1c1b97c66903299f3914456f867254b Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 26 Feb 2026 09:43:36 +0100 Subject: [PATCH 1265/1295] docs(security): clarify insecureHTTPParser threat model assumptions (#6533) --- SECURITY.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index fb011bd3a1a..f4802b4e6c0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ project and its official plugins. ## Threat Model Fastify's threat model extends the -[Node.js threat model](https://github.com/nodejs/node/blob/main/SECURITY.md#the-nodejs-threat-model). +[Node.js security policy](https://github.com/nodejs/node/blob/main/SECURITY.md). **Trusted:** Application code (plugins, handlers, hooks, schemas), configuration, and the runtime environment. @@ -14,6 +14,10 @@ and the runtime environment. **Untrusted:** All network input (HTTP headers, body, query strings, URL parameters). +Fastify assumes Node.js is running with `insecureHTTPParser: false` (the +secure default). Deployments that enable `insecureHTTPParser: true` are +outside Fastify's threat model. + ### Examples of Vulnerabilities - Parsing flaws that bypass validation or security controls @@ -36,6 +40,9 @@ patterns for routes or validation authorization (these are application-level concerns) - **Configuration mistakes**: Security issues arising from developer misconfiguration (configuration is trusted) +- **`insecureHTTPParser: true` deployments**: Reports that rely on enabling +Node.js `insecureHTTPParser` are out of scope; Fastify assumes this flag is +`false` - **Third-party dependencies**: Vulnerabilities in npm packages used by the application (not Fastify core dependencies) - **Resource exhaustion from handlers**: DoS caused by expensive operations in From 5c62eeb24cd2f920453131ea78334f97f68d6550 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 28 Feb 2026 07:49:31 +0000 Subject: [PATCH 1266/1295] chore(license): standardise license notice (#6511) See https://github.com/fastify/point-of-view/pull/497, easier to maintain if they all use the same style. Signed-off-by: Frazer Smith --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0ebc970ba29..266e719722c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-present The Fastify Team (members are listed in the README file) +Copyright (c) 2016-present The Fastify team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 8fad07272bf8042db8c0074c6fd40ed50fb6b0f7 Mon Sep 17 00:00:00 2001 From: slegarraga <64795732+slegarraga@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:28:58 -0300 Subject: [PATCH 1267/1295] docs: clarify anyOf nullable coercion behavior with primitive types (#6531) --- .../Reference/Validation-and-Serialization.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 1a1d8ee7dd8..7d3504bc282 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -329,6 +329,31 @@ server.setValidatorCompiler(req => { }) ``` +When type coercion is enabled, using `anyOf` with nullable primitive types +can produce unexpected results. For example, a value of `0` or `false` may be +coerced to `null` because Ajv evaluates `anyOf` schemas in order and applies +type coercion during matching. This means the `{ "type": "null" }` branch can +match before the intended type: + +```json +{ + "anyOf": [ + { "type": "null" }, + { "type": "number" } + ] +} +``` + +To avoid this, use the `nullable` keyword instead of `anyOf` for primitive +types: + +```json +{ + "type": "number", + "nullable": true +} +``` + For more information, see [Ajv Coercion](https://ajv.js.org/coercion.html). #### Ajv Plugins From 1e0be1294e85734f1e6ac651f3ff664d389d7799 Mon Sep 17 00:00:00 2001 From: super-mcgin <103895120+super-mcgin@users.noreply.github.com> Date: Sat, 28 Feb 2026 09:32:19 +0000 Subject: [PATCH 1268/1295] fix: remove format placeholder from FST_ERR_CTP_INVALID_MEDIA_TYPE message (#6528) --- lib/content-type-parser.js | 2 +- lib/errors.js | 2 +- test/content-parser.test.js | 26 +++++++++++++++++++++++++- test/internals/errors.test.js | 2 +- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/content-type-parser.js b/lib/content-type-parser.js index f83f010a8b2..6bb3260ce79 100644 --- a/lib/content-type-parser.js +++ b/lib/content-type-parser.js @@ -192,7 +192,7 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply } reply[kReplyIsError] = true - reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined)) + reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE()) return } diff --git a/lib/errors.js b/lib/errors.js index 670442b92bb..a5ce70ddd03 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -110,7 +110,7 @@ const codes = { ), FST_ERR_CTP_INVALID_MEDIA_TYPE: createError( 'FST_ERR_CTP_INVALID_MEDIA_TYPE', - 'Unsupported Media Type: %s', + 'Unsupported Media Type', 415 ), FST_ERR_CTP_INVALID_CONTENT_LENGTH: createError( diff --git a/test/content-parser.test.js b/test/content-parser.test.js index 09ee39da4ac..4c361cd2ca3 100644 --- a/test/content-parser.test.js +++ b/test/content-parser.test.js @@ -302,7 +302,7 @@ test('Error thrown 415 from content type is null and make post request to server t.plan(3) const fastify = Fastify() - const errMsg = new FST_ERR_CTP_INVALID_MEDIA_TYPE(undefined).message + const errMsg = new FST_ERR_CTP_INVALID_MEDIA_TYPE().message fastify.post('/', (req, reply) => { }) @@ -699,6 +699,30 @@ test('content-type regexp list should be cloned when plugin override', async t = } }) +test('invalid content-type error message should not contain format placeholder', (t, done) => { + t.plan(4) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send('ok') + }) + + fastify.inject({ + method: 'POST', + url: '/', + headers: { 'Content-Type': 'invalid-content-type' }, + body: 'test' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 415) + const body = JSON.parse(res.body) + t.assert.strictEqual(body.code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + t.assert.strictEqual(body.message, 'Unsupported Media Type') + done() + }) +}) + test('content-type fail when not a valid type', async t => { t.plan(1) diff --git a/test/internals/errors.test.js b/test/internals/errors.test.js index 9b0a8409754..e132f385cdf 100644 --- a/test/internals/errors.test.js +++ b/test/internals/errors.test.js @@ -165,7 +165,7 @@ test('FST_ERR_CTP_INVALID_MEDIA_TYPE', t => { const error = new errors.FST_ERR_CTP_INVALID_MEDIA_TYPE() t.assert.strictEqual(error.name, 'FastifyError') t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') - t.assert.strictEqual(error.message, 'Unsupported Media Type: %s') + t.assert.strictEqual(error.message, 'Unsupported Media Type') t.assert.strictEqual(error.statusCode, 415) t.assert.ok(error instanceof Error) }) From 3109f52a034c3287fe7ec9a687bc5d5695f0826a Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sun, 1 Mar 2026 13:44:31 +0000 Subject: [PATCH 1269/1295] docs(reference/hooks): fix note style (#6538) * docs(reference/hooks): fix note stlye Missed in #6521 Signed-off-by: Frazer Smith * Update docs/Reference/Hooks.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Frazer Smith --------- Signed-off-by: Frazer Smith Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/Reference/Hooks.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md index 24ecee84cd3..400b50d7ae8 100644 --- a/docs/Reference/Hooks.md +++ b/docs/Reference/Hooks.md @@ -284,7 +284,8 @@ service (if the `connectionTimeout` property is set on the Fastify instance). The `onTimeout` hook is executed when a request is timed out and the HTTP socket has been hung up. Therefore, you will not be able to send data to the client. -> Note: The `onTimeout` hook is triggered by socket-level timeouts set via +> ℹ️ Note: +> The `onTimeout` hook is triggered by socket-level timeouts set via > `connectionTimeout`. For application-level per-route timeouts, see the > [`handlerTimeout`](./Server.md#factory-handler-timeout) option which uses > `request.signal` for cooperative cancellation. From 3b4057303e63172ed8a4dde8d35f6ce1f7a5eb58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:28:14 +0000 Subject: [PATCH 1270/1295] chore: Bump lycheeverse/lychee-action from 2.7.0 to 2.8.0 (#6539) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/a8c4c7cb88f0c7386610c35eb25108e448569cb0...8646ba30535128ac92d33dfc9133794bfdd9b411) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-version: 2.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index b7cf6265f2d..e0aa89a0605 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -25,7 +25,7 @@ jobs: # See: https://github.com/lycheeverse/lychee-action/issues/17 - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # 2.7.0 + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # 2.8.0 with: fail: true # As external links behavior is not predictable, we check only internal links From 43c4e386c374c56a7de326e20f51eb7a71c0edda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:29:30 +0000 Subject: [PATCH 1271/1295] chore: Bump actions/dependency-review-action from 4.8.2 to 4.8.3 (#6540) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.8.2 to 4.8.3. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261...05fe4576374b728f0c523d6a13d64c25081e0803) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-version: 4.8.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61b81d07a89..5f13d0c0a81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Dependency review - uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 + uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3 check-licenses: name: Check licenses From 09b55b6ca6a3244df9f02b02157db41777198d4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:30:30 +0000 Subject: [PATCH 1272/1295] chore: Bump markdownlint-cli2 from 0.20.0 to 0.21.0 (#6542) Bumps [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) from 0.20.0 to 0.21.0. - [Changelog](https://github.com/DavidAnson/markdownlint-cli2/blob/main/CHANGELOG.md) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.20.0...v0.21.0) --- updated-dependencies: - dependency-name: markdownlint-cli2 dependency-version: 0.21.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8bf904df256..9eb0fb251fa 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "joi": "^18.0.1", "json-schema-to-ts": "^3.0.1", "JSONStream": "^1.3.5", - "markdownlint-cli2": "^0.20.0", + "markdownlint-cli2": "^0.21.0", "neostandard": "^0.12.0", "node-forge": "^1.3.1", "proxyquire": "^2.1.3", From 2590592da900172e04526f36dfe4dac617022469 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 1 Mar 2026 22:31:35 +0100 Subject: [PATCH 1273/1295] ci: remove broken links and add ecosystem link validator (#6421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: remove broken links and add ecosystem link validator - Remove 24 plugins with inaccessible GitHub repositories - Update 5 URLs that were redirecting to new locations - Add validation script (scripts/validate-ecosystem-links.js) - Add weekly GitHub Action to detect broken links Removed plugins (404/abandoned): - @fastify-userland/request-id, typeorm-query-runner, workflows - fastify-http-client, fastify-knexjs, fastify-knexjs-mock - fastify-mongo-memory, fastify-objectionjs, fastify-slonik - fastify-autocrud, fastify-file-upload, fastify-firebase - fastify-firebase-auth, fastify-nats, fastify-orientdb - fastify-socket.io, fastify-sse-v2, fastify-tokenize - fastify-twitch-ebs-tools, fastify-vite, typeorm-fastify-plugin Updated redirects: - zeit/next.js -> vercel/next.js - clerkinc/javascript -> clerk/javascript - nucleode/arecibo -> ducktors/arecibo - squaremo/amqp.node -> amqp-node/amqplib - Dev-Manny/fastify-appwrite -> maniecodes/fastify-appwrite * Update validate-ecosystem-links.js Co-authored-by: Frazer Smith Signed-off-by: Matteo Collina * Update validate-ecosystem-links.js Co-authored-by: Frazer Smith Signed-off-by: Matteo Collina * test: add tests for validate-ecosystem-links script - Refactor script to use fetch instead of https module for testability - Export functions for testing while maintaining CLI functionality - Add comprehensive tests using undici MockAgent for HTTP mocking - Test extractGitHubLinks with various markdown patterns - Test checkGitHubRepo for 200, 404, and error scenarios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * test(scripts): cover ecosystem link validator and exclude CLI entrypoint from c8 * chore(ci): resolve review feedback for ecosystem link validator * ci(links-check): scope external checks to changed markdown --------- Signed-off-by: Matteo Collina Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> Co-authored-by: Frazer Smith Co-authored-by: Claude Opus 4.5 Co-authored-by: Manuel Spigolon --- .github/workflows/links-check.yml | 20 +- .../workflows/validate-ecosystem-links.yml | 29 ++ docs/Guides/Ecosystem.md | 61 +--- scripts/validate-ecosystem-links.js | 179 +++++++++ test/scripts/validate-ecosystem-links.test.js | 339 ++++++++++++++++++ 5 files changed, 571 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/validate-ecosystem-links.yml create mode 100644 scripts/validate-ecosystem-links.js create mode 100644 test/scripts/validate-ecosystem-links.test.js diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index e0aa89a0605..5123940808a 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -20,6 +20,7 @@ jobs: uses: actions/checkout@v6 with: persist-credentials: false + fetch-depth: 0 # It will be possible to check only for the links in the changed files # See: https://github.com/lycheeverse/lychee-action/issues/17 @@ -35,10 +36,27 @@ jobs: env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Collect changed markdown files + id: changed-md + run: | + # docs/Guides/Ecosystem.md is checked by scripts/validate-ecosystem-links.js + changed_files=$(git diff --name-only --diff-filter=ACMRT "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" | grep -E '\.md$' | grep -v '^docs/Guides/Ecosystem\.md$' || true) + + if [ -z "$changed_files" ]; then + echo "files=" >> "$GITHUB_OUTPUT" + echo "No markdown files to check with linkinator." + exit 0 + fi + + files_as_space_list=$(echo "$changed_files" | tr '\n' ' ' | xargs) + echo "files=$files_as_space_list" >> "$GITHUB_OUTPUT" + echo "Checking markdown files: $files_as_space_list" + - name: External Link Checker + if: steps.changed-md.outputs.files != '' uses: JustinBeckwith/linkinator-action@f62ba0c110a76effb2ee6022cc6ce4ab161085e3 # 2.4.0 with: - paths: "*.md **/*.md" + paths: ${{ steps.changed-md.outputs.files }} retry: true redirects: error linksToSkip: "https://github.com/orgs/fastify/.*" diff --git a/.github/workflows/validate-ecosystem-links.yml b/.github/workflows/validate-ecosystem-links.yml new file mode 100644 index 00000000000..276402e74b4 --- /dev/null +++ b/.github/workflows/validate-ecosystem-links.yml @@ -0,0 +1,29 @@ +name: Validate Ecosystem Links + +on: + schedule: + # Run every Sunday at 00:00 UTC + - cron: '0 0 * * 0' + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate-links: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: Validate ecosystem links + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/validate-ecosystem-links.js diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 0fedae5fc7a..924bcf2fc31 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -79,7 +79,7 @@ section. connection plugin. - [`@fastify/nextjs`](https://github.com/fastify/fastify-nextjs) React server-side rendering support for Fastify with - [Next](https://github.com/zeit/next.js/). + [Next](https://github.com/vercel/next.js/). - [`@fastify/oauth2`](https://github.com/fastify/fastify-oauth2) Wrap around [`simple-oauth2`](https://github.com/lelylan/simple-oauth2). - [`@fastify/one-line-logger`](https://github.com/fastify/one-line-logger) Formats @@ -172,7 +172,7 @@ section. capabilities, and metrics tracking. - [`@blastorg/fastify-aws-dynamodb-cache`](https://github.com/blastorg/fastify-aws-dynamodb-cache) A plugin to help with caching API responses using AWS DynamoDB. -- [`@clerk/fastify`](https://github.com/clerkinc/javascript/tree/main/packages/fastify) +- [`@clerk/fastify`](https://github.com/clerk/javascript/tree/main/packages/fastify) Add authentication and user management to your Fastify application with Clerk. - [`@coobaha/typed-fastify`](https://github.com/Coobaha/typed-fastify) Strongly typed routes with a runtime validation using JSON schema generated from types. @@ -188,10 +188,6 @@ section. A Fastify plugin that protects against No(n)SQL injection by sanitizing data. - [`@exortek/remix-fastify`](https://github.com/ExorTek/remix-fastify) Fastify plugin for Remix. -- [`@fastify-userland/request-id`](https://github.com/fastify-userland/request-id) - Fastify Request ID Plugin -- [`@fastify-userland/typeorm-query-runner`](https://github.com/fastify-userland/typeorm-query-runner) - Fastify typeorm QueryRunner plugin - [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server) Tiny (~5k), Fast, KISS, and dependency-free Node.js library to make your Fastify API graceful. @@ -232,7 +228,7 @@ section. - [`apitally`](https://github.com/apitally/apitally-js) Fastify plugin to integrate with [Apitally](https://apitally.io/fastify), an API analytics, logging and monitoring tool. -- [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for +- [`arecibo`](https://github.com/ducktors/arecibo) Fastify ping responder for Kubernetes Liveness and Readiness Probes. - [`aws-xray-sdk-fastify`](https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/fastify) A Fastify plugin to log requests and subsegments through AWSXray. @@ -255,7 +251,7 @@ section. 405 responses for routes that have a handler but not for the request's method. - [`fastify-amqp`](https://github.com/RafaelGSS/fastify-amqp) Fastify AMQP connection plugin, to use with RabbitMQ or another connector. Just a wrapper - to [`amqplib`](https://github.com/squaremo/amqp.node). + to [`amqplib`](https://github.com/amqp-node/amqplib). - [`fastify-amqp-async`](https://github.com/kffl/fastify-amqp-async) Fastify AMQP plugin with a Promise-based API provided by [`amqplib-as-promised`](https://github.com/twawszczak/amqplib-as-promised). @@ -265,7 +261,7 @@ section. for Fastify - [`fastify-api-key`](https://github.com/arkerone/fastify-api-key) Fastify plugin to authenticate HTTP requests based on API key and signature -- [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify +- [`fastify-appwrite`](https://github.com/maniecodes/fastify-appwrite) Fastify Plugin for interacting with Appwrite server. - [`fastify-asyncforge`](https://github.com/mcollina/fastify-asyncforge) Plugin to access Fastify instance, logger, request and reply from Node.js [Async @@ -278,8 +274,6 @@ section. Auth0 verification plugin for Fastify, internally uses [fastify-jwt](https://npm.im/fastify-jwt) and [jsonwebtoken](https://npm.im/jsonwebtoken). -- [`fastify-autocrud`](https://github.com/paranoiasystem/fastify-autocrud) - Plugin to auto-generate CRUD routes as fast as possible. - [`fastify-autoroutes`](https://github.com/GiovanniCardamone/fastify-autoroutes) Plugin to scan and load routes based on filesystem path from a custom directory. @@ -374,15 +368,6 @@ section. [unleash](https://github.com/Unleash/unleash)). - [`fastify-file-routes`](https://github.com/spa5k/fastify-file-routes) Get Next.js based file system routing into fastify. -- [`fastify-file-upload`](https://github.com/huangang/fastify-file-upload) - Fastify plugin for uploading files. -- [`fastify-firebase`](https://github.com/now-ims/fastify-firebase) Fastify - plugin for [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup) - to Fastify so you can easily use Firebase Auth, Firestore, Cloud Storage, - Cloud Messaging, and more. -- [`fastify-firebase-auth`](https://github.com/oxsav/fastify-firebase-auth) - Firebase Authentication for Fastify supporting all of the methods relating to - the authentication API. - [`fastify-formidable`](https://github.com/climba03003/fastify-formidable) Handy plugin to provide multipart support and fastify-swagger integration. - [`fastify-gcloud-trace`](https://github.com/mkinoshi/fastify-gcloud-trace) @@ -420,8 +405,6 @@ section. [node-hl7-client](https://github.com/Bugs5382/node-hl7-client) and [node-hl7-server](https://github.com/Bugs5382/node-hl7-server) as the underlining technology to do this. -- [`fastify-http-client`](https://github.com/kenuyx/fastify-http-client) Plugin - to send HTTP(s) requests. Built upon [urllib](https://github.com/node-modules/urllib). - [`fastify-http-context`](https://github.com/thorough-developer/fastify-http-context) Fastify plugin for "simulating" a thread of execution to allow for true HTTP context to take place per API call within the Fastify lifecycle of calls. @@ -456,10 +439,6 @@ section. that adds support for KafkaJS - a modern Apache Kafka client library. - [`fastify-keycloak-adapter`](https://github.com/yubinTW/fastify-keycloak-adapter) A keycloak adapter for a Fastify app. -- [`fastify-knexjs`](https://github.com/chapuletta/fastify-knexjs) Fastify - plugin for supporting KnexJS Query Builder. -- [`fastify-knexjs-mock`](https://github.com/chapuletta/fastify-knexjs-mock) - Fastify Mock KnexJS for testing support. - [`fastify-koa`](https://github.com/rozzilla/fastify-koa) Convert Koa middlewares into Fastify plugins - [`fastify-kubernetes`](https://github.com/greguz/fastify-kubernetes) Fastify @@ -481,8 +460,6 @@ middlewares into Fastify plugins - [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua scripts with [fastify-redis](https://github.com/fastify/fastify-redis) and [lured](https://github.com/enobufs/lured). - A plugin to implement [Lyra](https://github.com/LyraSearch/lyra) search engine - on Fastify. - [`fastify-mailer`](https://github.com/coopflow/fastify-mailer) Plugin to initialize and encapsulate [Nodemailer](https://nodemailer.com)'s transporters instances in Fastify. @@ -495,8 +472,6 @@ middlewares into Fastify plugins exporting [Prometheus](https://prometheus.io) metrics. - [`fastify-minify`](https://github.com/Jelenkee/fastify-minify) Plugin for minification and transformation of responses. -- [`fastify-mongo-memory`](https://github.com/chapuletta/fastify-mongo-memory) - Fastify MongoDB in Memory Plugin for testing support. - [`fastify-mongodb-sanitizer`](https://github.com/KlemenKozelj/fastify-mongodb-sanitizer) Fastify plugin that sanitizes client input to prevent potential MongoDB query injection attacks. @@ -516,8 +491,6 @@ middlewares into Fastify plugins for handling multipart/form-data, which is primarily used for uploading files. - [`fastify-multilingual`](https://github.com/gbrugger/fastify-multilingual) Unobtrusively decorates fastify request with Polyglot.js for i18n. -- [`fastify-nats`](https://github.com/mahmed8003/fastify-nats) Plugin to share - [NATS](https://nats.io) client across Fastify. - [`fastify-next-auth`](https://github.com/wobsoriano/fastify-next-auth) NextAuth.js plugin for Fastify. - [`fastify-no-additional-properties`](https://github.com/greguz/fastify-no-additional-properties) @@ -531,9 +504,6 @@ middlewares into Fastify plugins rendering support for Fastify with Nuxt.js Framework. - [`fastify-oas`](https://gitlab.com/m03geek/fastify-oas) Generates OpenAPI 3.0+ documentation from routes schemas for Fastify. -- [`fastify-objectionjs`](https://github.com/jarcodallo/fastify-objectionjs) - Plugin for the Fastify framework that provides integration with objectionjs - ORM. - [`fastify-objectionjs-classes`](https://github.com/kamikazechaser/fastify-objectionjs-classes) Plugin to cherry-pick classes from objectionjs ORM. - [`fastify-opaque-apake`](https://github.com/squirrelchat/fastify-opaque-apake) @@ -552,9 +522,6 @@ middlewares into Fastify plugins [`oracledb`](https://github.com/oracle/node-oracledb) connection pool to a Fastify server instance. - [`fastify-orama`](https://github.com/mateonunez/fastify-orama) -- [`fastify-orientdb`](https://github.com/mahmed8003/fastify-orientdb) Fastify - OrientDB connection plugin, with which you can share the OrientDB connection - across every part of your server. - [`fastify-osm`](https://github.com/gzileni/fastify-osm) Fastify OSM plugin to run overpass queries by OpenStreetMap. - [`fastify-override`](https://github.com/matthyk/fastify-override) @@ -647,12 +614,8 @@ middlewares into Fastify plugins Fastify plugin for sending emails via AWS SES using AWS SDK v3. - [`fastify-shared-schema`](https://github.com/Adibla/fastify-shared-schema) Plugin for sharing schemas between different routes. -- [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik - plugin, with this you can use slonik in every part of your server. - [`fastify-slow-down`](https://github.com/nearform/fastify-slow-down) A plugin to delay the response from the server. -- [`fastify-socket.io`](https://github.com/alemagio/fastify-socket.io) a - Socket.io plugin for Fastify. - [`fastify-split-validator`](https://github.com/MetCoder95/fastify-split-validator) Small plugin to allow you use multiple validators in one route based on each HTTP part of the request. @@ -662,8 +625,6 @@ middlewares into Fastify plugins your application to a SQLite database with full Typescript support. - [`fastify-sse`](https://github.com/lolo32/fastify-sse) to provide Server-Sent Events with `reply.sse( … )` to Fastify. -- [`fastify-sse-v2`](https://github.com/nodefactoryio/fastify-sse-v2) to provide - Server-Sent Events using Async Iterators (supports newer versions of Fastify). - [`fastify-ssr-vite`](https://github.com/nineohnine/fastify-ssr-vite) A simple plugin for setting up server side rendering with vite. - [`fastify-stripe`](https://github.com/coopflow/fastify-stripe) Plugin to @@ -675,14 +636,8 @@ middlewares into Fastify plugins - [`fastify-tls-keygen`](https://gitlab.com/sebdeckers/fastify-tls-keygen) Automatically generate a browser-compatible, trusted, self-signed, localhost-only, TLS certificate. -- [`fastify-tokenize`](https://github.com/Bowser65/fastify-tokenize) - [Tokenize](https://github.com/Bowser65/Tokenize) plugin for Fastify that - removes the pain of managing authentication tokens, with built-in integration - for `fastify-auth`. - [`fastify-totp`](https://github.com/beliven-it/fastify-totp) A plugin to handle TOTP (e.g. for 2FA). -- [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools) - Useful functions for Twitch Extension Backend Services (EBS). - [`fastify-type-provider-effect-schema`](https://github.com/daotl/fastify-type-provider-effect-schema) Fastify [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/) @@ -701,8 +656,6 @@ middlewares into Fastify plugins subdomain HTTP requests to another server (useful if you want to point multiple subdomains to the same IP address, while running different servers on the same machine). -- [`fastify-vite`](https://github.com/galvez/fastify-vite) - [Vite](https://vitejs.dev/) plugin for Fastify with SSR data support. - [`fastify-vue-plugin`](https://github.com/TheNoim/fastify-vue) [Nuxt.js](https://nuxtjs.org) plugin for Fastify. Control the routes nuxt should use. @@ -745,13 +698,9 @@ middlewares into Fastify plugins plugin to easily create Google Cloud PubSub endpoints. - [`sequelize-fastify`](https://github.com/hsynlms/sequelize-fastify) A simple and lightweight Sequelize plugin for Fastify. -- [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple - and updated Typeorm plugin for use with Fastify. #### [Community Tools](#community-tools) -- [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows) - Reusable workflows for use in the Fastify plugin - [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration generator by directory structure. - [`fastify-flux`](https://github.com/Jnig/fastify-flux) Tool for building diff --git a/scripts/validate-ecosystem-links.js b/scripts/validate-ecosystem-links.js new file mode 100644 index 00000000000..d99e1533622 --- /dev/null +++ b/scripts/validate-ecosystem-links.js @@ -0,0 +1,179 @@ +#!/usr/bin/env node + +'use strict' +/** + * Script to validate GitHub links in the Ecosystem.md file + * Checks if repositories are accessible or return 404 + * + * Usage: + * node validate-ecosystem-links.js + * + * Environment variables: + * GITHUB_TOKEN - Optional GitHub token for higher rate limits + */ + +const fs = require('node:fs') +const path = require('node:path') + +const ECOSYSTEM_FILE = path.join(__dirname, '../docs/Guides/Ecosystem.md') +const GITHUB_OWNER_REGEX = /^[a-z\d](?:[a-z\d-]{0,38})$/i +const GITHUB_REPO_REGEX = /^[a-z\d._-]+$/i + +function getGitHubToken () { + return process.env.GITHUB_TOKEN +} + +function isValidGitHubReference (owner, repo) { + return GITHUB_OWNER_REGEX.test(owner) && GITHUB_REPO_REGEX.test(repo) +} + +function extractGitHubLinks (content) { + const regex = /\[([^\]]+)\]\((https:\/\/github\.com\/([^/]+)\/([^/)]+)[^)]*)\)/g + const links = [] + let match + + while ((match = regex.exec(content)) !== null) { + links.push({ + name: match[1], + url: match[2], + owner: match[3], + repo: match[4].replace(/[#?].*$/, '') + }) + } + + return links +} + +async function checkGitHubRepo (owner, repo, retries = 3) { + if (!isValidGitHubReference(owner, repo)) { + return { + owner, + repo, + status: 'invalid', + exists: false, + error: 'Invalid GitHub repository identifier' + } + } + + const headers = { + 'User-Agent': 'fastify-ecosystem-validator' + } + + const githubToken = getGitHubToken() + if (githubToken) { + headers.Authorization = `token ${githubToken}` + } + + try { + const response = await fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, { + method: 'HEAD', + headers + }) + + // Retry on rate limit (403) with exponential backoff + if (response.status === 403 && retries > 0) { + const delay = (4 - retries) * 2000 // 2s, 4s, 6s + await new Promise(resolve => setTimeout(resolve, delay)) + return checkGitHubRepo(owner, repo, retries - 1) + } + + return { + owner, + repo, + status: response.status, + exists: response.status === 200 + } + } catch (err) { + return { + owner, + repo, + status: 'error', + exists: false, + error: err.message + } + } +} + +async function validateAllLinks () { + console.log('Reading Ecosystem.md...\n') + const content = fs.readFileSync(ECOSYSTEM_FILE, 'utf8') + const links = extractGitHubLinks(content) + + // Deduplicate by owner/repo + const seen = new Set() + const uniqueLinks = links.filter(link => { + const key = `${link.owner}/${link.repo}`.toLowerCase() + if (seen.has(key)) return false + seen.add(key) + return true + }) + + console.log(`Found ${uniqueLinks.length} unique GitHub links to check:\n`) + + const results = [] + let checked = 0 + + for (const link of uniqueLinks) { + checked++ + process.stdout.write(`\r[${checked}/${uniqueLinks.length}] Checking: ${link.owner}/${link.repo}...`.padEnd(80)) + const result = await checkGitHubRepo(link.owner, link.repo) + results.push({ ...link, ...result }) + + // Rate limiting - wait a bit between requests + await new Promise(resolve => setTimeout(resolve, 200)) + } + + console.log('\n\n========== VALIDATION RESULTS ==========\n') + + const notFound = results.filter(r => !r.exists) + const found = results.filter(r => r.exists) + + if (notFound.length > 0) { + console.log('INACCESSIBLE (should be removed):') + console.log('-'.repeat(50)) + notFound.forEach(r => { + console.log(` [${r.status}] ${r.owner}/${r.repo}`) + console.log(` ${r.url}`) + }) + console.log() + } + + if (found.length > 0) { + console.log('ACCESSIBLE (kept):') + console.log('-'.repeat(50)) + found.forEach(r => { + console.log(` [${r.status}] ${r.owner}/${r.repo}`) + }) + console.log() + } + + console.log('========== SUMMARY ==========') + console.log(`Total links checked: ${results.length}`) + console.log(`Inaccessible: ${notFound.length}`) + console.log(`Accessible: ${found.length}`) + + return { notFound, found } +} + +// Export functions for testing +module.exports = { + extractGitHubLinks, + checkGitHubRepo, + validateAllLinks +} + +// Run if executed directly +/* c8 ignore start */ +if (require.main === module) { + validateAllLinks() + .then(({ notFound }) => { + if (notFound.length > 0) { + process.exit(1) + } + }) + .catch((err) => { + console.error(err) + process.exit(1) + }) +} +/* c8 ignore stop */ diff --git a/test/scripts/validate-ecosystem-links.test.js b/test/scripts/validate-ecosystem-links.test.js new file mode 100644 index 00000000000..9c98b291c5e --- /dev/null +++ b/test/scripts/validate-ecosystem-links.test.js @@ -0,0 +1,339 @@ +'use strict' + +const { describe, it, beforeEach, afterEach } = require('node:test') +const assert = require('node:assert') +const fs = require('node:fs') +const { MockAgent, setGlobalDispatcher, getGlobalDispatcher } = require('undici') + +function loadValidateEcosystemLinksModule () { + const modulePath = require.resolve('../../scripts/validate-ecosystem-links') + delete require.cache[modulePath] + return require(modulePath) +} + +describe('extractGitHubLinks', () => { + const { extractGitHubLinks } = loadValidateEcosystemLinksModule() + + it('extracts simple GitHub repository links', () => { + const content = ` +# Ecosystem + +- [fastify-helmet](https://github.com/fastify/fastify-helmet) - Important security headers for Fastify +- [fastify-cors](https://github.com/fastify/fastify-cors) - CORS support +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 2) + assert.deepStrictEqual(links[0], { + name: 'fastify-helmet', + url: 'https://github.com/fastify/fastify-helmet', + owner: 'fastify', + repo: 'fastify-helmet' + }) + assert.deepStrictEqual(links[1], { + name: 'fastify-cors', + url: 'https://github.com/fastify/fastify-cors', + owner: 'fastify', + repo: 'fastify-cors' + }) + }) + + it('extracts links with different owner/repo combinations', () => { + const content = ` +- [some-plugin](https://github.com/user123/awesome-plugin) +- [another-lib](https://github.com/org-name/lib-name) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 2) + assert.strictEqual(links[0].owner, 'user123') + assert.strictEqual(links[0].repo, 'awesome-plugin') + assert.strictEqual(links[1].owner, 'org-name') + assert.strictEqual(links[1].repo, 'lib-name') + }) + + it('handles links with hash fragments', () => { + const content = ` +- [project](https://github.com/owner/repo#readme) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 1) + assert.strictEqual(links[0].repo, 'repo') + assert.strictEqual(links[0].url, 'https://github.com/owner/repo#readme') + }) + + it('handles links with query parameters', () => { + const content = ` +- [project](https://github.com/owner/repo?tab=readme) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 1) + assert.strictEqual(links[0].repo, 'repo') + }) + + it('handles links with subpaths', () => { + const content = ` +- [docs](https://github.com/owner/repo/tree/main/docs) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 1) + assert.strictEqual(links[0].owner, 'owner') + assert.strictEqual(links[0].repo, 'repo') + }) + + it('returns empty array for content with no GitHub links', () => { + const content = ` +# No GitHub links here + +Just some regular text and [a link](https://example.com). +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 0) + }) + + it('ignores non-GitHub links', () => { + const content = ` +- [gitlab](https://gitlab.com/owner/repo) +- [github](https://github.com/owner/repo) +- [bitbucket](https://bitbucket.org/owner/repo) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 1) + assert.strictEqual(links[0].owner, 'owner') + }) + + it('extracts multiple links from complex markdown', () => { + const content = ` +## Category 1 + +Some description [inline link](https://github.com/a/b). + +| Plugin | Description | +|--------|-------------| +| [plugin1](https://github.com/x/y) | Desc 1 | +| [plugin2](https://github.com/z/w) | Desc 2 | +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 3) + }) +}) + +describe('checkGitHubRepo', () => { + let originalDispatcher + let mockAgent + let originalFetch + let originalSetTimeout + + beforeEach(() => { + delete process.env.GITHUB_TOKEN + originalDispatcher = getGlobalDispatcher() + mockAgent = new MockAgent() + mockAgent.disableNetConnect() + setGlobalDispatcher(mockAgent) + originalFetch = global.fetch + originalSetTimeout = global.setTimeout + }) + + afterEach(async () => { + global.fetch = originalFetch + global.setTimeout = originalSetTimeout + setGlobalDispatcher(originalDispatcher) + await mockAgent.close() + delete process.env.GITHUB_TOKEN + }) + + it('returns exists: true for status 200', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + const mockPool = mockAgent.get('https://api.github.com') + mockPool.intercept({ + path: '/repos/fastify/fastify', + method: 'HEAD' + }).reply(200) + + const result = await checkGitHubRepo('fastify', 'fastify') + + assert.strictEqual(result.exists, true) + assert.strictEqual(result.status, 200) + assert.strictEqual(result.owner, 'fastify') + assert.strictEqual(result.repo, 'fastify') + }) + + it('returns exists: false for status 404', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + const mockPool = mockAgent.get('https://api.github.com') + mockPool.intercept({ + path: '/repos/nonexistent/repo', + method: 'HEAD' + }).reply(404) + + const result = await checkGitHubRepo('nonexistent', 'repo') + + assert.strictEqual(result.exists, false) + assert.strictEqual(result.status, 404) + }) + + it('returns invalid status for malformed owner or repository names', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + let called = false + + global.fetch = async () => { + called = true + return { status: 200 } + } + + const result = await checkGitHubRepo('owner/evil', 'repo', 1) + + assert.strictEqual(called, false) + assert.strictEqual(result.exists, false) + assert.strictEqual(result.status, 'invalid') + assert.strictEqual(result.error, 'Invalid GitHub repository identifier') + }) + + it('retries on rate limit responses', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + let attempts = 0 + + global.setTimeout = (fn) => { + fn() + return 0 + } + + global.fetch = async () => { + attempts++ + return { + status: attempts === 1 ? 403 : 200 + } + } + + const result = await checkGitHubRepo('owner', 'repo', 1) + + assert.strictEqual(attempts, 2) + assert.strictEqual(result.exists, true) + assert.strictEqual(result.status, 200) + }) + + it('adds authorization header when GITHUB_TOKEN is set', async () => { + process.env.GITHUB_TOKEN = 'my-token' + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + let authorization + + global.fetch = async (url, options) => { + authorization = options.headers.Authorization + return { + status: 200 + } + } + + const result = await checkGitHubRepo('owner', 'repo') + + assert.strictEqual(authorization, 'token my-token') + assert.strictEqual(result.exists, true) + }) + + it('handles network errors', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + const mockPool = mockAgent.get('https://api.github.com') + mockPool.intercept({ + path: '/repos/owner/repo', + method: 'HEAD' + }).replyWithError(new Error('Network error')) + + const result = await checkGitHubRepo('owner', 'repo') + + assert.strictEqual(result.exists, false) + assert.strictEqual(result.status, 'error') + assert.ok(result.error.length > 0) + }) +}) + +describe('validateAllLinks', () => { + let originalReadFileSync + let originalFetch + let originalSetTimeout + let originalConsoleLog + let originalStdoutWrite + + beforeEach(() => { + originalReadFileSync = fs.readFileSync + originalFetch = global.fetch + originalSetTimeout = global.setTimeout + originalConsoleLog = console.log + originalStdoutWrite = process.stdout.write + + console.log = () => {} + process.stdout.write = () => true + + global.setTimeout = (fn) => { + fn() + return 0 + } + }) + + afterEach(() => { + fs.readFileSync = originalReadFileSync + global.fetch = originalFetch + global.setTimeout = originalSetTimeout + console.log = originalConsoleLog + process.stdout.write = originalStdoutWrite + }) + + it('validates links, deduplicates repositories and groups inaccessible links', async () => { + const { validateAllLinks } = loadValidateEcosystemLinksModule() + + fs.readFileSync = () => ` +- [repo one](https://github.com/owner/repo) +- [repo one duplicate](https://github.com/owner/repo) +- [repo two](https://github.com/another/project) +` + + let requests = 0 + global.fetch = async (url) => { + requests++ + const pathname = new URL(url).pathname + + if (pathname === '/repos/owner/repo') { + return { status: 404 } + } + + if (pathname === '/repos/another/project') { + return { status: 200 } + } + + throw new Error(`Unexpected url: ${url}`) + } + + const result = await validateAllLinks() + + assert.strictEqual(requests, 2) + assert.strictEqual(result.notFound.length, 1) + assert.strictEqual(result.found.length, 1) + assert.strictEqual(result.notFound[0].owner, 'owner') + assert.strictEqual(result.notFound[0].repo, 'repo') + assert.strictEqual(result.found[0].owner, 'another') + assert.strictEqual(result.found[0].repo, 'project') + }) + + it('returns empty result when no GitHub links are present', async () => { + const { validateAllLinks } = loadValidateEcosystemLinksModule() + + fs.readFileSync = () => '# Ecosystem\nNo links here.' + + let requests = 0 + global.fetch = async () => { + requests++ + return { status: 200 } + } + + const result = await validateAllLinks() + + assert.strictEqual(requests, 0) + assert.strictEqual(result.notFound.length, 0) + assert.strictEqual(result.found.length, 0) + }) +}) From cd58ed45d3c3b25cae3cfaa27e9c76a1c8865d0f Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 4 Mar 2026 07:12:53 +0000 Subject: [PATCH 1274/1295] ci(validate-ecoystem-links): add job level permission (#6545) Signed-off-by: Frazer Smith --- .github/workflows/validate-ecosystem-links.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/validate-ecosystem-links.yml b/.github/workflows/validate-ecosystem-links.yml index 276402e74b4..a841914dfa5 100644 --- a/.github/workflows/validate-ecosystem-links.yml +++ b/.github/workflows/validate-ecosystem-links.yml @@ -12,6 +12,8 @@ permissions: jobs: validate-links: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Check out repo uses: actions/checkout@v6 From bbdfe82ae891199ba0d2b49326b7ddce5f103ab3 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 4 Mar 2026 07:36:14 +0000 Subject: [PATCH 1275/1295] style: remove trailing whitespace (#6543) --- .github/workflows/citgm-package.yml | 2 +- .github/workflows/citgm.yml | 2 +- CONTRIBUTING.md | 2 +- docs/Guides/Contributing.md | 2 +- docs/Guides/Ecosystem.md | 5 ++--- docs/Guides/Migration-Guide-V5.md | 2 +- docs/Reference/LTS.md | 2 +- docs/Reference/Server.md | 18 +++++++++--------- docs/Reference/TypeScript.md | 2 +- 9 files changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml index 5f81d7d64e9..4658431c2dc 100644 --- a/.github/workflows/citgm-package.yml +++ b/.github/workflows/citgm-package.yml @@ -7,7 +7,7 @@ on: description: 'Package to test' required: true type: string - + node-version: description: 'Node version to test' required: true diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml index 38bf72f212f..535fdcc7194 100644 --- a/.github/workflows/citgm.yml +++ b/.github/workflows/citgm.yml @@ -3,7 +3,7 @@ name: CITGM on: pull_request: types: [labeled] - + workflow_dispatch: inputs: node-version: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c6d8c9e629..6f1e68b702c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -108,7 +108,7 @@ the following tasks: 5. Read the [pinned announcements](https://github.com/orgs/fastify/discussions/categories/announcements) to be updated with the organization’s news. 6. The person who does the onboarding must open a pull request to - [`fastify/org-admin`](https://github.com/fastify/org-admin?tab=readme-ov-file#org-admin) + [`fastify/org-admin`](https://github.com/fastify/org-admin?tab=readme-ov-file#org-admin) so an admin can add the new member to the [npm org](https://www.npmjs.com/org/fastify) and the GitHub Team, so that the new joiner can help maintain the official plugins. diff --git a/docs/Guides/Contributing.md b/docs/Guides/Contributing.md index afea9ace780..d2df29e935d 100644 --- a/docs/Guides/Contributing.md +++ b/docs/Guides/Contributing.md @@ -7,7 +7,7 @@ us. > ## Note > This is an informal guide. For full details, please review the formal -> [CONTRIBUTING +> [CONTRIBUTING > document](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md) > our [Developer Certificate of > Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin). diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 924bcf2fc31..bb4afad83ec 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -201,7 +201,7 @@ section. Minimalistic and opinionated plugin that collects usage/process metrics and dispatches to [statsd](https://github.com/statsd/statsd). - [`@inaiat/fastify-papr`](https://github.com/inaiat/fastify-papr) - A plugin to integrate [Papr](https://github.com/plexinc/papr), + A plugin to integrate [Papr](https://github.com/plexinc/papr), the MongoDB ORM for TypeScript & MongoDB, with Fastify. - [`@jerome1337/fastify-enforce-routes-pattern`](https://github.com/Jerome1337/fastify-enforce-routes-pattern) A Fastify plugin that enforces naming pattern for routes path. @@ -593,7 +593,7 @@ middlewares into Fastify plugins - [`fastify-route-group`](https://github.com/TakNePoidet/fastify-route-group) Convenient grouping and inheritance of routes. - [`fastify-route-preset`](https://github.com/inyourtime/fastify-route-preset) - A Fastify plugin that enables you to create route configurations that can be + A Fastify plugin that enables you to create route configurations that can be applied to multiple routes. - [`fastify-s3-buckets`](https://github.com/kibertoad/fastify-s3-buckets) Ensure the existence of defined S3 buckets on the application startup. @@ -714,4 +714,3 @@ middlewares into Fastify plugins Fastify plugin for Vite with Hot-module Replacement. - [`vite-plugin-fastify-routes`](https://github.com/Vanilla-IceCream/vite-plugin-fastify-routes) File-based routing for Fastify applications using Vite. - diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md index ebc6b266378..9c8ad875a3c 100644 --- a/docs/Guides/Migration-Guide-V5.md +++ b/docs/Guides/Migration-Guide-V5.md @@ -550,7 +550,7 @@ so you should have already updated your code. The updated AJV compiler updates `ajv-formats` which now enforce the use of timezone in `time` and `date-time` format. A workaround is to use `iso-time` and `iso-date-time` formats -which support an optional timezone for backwards compatibility. +which support an optional timezone for backwards compatibility. See the [full discussion](https://github.com/fastify/fluent-json-schema/issues/267). diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md index 70984581c63..2e59c16726d 100644 --- a/docs/Reference/LTS.md +++ b/docs/Reference/LTS.md @@ -38,7 +38,7 @@ A "month" is defined as 30 consecutive days. > occasions where we need to release breaking changes as a _minor_ version > release. Such changes will _always_ be noted in the [release > notes](https://github.com/fastify/fastify/releases). -> +> > To avoid automatically receiving breaking security updates it is possible to > use the tilde (`~`) range qualifier. For example, to get patches for the 3.15 > release, and avoid automatically updating to the 3.16 release, specify the diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 28a7e6873f9..3a3426f3847 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -587,8 +587,8 @@ controls [avvio](https://www.npmjs.com/package/avvio) 's `timeout` parameter. ### `querystringParser` -The default query string parser that Fastify uses is a more performant fork -of Node.js's core `querystring` module called +The default query string parser that Fastify uses is a more performant fork +of Node.js's core `querystring` module called [`fast-querystring`](https://github.com/anonrig/fast-querystring). You can use this option to use a custom parser, such as @@ -817,7 +817,7 @@ function rewriteUrl (req) { Fastify uses [`find-my-way`](https://github.com/delvedor/find-my-way) for its -HTTP router. The `routerOptions` parameter allows passing +HTTP router. The `routerOptions` parameter allows passing [`find-my-way` options](https://github.com/delvedor/find-my-way?tab=readme-ov-file#findmywayoptions) to customize the HTTP router within Fastify. @@ -841,8 +841,8 @@ fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => { Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which -supports, `buildPrettyMeta` where you can assign a `buildPrettyMeta` -function to sanitize a route's store object to use with the `prettyPrint` +supports, `buildPrettyMeta` where you can assign a `buildPrettyMeta` +function to sanitize a route's store object to use with the `prettyPrint` functions. This function should accept a single object and return an object. ```js @@ -1102,11 +1102,11 @@ fastify.get('/dev', async (request, reply) => { * **Default:** `true` > ⚠ Warning: -> This option will be set to `false` by default +> This option will be set to `false` by default > in the next major release. -When set to `false`, it prevents `setErrorHandler` from being called -multiple times within the same scope, ensuring that the previous error +When set to `false`, it prevents `setErrorHandler` from being called +multiple times within the same scope, ensuring that the previous error handler is not unintentionally overridden. #### Example of incorrect usage: @@ -1851,7 +1851,7 @@ set it to 500 before calling the error handler. - not found (404) errors. Use [`setNotFoundHandler`](#set-not-found-handler) instead. - Stream errors thrown during piping into the response socket, as - headers/response were already sent to the client. + headers/response were already sent to the client. Use custom in-stream data to signal such errors. ```js diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md index 5e2aaef91c0..56e0e39c491 100644 --- a/docs/Reference/TypeScript.md +++ b/docs/Reference/TypeScript.md @@ -1635,7 +1635,7 @@ executed in the request lifecycle. The previous hook was `preSerialization`, the next hook will be `onResponse`. > ℹ️ Note: -> If you change the payload, you may only change it to a string, +> If you change the payload, you may only change it to a string, > a Buffer, a stream, or null. ##### fastify.onResponseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void From 9b06a781f90d5a8d5ac5fc43eb2fddc392864681 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 5 Mar 2026 10:29:36 +0100 Subject: [PATCH 1276/1295] Bumped v5.8.0 Signed-off-by: Matteo Collina --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9eb0fb251fa..2680ade28d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.7.4", + "version": "5.8.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 161578a366edd40ef6d7eb06bfd57bc2b55f37b6 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 5 Mar 2026 10:53:46 +0100 Subject: [PATCH 1277/1295] chore: sync version Signed-off-by: Matteo Collina --- fastify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastify.js b/fastify.js index 67f6c9f90fd..756fa01a64f 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.7.4' +const VERSION = '5.8.0' const Avvio = require('avvio') const http = require('node:http') From 67f6c9b32cb3623d3c9470cc17ed830dd2f083d7 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Thu, 5 Mar 2026 04:54:15 -0500 Subject: [PATCH 1278/1295] Merge commit from fork --- lib/content-type.js | 12 +++++++++--- test/content-type.test.js | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/lib/content-type.js b/lib/content-type.js index 56f722fa333..db548de2efb 100644 --- a/lib/content-type.js +++ b/lib/content-type.js @@ -11,7 +11,9 @@ const keyValuePairsReg = /([\w!#$%&'*+.^`|~-]+)=([^;]*)/gm /** * typeNameReg is used to validate that the first part of the media-type - * does not use disallowed characters. + * does not use disallowed characters. Types must consist solely of + * characters that match the specified character class. It must terminate + * with a matching character. * * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators * @type {RegExp} @@ -20,12 +22,16 @@ const typeNameReg = /^[\w!#$%&'*+.^`|~-]+$/ /** * subtypeNameReg is used to validate that the second part of the media-type - * does not use disallowed characters. + * does not use disallowed characters. Subtypes must consist solely of + * characters that match the specified character class, and optionally + * terminated with any amount of whitespace characters. Without the terminating + * anchor (`$`), the regular expression will match the leading portion of a + * string instead of the whole string. * * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators * @type {RegExp} */ -const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*/ +const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*$/ /** * ContentType parses and represents the value of the content-type header. diff --git a/test/content-type.test.js b/test/content-type.test.js index a2997e4eeda..2d037c60e26 100644 --- a/test/content-type.test.js +++ b/test/content-type.test.js @@ -74,6 +74,44 @@ describe('ContentType class', () => { found = new ContentType('foo/π; param=1') t.assert.equal(found.isEmpty, true) t.assert.equal(found.isValid, false) + + found = new ContentType('application/json') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json/extra/slashes') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json(garbage)') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json@evil') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json\x00garbage') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + }) + + test('subtype with multiple fields validates as incorrect', (t) => { + let found = new ContentType('application/json whatever') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/ json whatever') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/json whatever; foo=bar') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/ json whatever; foo=bar') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) }) test('returns a plain media type instance', (t) => { From 073ff8132f350aaa53935231019499e983cb9794 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 5 Mar 2026 10:54:53 +0100 Subject: [PATCH 1279/1295] Bumped v5.8.1 Signed-off-by: Matteo Collina --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 756fa01a64f..fdde55f835b 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.8.0' +const VERSION = '5.8.1' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 2680ade28d9..ae313b4f5ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.8.0", + "version": "5.8.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From b61c362cc9fba35e7e060a71284154e4f86d54f4 Mon Sep 17 00:00:00 2001 From: yeliex Date: Fri, 6 Mar 2026 15:58:00 +0800 Subject: [PATCH 1280/1295] docs(ecosystem): add @yeliex/fastify-problem-details (#6546) Co-authored-by: Frazer Smith --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index bb4afad83ec..549b967e39f 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -225,6 +225,8 @@ section. Beautiful OpenAPI/Swagger API references for Fastify - [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs) SeaweedFS for Fastify +- [`@yeliex/fastify-problem-details`](https://github.com/yeliex/fastify-problem-details) + RFC 9457 Problem Details implementation for Fastify, with typed HTTP errors. - [`apitally`](https://github.com/apitally/apitally-js) Fastify plugin to integrate with [Apitally](https://apitally.io/fastify), an API analytics, logging and monitoring tool. From cdcc4de5ee7c91e9214df775c2a266c38098f685 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Sat, 7 Mar 2026 14:08:56 +0800 Subject: [PATCH 1281/1295] Revert "chore: upgrade borp to v1.0.0 (#6510)" (#6564) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae313b4f5ef..04bd9519cd9 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "ajv-i18n": "^4.2.0", "ajv-merge-patch": "^5.0.1", "autocannon": "^8.0.0", - "borp": "^1.0.0", + "borp": "^0.21.0", "branch-comparer": "^1.1.0", "concurrently": "^9.1.2", "cross-env": "^10.0.0", From 0d3b560247322cc4afa7d869750a88f9a06b5292 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 7 Mar 2026 08:46:53 +0100 Subject: [PATCH 1282/1295] docs: document body validation with custom content type parsers (#6556) --- docs/Reference/ContentTypeParser.md | 7 ++++ .../Reference/Validation-and-Serialization.md | 39 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md index c2b05e59e44..8f92873da43 100644 --- a/docs/Reference/ContentTypeParser.md +++ b/docs/Reference/ContentTypeParser.md @@ -17,6 +17,13 @@ declared in a plugin, it is available only in that scope and its children. Fastify automatically adds the parsed request payload to the [Fastify request](./Request.md) object, accessible via `request.body`. +> **Important:** When using a body schema with the +> [`content`](./Validation-and-Serialization.md#body-content-type-validation) +> property to validate per content type, only content types listed in the schema +> will be validated. If you add a custom content type parser but do not include +> its content type in the body schema's `content` property, the incoming data +> will be parsed but **not validated**. + Note that for `GET` and `HEAD` requests, the payload is never parsed. For `OPTIONS` and `DELETE` requests, the payload is parsed only if a valid `content-type` header is provided. Unlike `POST`, `PUT`, and `PATCH`, the diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 7d3504bc282..204492157a2 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -5,7 +5,11 @@ Fastify uses a schema-based approach. We recommend using [JSON Schema](https://json-schema.org/) to validate routes and serialize outputs. Fastify compiles the schema into a highly performant function. -Validation is only attempted if the content type is `application/json`. +Validation is only attempted if the content type is `application/json`, +unless the body schema uses the [`content`](#body-content-type-validation) +property to specify validation per content type. When the body schema defines +a `content` field, it must enumerate all possible content types the +application expects to handle with the associated handler. All examples use the [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7) @@ -228,6 +232,9 @@ const schema = { fastify.post('/the/url', { schema }, handler) ``` +#### Body Content-Type Validation + + For `body` schema, it is further possible to differentiate the schema per content type by nesting the schemas inside `content` property. The schema validation will be applied based on the `Content-Type` header in the request. @@ -250,6 +257,36 @@ fastify.post('/the/url', { }, handler) ``` +> **Important:** When using [custom content type +> parsers](./ContentTypeParser.md), the parsed body will **only** be validated +> if the request's content type is listed in the `content` object above. If +> a parser for a content type (e.g., `application/yaml`) is defined, +> but it is not not included in the body schema's `content` property, +> the incoming data will be parsed but **not validated**. +> +> ```js +> // Add a custom parser for YAML +> fastify.addContentTypeParser('application/yaml', { parseAs: 'string' }, (req, body, done) => { +> done(null, YAML.parse(body)) +> }) +> +> fastify.post('/the/url', { +> schema: { +> body: { +> content: { +> 'application/json': { +> schema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } +> }, +> // Without this entry, application/yaml requests will NOT be validated +> 'application/yaml': { +> schema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } +> } +> } +> } +> } +> }, handler) +> ``` + Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) values to the types specified in the schema `type` keywords, both to pass validation and to use the correctly typed data afterwards. From deaeb4040f6d8b6588318ecc8a058af61322a375 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Sat, 7 Mar 2026 03:01:06 -0500 Subject: [PATCH 1283/1295] docs(ecosystem): add fastify-file-router (#6441) --- docs/Guides/Ecosystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index 549b967e39f..e846b6b10ef 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -368,6 +368,8 @@ section. Fastify feature flags plugin with multiple providers support (e.g. env, [config](https://lorenwest.github.io/node-config/), [unleash](https://github.com/Unleash/unleash)). +- [`fastify-file-router`](https://github.com/bhouston/fastify-file-router) + A typesafe TanStack Start / Next.JS-style router with JSON + Zod schema support. - [`fastify-file-routes`](https://github.com/spa5k/fastify-file-routes) Get Next.js based file system routing into fastify. - [`fastify-formidable`](https://github.com/climba03003/fastify-formidable) From e4474cfd794b1ff3cf4cbdb60c465c69fed4db1d Mon Sep 17 00:00:00 2001 From: Matthias Dittgen Date: Sat, 7 Mar 2026 09:08:02 +0100 Subject: [PATCH 1284/1295] docs: add fastify-svelte-view to Ecosystem list (#6453) * docs: add fastify-svelte-view to Ecosystem list * Fix markdown lint complain * fix linter complain --------- Co-authored-by: Jean <110341611+jean-michelet@users.noreply.github.com> Co-authored-by: Frazer Smith --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index e846b6b10ef..f6632c02607 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -207,6 +207,9 @@ section. A Fastify plugin that enforces naming pattern for routes path. - [`@joggr/fastify-prisma`](https://github.com/joggrdocs/fastify-prisma) A plugin for accessing an instantiated PrismaClient on your server. +- [`@matths/fastify-svelte-view`](https://github.com/matths/fastify-svelte-view) + A Fastify plugin for rendering Svelte components with support for SSR + (Server-Side Rendering), CSR (Client-Side Rendering), and SSR with hydration. - [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit) A plugin to close the server gracefully - [`@mgcrea/fastify-request-logger`](https://github.com/mgcrea/fastify-request-logger) From 3b0f76993d51f8db662814c693f4ebea8d97cc95 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 7 Mar 2026 09:44:16 +0100 Subject: [PATCH 1285/1295] fix: anchor keyValuePairsReg to prevent quadratic backtracking (#6558) --- lib/content-type.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/content-type.js b/lib/content-type.js index db548de2efb..1e3e51562d2 100644 --- a/lib/content-type.js +++ b/lib/content-type.js @@ -2,12 +2,14 @@ /** * keyValuePairsReg is used to split the parameters list into associated - * key value pairings. + * key value pairings. The leading `(?:^|;)\s*` anchor ensures the regex + * only attempts matches at parameter boundaries, preventing quadratic + * backtracking on malformed input. * * @see https://httpwg.org/specs/rfc9110.html#parameter * @type {RegExp} */ -const keyValuePairsReg = /([\w!#$%&'*+.^`|~-]+)=([^;]*)/gm +const keyValuePairsReg = /(?:^|;)\s*([\w!#$%&'*+.^`|~-]+)=([^;]*)/gm /** * typeNameReg is used to validate that the first part of the media-type From c9bcde46609314b175b738970f9d6a6a9cd71de6 Mon Sep 17 00:00:00 2001 From: Espen Date: Sat, 7 Mar 2026 09:52:03 +0100 Subject: [PATCH 1286/1295] docs: added note on handling of invalid URLs in setNotFoundHandler (#5661) --- docs/Reference/Server.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 3a3426f3847..3b859f50365 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -1784,7 +1784,10 @@ call is encapsulated by prefix, so different plugins can set different not found handlers if a different [`prefix` option](./Plugins.md#route-prefixing-option) is passed to `fastify.register()`. The handler is treated as a regular route handler so requests will go through the full [Fastify -lifecycle](./Lifecycle.md#lifecycle). *async-await* is supported as well. +lifecycle](./Lifecycle.md#lifecycle) for unexisting URLs. +*async-await* is supported as well. +Badly formatted URLs are sent to the [`onBadUrl`](#onbadurl) +handler instead. You can also register [`preValidation`](./Hooks.md#route-hooks) and [`preHandler`](./Hooks.md#route-hooks) hooks for the 404 handler. From 4a5304f4a00be6a329a069de2b4dc82c12cb3f19 Mon Sep 17 00:00:00 2001 From: Oluchi Ezeifedikwa Date: Sat, 7 Mar 2026 09:55:57 +0100 Subject: [PATCH 1287/1295] docs(guides): update codemod links (#6479) --- docs/Guides/Migration-Guide-V4.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md index 3dd4f23a911..7fc50a74ff8 100644 --- a/docs/Guides/Migration-Guide-V4.md +++ b/docs/Guides/Migration-Guide-V4.md @@ -14,24 +14,19 @@ To help with the upgrade, we’ve worked with the team at publish codemods that will automatically update your code to many of the new APIs and patterns in Fastify v4. -Run the following -[migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to -automatically update your code to Fastify v4: -``` +```bash npx codemod@latest fastify/4/migration-recipe ``` +This applies the following codemods: -This will run the following codemods: - -- [`fastify/4/remove-app-use`](https://go.codemod.com/fastify-4-remove-app-use) -- [`fastify/4/reply-raw-access`](https://go.codemod.com/fastify-4-reply-raw-access) -- [`fastify/4/wrap-routes-plugin`](https://go.codemod.com/fastify-4-wrap-routes-plugin) -- [`fastify/4/await-register-calls`](https://go.codemod.com/fastify-4-await-register-calls) +- fastify/4/remove-app-use +- fastify/4/reply-raw-access +- fastify/4/wrap-routes-plugin +- fastify/4/await-register-calls -Each of these codemods automates the changes listed in the v4 migration guide. -For a complete list of available Fastify codemods and further details, -see [Codemod Registry](https://go.codemod.com/fastify). +For information on the migration recipe, see +https://app.codemod.com/registry/fastify/4/migration-recipe. ## Breaking Changes From 25a70ffbe0eae989e3048c8351b721c8c32d21c9 Mon Sep 17 00:00:00 2001 From: Avi Fenesh <55848801+avifenesh@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:58:01 +0200 Subject: [PATCH 1288/1295] docs: add @glidemq/fastify to community plugins list (#6560) --- docs/Guides/Ecosystem.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md index f6632c02607..15dd405140f 100644 --- a/docs/Guides/Ecosystem.md +++ b/docs/Guides/Ecosystem.md @@ -188,6 +188,9 @@ section. A Fastify plugin that protects against No(n)SQL injection by sanitizing data. - [`@exortek/remix-fastify`](https://github.com/ExorTek/remix-fastify) Fastify plugin for Remix. +- [`@glidemq/fastify`](https://github.com/avifenesh/glidemq-fastify) + Queue management plugin for glide-mq with REST API endpoints, SSE events, + and in-memory testing mode. Powered by Valkey/Redis Streams. - [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server) Tiny (~5k), Fast, KISS, and dependency-free Node.js library to make your Fastify API graceful. From 375e136074c77347a9bbbf6c02ad2d106a88fd76 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 7 Mar 2026 10:42:42 +0100 Subject: [PATCH 1289/1295] Bumped v5.8.2 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index fdde55f835b..343ce5de5c6 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '5.8.1' +const VERSION = '5.8.2' const Avvio = require('avvio') const http = require('node:http') diff --git a/package.json b/package.json index 04bd9519cd9..d0cae6c0257 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "5.8.1", + "version": "5.8.2", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 7a11eead79620120a9e2be9f15932acecbdb164e Mon Sep 17 00:00:00 2001 From: Antonio Tripodi Date: Sat, 7 Mar 2026 18:19:32 +0100 Subject: [PATCH 1290/1295] docs(readme): add @Tony133 to plugin team (#6565) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bc8007bece6..403563ce97c 100644 --- a/README.md +++ b/README.md @@ -345,6 +345,7 @@ listed in alphabetical order. * [__Frazer Smith__](https://github.com/Fdawgs), * [__Manuel Spigolon__](https://github.com/eomm), , +* [__Antonio Tripodi__](https://github.com/Tony133), ### Emeritus Contributors Great contributors to a specific area of the Fastify ecosystem will be invited From 8abcd98c6efcba74345c84af5bb00adac5051d43 Mon Sep 17 00:00:00 2001 From: Illia <72549318+kyrylchenko@users.noreply.github.com> Date: Sun, 8 Mar 2026 07:40:07 -0700 Subject: [PATCH 1291/1295] Updated Plugins-Guide.md; Changed "fastify" to "instance" during plugin registration to showcase that it's added as a child (#6566) --- docs/Guides/Plugins-Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index 0d72a9740cb..433d5af7faa 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -147,7 +147,7 @@ fastify.register((instance, opts, done) => { instance.decorate('util', (a, b) => a + b) console.log(instance.util('that is ', 'awesome')) - fastify.register((instance, opts, done) => { + instance.register((instance, opts, done) => { console.log(instance.util('that is ', 'awesome')) // This will not throw an error done() }) From da9b338c295c635e96e87f10b568cbdc38132688 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:10:11 +0800 Subject: [PATCH 1292/1295] test: use fastify.test in test case (#6568) --- test/client-timeout.test.js | 2 +- test/close.test.js | 4 +-- test/constrained-routes.test.js | 12 +++---- test/hooks.test.js | 36 ++++++++++---------- test/http-methods/get.test.js | 2 +- test/http-methods/lock.test.js | 6 ++-- test/http-methods/propfind.test.js | 2 +- test/http-methods/proppatch.test.js | 6 ++-- test/https/https.test.js | 4 +-- test/internals/reply.test.js | 8 ++--- test/internals/request.test.js | 18 +++++----- test/max-requests-per-socket.test.js | 12 +++---- test/request-error.test.js | 6 ++-- test/schema-examples.test.js | 4 +-- test/schema-feature.test.js | 30 ++++++++--------- test/schema-serialization.test.js | 12 +++---- test/schema-special-usage.test.js | 10 +++--- test/schema-validation.test.js | 4 +-- test/skip-reply-send.test.js | 2 +- test/trust-proxy.test.js | 18 +++++----- test/types/logger.test-d.ts | 50 ++++++++++++++-------------- test/types/route.test-d.ts | 6 ++-- 22 files changed, 127 insertions(+), 127 deletions(-) diff --git a/test/client-timeout.test.js b/test/client-timeout.test.js index 2205b2a4384..1b858bc99a1 100644 --- a/test/client-timeout.test.js +++ b/test/client-timeout.test.js @@ -24,7 +24,7 @@ test('requestTimeout should return 408', (t, done) => { let data = Buffer.alloc(0) const socket = connect(fastify.server.address().port) - socket.write('POST / HTTP/1.1\r\nHost: example.com\r\nConnection-Length: 1\r\n') + socket.write('POST / HTTP/1.1\r\nHost: fastify.test\r\nConnection-Length: 1\r\n') socket.on('data', c => (data = Buffer.concat([data, c]))) socket.on('end', () => { diff --git a/test/close.test.js b/test/close.test.js index f80eec8717f..810ffe4f3f7 100644 --- a/test/close.test.js +++ b/test/close.test.js @@ -230,7 +230,7 @@ test('Current opened connection should NOT continue to work after closing and re const port = fastify.server.address().port const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') client.on('error', function () { // Depending on the Operating System @@ -247,7 +247,7 @@ test('Current opened connection should NOT continue to work after closing and re t.assert.match(data.toString(), /Connection:\s*keep-alive/i) t.assert.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') }) }) }) diff --git a/test/constrained-routes.test.js b/test/constrained-routes.test.js index b8b057ae2b9..c025cfd506b 100644 --- a/test/constrained-routes.test.js +++ b/test/constrained-routes.test.js @@ -33,7 +33,7 @@ test('Should register a host constrained route', async t => { method: 'GET', url: '/', headers: { - host: 'example.com' + host: 'fastify.test' } }) @@ -65,9 +65,9 @@ test('Should register the same route with host constraints', async t => { fastify.route({ method: 'GET', url: '/', - constraints: { host: 'example.com' }, + constraints: { host: 'fastify.test' }, handler: (req, reply) => { - reply.send('example.com') + reply.send('fastify.test') } }) @@ -88,12 +88,12 @@ test('Should register the same route with host constraints', async t => { method: 'GET', url: '/', headers: { - host: 'example.com' + host: 'fastify.test' } }) t.assert.strictEqual(res.statusCode, 200) - t.assert.strictEqual(res.payload, 'example.com') + t.assert.strictEqual(res.payload, 'fastify.test') } { @@ -568,7 +568,7 @@ test('Should allow registering an unconstrained route after a constrained route' method: 'GET', url: '/', headers: { - host: 'example.com' + host: 'fastify.test' } }) t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from any other domain' }) diff --git a/test/hooks.test.js b/test/hooks.test.js index 0a4e2a7c150..f5aab8b2360 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -204,10 +204,10 @@ test('onRequest hook should support encapsulation / 2', (t, testDone) => { const fastify = Fastify() let pluginInstance - fastify.addHook('onRequest', () => {}) + fastify.addHook('onRequest', () => { }) fastify.register((instance, opts, done) => { - instance.addHook('onRequest', () => {}) + instance.addHook('onRequest', () => { }) pluginInstance = instance done() }) @@ -650,7 +650,7 @@ test('onRoute hook should preserve system route configuration', (t, testDone) => test('onRoute hook should preserve handler function in options of shorthand route system configuration', (t, testDone) => { t.plan(2) - const handler = (req, reply) => {} + const handler = (req, reply) => { } const fastify = Fastify({ exposeHeadRoutes: false }) fastify.register((instance, opts, done) => { @@ -889,10 +889,10 @@ test('onResponse hook should support encapsulation / 2', (t, testDone) => { const fastify = Fastify() let pluginInstance - fastify.addHook('onResponse', () => {}) + fastify.addHook('onResponse', () => { }) fastify.register((instance, opts, done) => { - instance.addHook('onResponse', () => {}) + instance.addHook('onResponse', () => { }) pluginInstance = instance done() }) @@ -959,10 +959,10 @@ test('onSend hook should support encapsulation / 1', (t, testDone) => { const fastify = Fastify() let pluginInstance - fastify.addHook('onSend', () => {}) + fastify.addHook('onSend', () => { }) fastify.register((instance, opts, done) => { - instance.addHook('onSend', () => {}) + instance.addHook('onSend', () => { }) pluginInstance = instance done() }) @@ -1426,7 +1426,7 @@ test('cannot add hook after binding', (t, testDone) => { t.assert.ifError(err) try { - instance.addHook('onRequest', () => {}) + instance.addHook('onRequest', () => { }) t.assert.fail() } catch (e) { testDone() @@ -2396,10 +2396,10 @@ test('preValidation hook should support encapsulation / 2', (t, testDone) => { const fastify = Fastify() let pluginInstance - fastify.addHook('preValidation', () => {}) + fastify.addHook('preValidation', () => { }) fastify.register((instance, opts, done) => { - instance.addHook('preValidation', () => {}) + instance.addHook('preValidation', () => { }) pluginInstance = instance done() }) @@ -2739,10 +2739,10 @@ test('preParsing hook should support encapsulation / 2', (t, testDone) => { const fastify = Fastify() let pluginInstance - fastify.addHook('preParsing', function a () {}) + fastify.addHook('preParsing', function a () { }) fastify.register((instance, opts, done) => { - instance.addHook('preParsing', function b () {}) + instance.addHook('preParsing', function b () { }) pluginInstance = instance done() }) @@ -3383,7 +3383,7 @@ test('onRequestAbort should be triggered', (t, testDone) => { const socket = connect(fastify.server.address().port) - socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') sleep(500).then(() => socket.destroy()) }) @@ -3434,7 +3434,7 @@ test('onRequestAbort should support encapsulation', (t, testDone) => { const socket = connect(fastify.server.address().port) - socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') sleep(500).then(() => socket.destroy()) }) @@ -3468,7 +3468,7 @@ test('onRequestAbort should handle errors / 1', (t, testDone) => { const socket = connect(fastify.server.address().port) - socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') sleep(500).then(() => socket.destroy()) }) @@ -3502,7 +3502,7 @@ test('onRequestAbort should handle errors / 2', (t, testDone) => { const socket = connect(fastify.server.address().port) - socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') sleep(500).then(() => socket.destroy()) }) @@ -3536,7 +3536,7 @@ test('onRequestAbort should handle async errors / 1', (t, testDone) => { const socket = connect(fastify.server.address().port) - socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') sleep(500).then(() => socket.destroy()) }) @@ -3571,7 +3571,7 @@ test('onRequestAbort should handle async errors / 2', (t, testDone) => { const socket = connect(fastify.server.address().port) - socket.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') sleep(500).then(() => socket.destroy()) }) diff --git a/test/http-methods/get.test.js b/test/http-methods/get.test.js index b9dea10db5d..8522a083c64 100644 --- a/test/http-methods/get.test.js +++ b/test/http-methods/get.test.js @@ -401,7 +401,7 @@ test('get test', async t => { path: '/port', method: 'GET', headers: { - host: 'example.com' + host: 'fastify.test' } }) diff --git a/test/http-methods/lock.test.js b/test/http-methods/lock.test.js index b0a00b668bd..6df050207fa 100644 --- a/test/http-methods/lock.test.js +++ b/test/http-methods/lock.test.js @@ -9,7 +9,7 @@ const bodySample = ` - http://example.org/~ejw/contact.html + http://fastify.test/~ejw/contact.html ` @@ -34,14 +34,14 @@ test('can be created - lock', t => { infinity - http://example.org/~ejw/contact.html + http://fastify.test/~ejw/contact.html Second-604800 urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4 - http://example.com/workspace/webdav/proposal.oc + http://fastify.test/workspace/webdav/proposal.oc diff --git a/test/http-methods/propfind.test.js b/test/http-methods/propfind.test.js index 385f5ec573d..1d6dd22ec8c 100644 --- a/test/http-methods/propfind.test.js +++ b/test/http-methods/propfind.test.js @@ -6,7 +6,7 @@ fastify.addHttpMethod('PROPFIND', { hasBody: true }) const bodySample = ` - + diff --git a/test/http-methods/proppatch.test.js b/test/http-methods/proppatch.test.js index d7ea0a612eb..c2ec2180b7d 100644 --- a/test/http-methods/proppatch.test.js +++ b/test/http-methods/proppatch.test.js @@ -6,7 +6,7 @@ fastify.addHttpMethod('PROPPATCH', { hasBody: true }) const bodySample = ` + xmlns:Z="http://ns.fastify.test/standards/z39.50/"> @@ -33,9 +33,9 @@ test('shorthand - proppatch', t => { .code(207) .send(` + xmlns:Z="http://ns.fastify.test/standards/z39.50/"> - http://www.example.com/bar.html + http://www.fastify.test/bar.html diff --git a/test/https/https.test.js b/test/https/https.test.js index 1e42c1fd6f6..7e0a9f3d5d1 100644 --- a/test/https/https.test.js +++ b/test/https/https.test.js @@ -121,7 +121,7 @@ test('https - headers', async (t) => { const result = await request('https://localhost:' + fastify.server.address().port, { method: 'GET', headers: { - host: 'example.com' + host: 'fastify.test' }, dispatcher: new Agent({ connect: { @@ -131,6 +131,6 @@ test('https - headers', async (t) => { }) t.assert.strictEqual(result.statusCode, 200) - t.assert.deepStrictEqual(await result.body.json(), { hello: 'world', hostname: 'example.com', port: null }) + t.assert.deepStrictEqual(await result.body.json(), { hello: 'world', hostname: 'fastify.test', port: null }) }) }) diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index eeb61c5e3c4..adfc352dc31 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -692,11 +692,11 @@ test('plain string with custom json content type should NOT be serialized as jso const customSamples = { collectionjson: { mimeType: 'application/vnd.collection+json', - sample: '{"collection":{"version":"1.0","href":"http://api.example.com/people/"}}' + sample: '{"collection":{"version":"1.0","href":"http://api.fastify.test/people/"}}' }, hal: { mimeType: 'application/hal+json', - sample: '{"_links":{"self":{"href":"https://api.example.com/people/1"}},"name":"John Doe"}' + sample: '{"_links":{"self":{"href":"https://api.fastify.test/people/1"}},"name":"John Doe"}' }, jsonapi: { mimeType: 'application/vnd.api+json', @@ -777,11 +777,11 @@ test('non-string with custom json content type SHOULD be serialized as json', as const customSamples = { collectionjson: { mimeType: 'application/vnd.collection+json', - sample: JSON.parse('{"collection":{"version":"1.0","href":"http://api.example.com/people/"}}') + sample: JSON.parse('{"collection":{"version":"1.0","href":"http://api.fastify.test/people/"}}') }, hal: { mimeType: 'application/hal+json', - sample: JSON.parse('{"_links":{"self":{"href":"https://api.example.com/people/1"}},"name":"John Doe"}') + sample: JSON.parse('{"_links":{"self":{"href":"https://api.fastify.test/people/1"}},"name":"John Doe"}') }, jsonapi: { mimeType: 'application/vnd.api+json', diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 51d9e23f2bd..7ca08eb6d56 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -214,7 +214,7 @@ test('Request with trust proxy', t => { t.plan(18) const headers = { 'x-forwarded-for': '2.2.2.2, 1.1.1.1', - 'x-forwarded-host': 'example.com' + 'x-forwarded-host': 'fastify.test' } const req = { method: 'GET', @@ -257,7 +257,7 @@ test('Request with trust proxy', t => { t.assert.strictEqual(request.log, 'log') t.assert.strictEqual(request.ip, '2.2.2.2') t.assert.deepStrictEqual(request.ips, ['ip', '1.1.1.1', '2.2.2.2']) - t.assert.strictEqual(request.host, 'example.com') + t.assert.strictEqual(request.host, 'fastify.test') t.assert.strictEqual(request.body, undefined) t.assert.strictEqual(request.method, 'GET') t.assert.strictEqual(request.url, '/') @@ -272,7 +272,7 @@ test('Request with trust proxy, encrypted', t => { t.plan(2) const headers = { 'x-forwarded-for': '2.2.2.2, 1.1.1.1', - 'x-forwarded-host': 'example.com' + 'x-forwarded-host': 'fastify.test' } const req = { method: 'GET', @@ -377,7 +377,7 @@ test('Request with trust proxy - x-forwarded-host header has precedence over hos t.plan(2) const headers = { 'x-forwarded-for': ' 2.2.2.2, 1.1.1.1', - 'x-forwarded-host': 'example.com', + 'x-forwarded-host': 'fastify.test', host: 'hostname' } const req = { @@ -390,13 +390,13 @@ test('Request with trust proxy - x-forwarded-host header has precedence over hos const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') t.assert.ok(request instanceof TpRequest) - t.assert.strictEqual(request.host, 'example.com') + t.assert.strictEqual(request.host, 'fastify.test') }) test('Request with trust proxy - handles multiple entries in x-forwarded-host/proto', t => { t.plan(3) const headers = { - 'x-forwarded-host': 'example2.com, example.com', + 'x-forwarded-host': 'example2.com, fastify.test', 'x-forwarded-proto': 'http, https' } const req = { @@ -409,7 +409,7 @@ test('Request with trust proxy - handles multiple entries in x-forwarded-host/pr const TpRequest = Request.buildRequest(Request, true) const request = new TpRequest('id', 'params', req, 'query', 'log') t.assert.ok(request instanceof TpRequest) - t.assert.strictEqual(request.host, 'example.com') + t.assert.strictEqual(request.host, 'fastify.test') t.assert.strictEqual(request.protocol, 'https') }) @@ -417,7 +417,7 @@ test('Request with trust proxy - plain', t => { t.plan(1) const headers = { 'x-forwarded-for': '2.2.2.2, 1.1.1.1', - 'x-forwarded-host': 'example.com' + 'x-forwarded-host': 'fastify.test' } const req = { method: 'GET', @@ -491,7 +491,7 @@ test('Request with trust proxy and undefined socket', t => { t.plan(1) const headers = { 'x-forwarded-for': '2.2.2.2, 1.1.1.1', - 'x-forwarded-host': 'example.com' + 'x-forwarded-host': 'fastify.test' } const req = { method: 'GET', diff --git a/test/max-requests-per-socket.test.js b/test/max-requests-per-socket.test.js index 7e4401e5f62..d27adbbf18b 100644 --- a/test/max-requests-per-socket.test.js +++ b/test/max-requests-per-socket.test.js @@ -17,20 +17,20 @@ test('maxRequestsPerSocket', (t, done) => { const port = fastify.server.address().port const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') client.once('data', data => { t.assert.match(data.toString(), /Connection:\s*keep-alive/i) t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) t.assert.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') client.once('data', data => { t.assert.match(data.toString(), /Connection:\s*close/i) t.assert.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') client.once('data', data => { t.assert.match(data.toString(), /Connection:\s*close/i) @@ -58,21 +58,21 @@ test('maxRequestsPerSocket zero should behave same as null', (t, done) => { const port = fastify.server.address().port const client = net.createConnection({ port }, () => { - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') client.once('data', data => { t.assert.match(data.toString(), /Connection:\s*keep-alive/i) t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) t.assert.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') client.once('data', data => { t.assert.match(data.toString(), /Connection:\s*keep-alive/i) t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) t.assert.match(data.toString(), /200 OK/i) - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') client.once('data', data => { t.assert.match(data.toString(), /Connection:\s*keep-alive/i) diff --git a/test/request-error.test.js b/test/request-error.test.js index 817dcd7a23f..d56c2e02dee 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -127,7 +127,7 @@ test('default clientError handler ignores ECONNRESET', (t, done) => { client.resume() client.write('GET / HTTP/1.1\r\n') - client.write('Host: example.com\r\n') + client.write('Host: fastify.test\r\n') client.write('Connection: close\r\n') client.write('\r\n\r\n') }) @@ -316,10 +316,10 @@ test('default clientError replies with bad request on reused keep-alive connecti client.resume() client.write('GET / HTTP/1.1\r\n') - client.write('Host: example.com\r\n') + client.write('Host: fastify.test\r\n') client.write('\r\n\r\n') client.write('GET /?a b HTTP/1.1\r\n') - client.write('Host: example.com\r\n') + client.write('Host: fastify.test\r\n') client.write('Connection: close\r\n') client.write('\r\n\r\n') }) diff --git a/test/schema-examples.test.js b/test/schema-examples.test.js index 96fd03f68ee..7292a8136ae 100644 --- a/test/schema-examples.test.js +++ b/test/schema-examples.test.js @@ -8,7 +8,7 @@ test('Example - URI $id', (t, done) => { t.plan(1) const fastify = Fastify() fastify.addSchema({ - $id: 'http://example.com/', + $id: 'http://fastify.test/', type: 'object', properties: { hello: { type: 'string' } @@ -20,7 +20,7 @@ test('Example - URI $id', (t, done) => { schema: { body: { type: 'array', - items: { $ref: 'http://example.com#/properties/hello' } + items: { $ref: 'http://fastify.test#/properties/hello' } } } }) diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index c999bd41459..6e54d1817f7 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -12,7 +12,7 @@ const { waitForCb } = require('./toolkit') const echoParams = (req, reply) => { reply.send(req.params) } const echoBody = (req, reply) => { reply.send(req.body) } -;['addSchema', 'getSchema', 'getSchemas', 'setValidatorCompiler', 'setSerializerCompiler'].forEach(f => { + ;['addSchema', 'getSchema', 'getSchemas', 'setValidatorCompiler', 'setSerializerCompiler'].forEach(f => { test(`Should expose ${f} function`, t => { t.plan(1) const fastify = Fastify() @@ -130,8 +130,8 @@ test('Get compilers is empty when settle on routes', (t, testDone) => { } } }, - validatorCompiler: ({ schema, method, url, httpPart }) => {}, - serializerCompiler: ({ schema, method, url, httpPart }) => {} + validatorCompiler: ({ schema, method, url, httpPart }) => { }, + serializerCompiler: ({ schema, method, url, httpPart }) => { } }, function (req, reply) { reply.send('ok') }) @@ -177,7 +177,7 @@ test('Cannot add schema for query and querystring', (t, testDone) => { const fastify = Fastify() fastify.get('/', { - handler: () => {}, + handler: () => { }, schema: { query: { type: 'object', @@ -956,7 +956,7 @@ test('Get schema anyway should not add `properties` if allOf is present', (t, te }) fastify.get('/', { - handler: () => {}, + handler: () => { }, schema: { querystring: fastify.getSchema('second'), response: { 200: fastify.getSchema('second') } @@ -996,7 +996,7 @@ test('Get schema anyway should not add `properties` if oneOf is present', (t, te }) fastify.get('/', { - handler: () => {}, + handler: () => { }, schema: { querystring: fastify.getSchema('second'), response: { 200: fastify.getSchema('second') } @@ -1036,7 +1036,7 @@ test('Get schema anyway should not add `properties` if anyOf is present', (t, te }) fastify.get('/', { - handler: () => {}, + handler: () => { }, schema: { querystring: fastify.getSchema('second'), response: { 200: fastify.getSchema('second') } @@ -1386,7 +1386,7 @@ test('onReady hook has the compilers ready', (t, testDone) => { done() }) - i.register(async (i, o) => {}) + i.register(async (i, o) => { }) i.addHook('onReady', function (done) { hookCallCounter++ @@ -1471,11 +1471,11 @@ test('Add schema order should not break the startup', (t, testDone) => { t.plan(1) const fastify = Fastify() - fastify.get('/', { schema: { random: 'options' } }, () => {}) + fastify.get('/', { schema: { random: 'options' } }, () => { }) fastify.register(fp((f, opts) => { f.addSchema({ - $id: 'https://example.com/bson/objectId', + $id: 'https://fastify.test/bson/objectId', type: 'string', pattern: '\\b[0-9A-Fa-f]{24}\\b' }) @@ -1487,11 +1487,11 @@ test('Add schema order should not break the startup', (t, testDone) => { params: { type: 'object', properties: { - id: { $ref: 'https://example.com/bson/objectId#' } + id: { $ref: 'https://fastify.test/bson/objectId#' } } } } - }, () => {}) + }, () => { }) fastify.ready(err => { t.assert.ifError(err) @@ -2174,7 +2174,7 @@ test('Should return a human-friendly error if response status codes are not spec test('setSchemaController: custom validator instance should not mutate headers schema', async t => { t.plan(2) - class Headers {} + class Headers { } const fastify = Fastify() fastify.setSchemaController({ @@ -2182,7 +2182,7 @@ test('setSchemaController: custom validator instance should not mutate headers s buildValidator: function () { return ({ schema, method, url, httpPart }) => { t.assert.ok(schema instanceof Headers) - return () => {} + return () => { } } } } @@ -2192,7 +2192,7 @@ test('setSchemaController: custom validator instance should not mutate headers s schema: { headers: new Headers() } - }, () => {}) + }, () => { }) await fastify.ready() }) diff --git a/test/schema-serialization.test.js b/test/schema-serialization.test.js index 72b881ffff1..d2ed451e38d 100644 --- a/test/schema-serialization.test.js +++ b/test/schema-serialization.test.js @@ -521,7 +521,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s const fastify = Fastify() fastify.addSchema({ - $id: 'http://example.com/asset.json', + $id: 'http://fastify.test/asset.json', $schema: 'http://json-schema.org/draft-07/schema#', title: 'Physical Asset', description: 'A generic representation of a physical asset', @@ -539,7 +539,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s model: { type: 'string' }, - location: { $ref: 'http://example.com/point.json#' } + location: { $ref: 'http://fastify.test/point.json#' } }, definitions: { inner: { @@ -551,7 +551,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s }) fastify.addSchema({ - $id: 'http://example.com/point.json', + $id: 'http://fastify.test/point.json', $schema: 'http://json-schema.org/draft-07/schema#', title: 'Longitude and Latitude Values', description: 'A geographical coordinate.', @@ -561,7 +561,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s 'longitude' ], properties: { - email: { $ref: 'http://example.com/asset.json#/definitions/inner' }, + email: { $ref: 'http://fastify.test/asset.json#/definitions/inner' }, latitude: { type: 'number', minimum: -90, @@ -579,11 +579,11 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s }) const schemaLocations = { - $id: 'http://example.com/locations.json', + $id: 'http://fastify.test/locations.json', $schema: 'http://json-schema.org/draft-07/schema#', title: 'List of Asset locations', type: 'array', - items: { $ref: 'http://example.com/asset.json#' } + items: { $ref: 'http://fastify.test/asset.json#' } } fastify.post('/', { diff --git a/test/schema-special-usage.test.js b/test/schema-special-usage.test.js index 1d1cd627022..85b466a1ba8 100644 --- a/test/schema-special-usage.test.js +++ b/test/schema-special-usage.test.js @@ -288,7 +288,7 @@ test("serializer read validator's schemas", (t, testDone) => { const ajvInstance = new AJV() const baseSchema = { - $id: 'http://example.com/schemas/base', + $id: 'http://fastify.test/schemas/base', definitions: { hello: { type: 'string' } }, @@ -299,10 +299,10 @@ test("serializer read validator's schemas", (t, testDone) => { } const refSchema = { - $id: 'http://example.com/schemas/ref', + $id: 'http://fastify.test/schemas/ref', type: 'object', properties: { - hello: { $ref: 'http://example.com/schemas/base#/definitions/hello' } + hello: { $ref: 'http://fastify.test/schemas/base#/definitions/hello' } } } @@ -332,7 +332,7 @@ test("serializer read validator's schemas", (t, testDone) => { fastify.get('/', { schema: { response: { - '2xx': ajvInstance.getSchema('http://example.com/schemas/ref').schema + '2xx': ajvInstance.getSchema('http://fastify.test/schemas/ref').schema } }, handler (req, res) { res.send({ hello: 'world', evict: 'this' }) } @@ -754,7 +754,7 @@ test('The default schema compilers should not be called when overwritten by the 200: { type: 'object' } } } - }, () => {}) + }, () => { }) await fastify.ready() }) diff --git a/test/schema-validation.test.js b/test/schema-validation.test.js index 86010a5bd90..c5006463255 100644 --- a/test/schema-validation.test.js +++ b/test/schema-validation.test.js @@ -875,7 +875,7 @@ test('Use items with $ref', (t, testDone) => { const fastify = Fastify() fastify.addSchema({ - $id: 'http://example.com/ref-to-external-validator.json', + $id: 'http://fastify.test/ref-to-external-validator.json', type: 'object', properties: { hello: { type: 'string' } @@ -884,7 +884,7 @@ test('Use items with $ref', (t, testDone) => { const body = { type: 'array', - items: { $ref: 'http://example.com/ref-to-external-validator.json#' } + items: { $ref: 'http://fastify.test/ref-to-external-validator.json#' } } fastify.post('/', { diff --git a/test/skip-reply-send.test.js b/test/skip-reply-send.test.js index 202a758f006..506bf62e388 100644 --- a/test/skip-reply-send.test.js +++ b/test/skip-reply-send.test.js @@ -201,7 +201,7 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { app.listen({ port: 0 }, err => { t.assert.ifError(err) const client = net.createConnection({ port: (app.server.address()).port }, () => { - client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') let chunks = '' client.setEncoding('utf8') diff --git a/test/trust-proxy.test.js b/test/trust-proxy.test.js index 0153e631605..ae7b7568a6d 100644 --- a/test/trust-proxy.test.js +++ b/test/trust-proxy.test.js @@ -7,7 +7,7 @@ const helper = require('./helper') const fetchForwardedRequest = async (fastifyServer, forHeader, path, protoHeader) => { const headers = { 'X-Forwarded-For': forHeader, - 'X-Forwarded-Host': 'example.com' + 'X-Forwarded-Host': 'fastify.test' } if (protoHeader) { headers['X-Forwarded-Proto'] = protoHeader @@ -55,7 +55,7 @@ test('trust proxy, not add properties to node req', async t => { t.after(() => app.close()) app.get('/trustproxy', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) + testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) @@ -78,7 +78,7 @@ test('trust proxy chain', async t => { t.after(() => app.close()) app.get('/trustproxychain', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) + testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) @@ -94,7 +94,7 @@ test('trust proxy function', async t => { t.after(() => app.close()) app.get('/trustproxyfunc', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port }) + testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) @@ -110,7 +110,7 @@ test('trust proxy number', async t => { t.after(() => app.close()) app.get('/trustproxynumber', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'example.com', port: app.server.address().port }) + testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) @@ -126,7 +126,7 @@ test('trust proxy IP addresses', async t => { t.after(() => app.close()) app.get('/trustproxyipaddrs', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'example.com', port: app.server.address().port }) + testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) @@ -142,15 +142,15 @@ test('trust proxy protocol', async t => { t.after(() => app.close()) app.get('/trustproxyprotocol', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem', host: 'example.com', port: app.server.address().port }) + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem', host: 'fastify.test', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) app.get('/trustproxynoprotocol', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'http', host: 'example.com', port: app.server.address().port }) + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'http', host: 'fastify.test', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) app.get('/trustproxyprotocols', function (req, reply) { - testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'dolor', host: 'example.com', port: app.server.address().port }) + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'dolor', host: 'fastify.test', port: app.server.address().port }) reply.code(200).send({ ip: req.ip, host: req.host }) }) diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index 04fc88bcad4..847cf768ff8 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -1,19 +1,19 @@ +import * as fs from 'node:fs' +import { IncomingMessage, Server, ServerResponse } from 'node:http' +import P from 'pino' import { expectAssignable, expectDeprecated, expectError, expectNotAssignable, expectType } from 'tsd' import fastify, { - FastifyLogFn, - LogLevel, FastifyBaseLogger, + FastifyLogFn, + FastifyReply, FastifyRequest, - FastifyReply + LogLevel } from '../../fastify' -import { Server, IncomingMessage, ServerResponse } from 'node:http' -import * as fs from 'node:fs' -import P from 'pino' import { FastifyLoggerInstance, ResSerializerReply } from '../../types/logger' expectType(fastify().log) -class Foo {} +class Foo { } ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(logLevel => { expectType( @@ -62,19 +62,19 @@ class CustomLoggerImpl implements CustomLogger { const customLogger = new CustomLoggerImpl() const serverWithCustomLogger = fastify< -Server, -IncomingMessage, -ServerResponse, -CustomLoggerImpl + Server, + IncomingMessage, + ServerResponse, + CustomLoggerImpl >({ logger: customLogger }) expectType(serverWithCustomLogger.log) const serverWithPino = fastify< -Server, -IncomingMessage, -ServerResponse, -P.Logger + Server, + IncomingMessage, + ServerResponse, + P.Logger >({ logger: P({ level: 'info', @@ -99,9 +99,9 @@ serverWithPino.get('/', function (request) { }) const serverWithLogOptions = fastify< -Server, -IncomingMessage, -ServerResponse + Server, + IncomingMessage, + ServerResponse >({ logger: { level: 'info' @@ -111,9 +111,9 @@ ServerResponse expectType(serverWithLogOptions.log) const serverWithFileOption = fastify< -Server, -IncomingMessage, -ServerResponse + Server, + IncomingMessage, + ServerResponse >({ logger: { level: 'info', @@ -150,7 +150,7 @@ const serverWithPinoConfig = fastify({ method: 'method', url: 'url', version: 'version', - host: 'hostname', + host: 'fastify.test', remoteAddress: 'remoteAddress', remotePort: 80, other: '' @@ -213,7 +213,7 @@ const serverAutoInferredSerializerObjectOption = fastify({ method: 'method', url: 'url', version: 'version', - host: 'hostname', + host: 'fastify.test', remoteAddress: 'remoteAddress', remotePort: 80, other: '' @@ -268,8 +268,8 @@ const childParent = fastify().log expectType(childParent.child({}, { level: 'info' })) expectType(childParent.child({}, { level: 'silent' })) expectType(childParent.child({}, { redact: ['pass', 'pin'] })) -expectType(childParent.child({}, { serializers: { key: () => {} } })) -expectType(childParent.child({}, { level: 'info', redact: ['pass', 'pin'], serializers: { key: () => {} } })) +expectType(childParent.child({}, { serializers: { key: () => { } } })) +expectType(childParent.child({}, { level: 'info', redact: ['pass', 'pin'], serializers: { key: () => { } } })) // no option pass expectError(childParent.child()) diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index e1acdeb1240..26e4ae4647f 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -481,19 +481,19 @@ expectType(fastify().hasRoute({ expectType(fastify().hasRoute({ url: '/', method: 'GET', - constraints: { host: 'auth.fastify.dev' } + constraints: { host: 'auth.fastify.test' } })) expectType(fastify().hasRoute({ url: '/', method: 'GET', - constraints: { host: /.*\.fastify\.dev$/ } + constraints: { host: /.*\.fastify\.test$/ } })) expectType(fastify().hasRoute({ url: '/', method: 'GET', - constraints: { host: /.*\.fastify\.dev$/, version: '1.2.3' } + constraints: { host: /.*\.fastify\.test$/, version: '1.2.3' } })) expectType(fastify().hasRoute({ From 7248a6b39b2769d2ae3b476817287d1d649c507f Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:10:20 +0800 Subject: [PATCH 1293/1295] docs: use fastify.example in documentation (#6567) --- docs/Guides/Plugins-Guide.md | 2 +- docs/Guides/Recommendations.md | 10 +++++----- docs/Reference/Routes.md | 8 ++++---- docs/Reference/Validation-and-Serialization.md | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md index 433d5af7faa..6edb74fa82e 100644 --- a/docs/Guides/Plugins-Guide.md +++ b/docs/Guides/Plugins-Guide.md @@ -412,7 +412,7 @@ function dbPlugin (fastify, opts, done) { }) } -fastify.register(fp(dbPlugin), { url: 'https://example.com' }) +fastify.register(fp(dbPlugin), { url: 'https://fastify.example' }) fastify.register(require('your-plugin'), parent => { return { connection: parent.db, otherOption: 'foo-bar' } }) diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index 0d08cc36d01..fb0906166e0 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -112,7 +112,7 @@ frontend proxy-ssl # Here we define rule pairs to handle static resources. Any incoming request # that has a path starting with `/static`, e.g. - # `https://one.example.com/static/foo.jpeg`, will be redirected to the + # `https://one.fastify.example/static/foo.jpeg`, will be redirected to the # static resources server. acl is_static path -i -m beg /static use_backend static-backend if is_static @@ -122,10 +122,10 @@ frontend proxy-ssl # the incoming hostname and define a boolean indicating if it is a match. # The `use_backend` line is used to direct the traffic if the boolean is # true. - acl example1 hdr_sub(Host) one.example.com + acl example1 hdr_sub(Host) one.fastify.example use_backend example1-backend if example1 - acl example2 hdr_sub(Host) two.example.com + acl example2 hdr_sub(Host) two.fastify.example use_backend example2-backend if example2 # Finally, we have a fallback redirect if none of the requested hosts @@ -144,14 +144,14 @@ backend default-server # requests over TLS, but that is outside the scope of this example. server server1 10.10.10.2:80 -# This backend configuration will serve requests for `https://one.example.com` +# This backend configuration will serve requests for `https://one.fastify.example` # by proxying requests to three backend servers in a round-robin manner. backend example1-backend server example1-1 10.10.11.2:80 server example1-2 10.10.11.2:80 server example2-2 10.10.11.3:80 -# This one serves requests for `https://two.example.com` +# This one serves requests for `https://two.fastify.example` backend example2-backend server example2-1 10.10.12.2:80 server example2-2 10.10.12.2:80 diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md index 298ea245f16..3cb215319c4 100644 --- a/docs/Reference/Routes.md +++ b/docs/Reference/Routes.md @@ -708,9 +708,9 @@ specified as strings for exact matches or RegExps for arbitrary host matching. fastify.route({ method: 'GET', url: '/', - constraints: { host: 'auth.fastify.dev' }, + constraints: { host: 'auth.fastify.example' }, handler: function (request, reply) { - reply.send('hello world from auth.fastify.dev') + reply.send('hello world from auth.fastify.example') } }) @@ -718,7 +718,7 @@ fastify.inject({ method: 'GET', url: '/', headers: { - 'Host': 'example.com' + 'Host': 'fastify.example' } }, (err, res) => { // 404 because the host doesn't match the constraint @@ -742,7 +742,7 @@ matching wildcard subdomains (or any other pattern): fastify.route({ method: 'GET', url: '/', - constraints: { host: /.*\.fastify\.dev/ }, // will match any subdomain of fastify.dev + constraints: { host: /.*\.fastify\.example/ }, // will match any subdomain of fastify.dev handler: function (request, reply) { reply.send('hello world from ' + request.headers.host) } diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md index 204492157a2..399c7a202f6 100644 --- a/docs/Reference/Validation-and-Serialization.md +++ b/docs/Reference/Validation-and-Serialization.md @@ -67,7 +67,7 @@ keyword. Here is an overview of how references work: ```js fastify.addSchema({ - $id: 'http://example.com/', + $id: 'http://fastify.example/', type: 'object', properties: { hello: { type: 'string' } @@ -79,7 +79,7 @@ fastify.post('/', { schema: { body: { type: 'array', - items: { $ref: 'http://example.com#/properties/hello' } + items: { $ref: 'http://fastify.example#/properties/hello' } } } }) From 128ca6e22f6a7f6c687c81c0e48c8bbe68cca04d Mon Sep 17 00:00:00 2001 From: Max Petrusenko <37906420+maxpetrusenko@users.noreply.github.com> Date: Mon, 9 Mar 2026 04:51:46 -0400 Subject: [PATCH 1294/1295] docs: add common performance degradation guidance (#6520) --- docs/Guides/Recommendations.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md index fb0906166e0..3747a2dc10c 100644 --- a/docs/Guides/Recommendations.md +++ b/docs/Guides/Recommendations.md @@ -7,6 +7,7 @@ This document contains a set of recommendations when using Fastify. - [Use A Reverse Proxy](#use-a-reverse-proxy) - [HAProxy](#haproxy) - [Nginx](#nginx) +- [Common Causes Of Performance Degradation](#common-causes-of-performance-degradation) - [Kubernetes](#kubernetes) - [Capacity Planning For Production](#capacity) - [Running Multiple Instances](#multiple) @@ -282,6 +283,30 @@ server { [nginx]: https://nginx.org/ +## Common Causes Of Performance Degradation + +These patterns can increase latency or reduce throughput in production: + +- Prefer static or simple parametric routes on hot paths. RegExp routes are + expensive, and routes with many parameters can also hurt router performance. + See [Routes - Url building](../Reference/Routes.md#url-building). +- Use route constraints carefully. Version constraints can degrade router + performance, and asynchronous custom constraints should be treated as a last + resort. See [Routes - Constraints](../Reference/Routes.md#constraints). +- Prefer Fastify plugins/hooks over generic middleware when possible. Fastify's + middleware adapters work, but native integrations are typically better for + performance-sensitive paths. See [Middleware](../Reference/Middleware.md). +- Define response schemas to speed up JSON serialization. See + [Getting Started - Serialize your data](./Getting-Started.md#serialize-data). +- Keep Ajv `allErrors` disabled by default. Enable it only when detailed + validation feedback is needed (for example, form-heavy APIs), and avoid it + on latency-sensitive endpoints. When `allErrors: true` is enabled, validation + can do more work per request and make denial-of-service attacks easier on + untrusted inputs. + See also: + - [Validation and Serialization - Validator Compiler](../Reference/Validation-and-Serialization.md#schema-validator) + - [Ajv Security Risks of Trusted Schemas](https://ajv.js.org/security.html#security-risks-of-trusted-schemas). + ## Kubernetes From e02d602f32bb3c85fcd3a33d84c2e1e8ab25faff Mon Sep 17 00:00:00 2001 From: deepvamja <143236575+Deepvamja@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:44:21 +0530 Subject: [PATCH 1295/1295] docs(server): fix camelCase anchor links in TOC (#6530) * docs: fix camelCase anchor links in Server.md TOC * Update docs/Reference/Server.md Co-authored-by: Antonio Tripodi Signed-off-by: deepvamja <143236575+Deepvamja@users.noreply.github.com> * Update docs/Reference/Server.md Co-authored-by: Antonio Tripodi Signed-off-by: deepvamja <143236575+Deepvamja@users.noreply.github.com> * docs: revert addHttpMethod anchor to original casing * docs: fix setGenReqId casing in code examples * docs: revert setGenReqId heading to original casing * docs: fix extra backticks on loggerInstance in TOC --------- Signed-off-by: deepvamja <143236575+Deepvamja@users.noreply.github.com> Co-authored-by: Antonio Tripodi --- docs/Reference/Server.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md index 3b859f50365..36827525808 100644 --- a/docs/Reference/Server.md +++ b/docs/Reference/Server.md @@ -21,7 +21,7 @@ describes the properties available in that options object. - [`onProtoPoisoning`](#onprotopoisoning) - [`onConstructorPoisoning`](#onconstructorpoisoning) - [`logger`](#logger) - - [`loggerInstance`](#loggerInstance) + - [`loggerInstance`](#loggerinstance) - [`disableRequestLogging`](#disablerequestlogging) - [`serverFactory`](#serverfactory) - [`requestIdHeader`](#requestidheader) @@ -87,7 +87,7 @@ describes the properties available in that options object. - [setNotFoundHandler](#setnotfoundhandler) - [setErrorHandler](#seterrorhandler) - [setChildLoggerFactory](#setchildloggerfactory) - - [setGenReqId](#setGenReqId) + - [setGenReqId](#setgenreqid) - [addConstraintStrategy](#addconstraintstrategy) - [hasConstraintStrategy](#hasconstraintstrategy) - [printRoutes](#printroutes) @@ -1943,7 +1943,7 @@ const fastify = require('fastify')({ The handler is bound to the Fastify instance and is fully encapsulated, so different plugins can set different logger factories. -#### setGenReqId +#### setgenreqid `fastify.setGenReqId(function (rawReq))` Synchronous function for setting the request-id